11.2 Essentials of writing R functions
11.2.1 Basics
The basic structure for defining a new function f_name()
:
<- function(<args>) {<body>} f_name
The function’s arguments
<args>
specify the inputs (or the data, represented as R objects) accepted by a function.
Each argument can be mandatory or optional (by providing defaults).The function’s
<body>
typically uses the inputs provided by<args>
to perform the task for which the function is created. It can contain arbitrary amounts of R code (including references to existing R objects and other functions). By default, the function returns the result of its last expression.
An example function:
<- function(x, exp = 1) {
power ^exp
x }
Note that the power()
function contains two arguments:
x
is mandatory, as no default is specified in the function definition.exp
is optional, as the default value of1
is used when no other value is provided when calling the function.
Example calls:
power(x = 2)
#> [1] 2
power(x = 2, exp = 2)
#> [1] 4
power(2, 3) # arguments omitted
#> [1] 8
power(exp = 2, x = 3) # arguments reversed
#> [1] 9
# Note:
power(c(1, 2, 3), 2)
#> [1] 1 4 9
power(2, c(1, 2, 3))
#> [1] 2 4 8
power(c(1, 2, 3), c(1, 2, 3))
#> [1] 1 4 27
Other checks:
power(NA)
power("A")
Guidance on evaluating functions:
First check the intended functionality. Which data types and data structure does it accept?
Then try testing the function’s limits (e.g., extreme, unusual, or missing inputs). Where does it break?
11.2.2 Advanced aspects of functions
Steps towards writing more complicated functions:
- The structure of function body
- Adding
return()
statements - Side effects and
...
arguments - Issues of style
Most function bodies are more complex.
Explicate the structure of function <body>
:
- Prepare: Initialize variables and check inputs,
- Main part(s),
- Output: `return()` results.
Explicit return()
Illustrate cases:
- Add an input check (as a conditional):
<- function(x, exp = 1) {
power_2a
# check inputs:
if (!is.numeric(x)){
message("Please note: x should be numeric.")
}
# main:
^exp
x
}
power_2a("A")
Note: Main part of function is still executed.
- Adding premature exit/early
return()
:
# (b)
<- function(x, exp = 1) {
power_2b
# check inputs:
if (!is.numeric(x)){
message("Please note: x should be numeric.")
return(paste0("You entered x = ", x)) # stops the function execution/exits the function
}
# main:
^exp
x
}
power_2b("A") # stops when if is TRUE
#> [1] "You entered x = A"
Note: Premature exit when if
is TRUE.
- Adding explicit exit/final
return()
:
# (c) Structure: 3 explicit parts;
<- function(x, exp = 1) {
power_2c
# A. prepare:
# check inputs:
if (!is.numeric(x)){
message("Please note: x should be numeric.")
return(paste0("You entered x = ", x)) # stops the function execution/exits the function
}
# initialize variables:
<- NA # "something"
output
# B. main:
<- x^exp
output
# C. return the result:
return(output)
# output # (would also work)
}
power_2c("A") # stops earlier
#> [1] "You entered x = A"
power_2c(2) # output is returned
#> [1] 2
- Question: What happens when omitting the final return?
# (c) 3 explicit parts;
<- function(x, exp = 1) {
power_2d
# A. prepare:
# initialize variables:
<- NA # "something"
output
# check inputs:
if (!is.numeric(x)){
message("Please note: x should be numeric.")
return(paste0("You entered x = ", x)) # stops the function execution/exits the function
}
# B. main:
<- x^exp
output
# C. return the result:
# return(output)
}
power_2d(2) # nothing is returned!
Note: Nothing is returned!