Functions
Partial matching of argument names exists (mean(x, na = TRUE)) but avoid relying on it.
9.1 Defining and calling
square <- function(x) {
x^2
}
square(4) # 16 — the last expression is returned
add <- function(a, b = 10) a + b # default argument
add(5) # 15
add(b = 1, a = 2) # named arguments, any order
add(5, 3)
# Explicit return (use for early exit)
safe_sqrt <- function(x) {
if (x < 0) return(NA)
sqrt(x)
}9.2 Anonymous functions & lambdas
(function(x) x * 2)(5) # 10
sapply(1:3, function(x) x^2)
sapply(1:3, \(x) x^2) # R 4.1+ shorthand
purrr::map(1:3, ~ .x^2) # purrr formula lambda9.3 Arguments in depth
Partial matching of argument names exists (mean(x, na = TRUE)) but avoid relying on it.
f <- function(x, ...) { # ... = variadic arguments
args <- list(...)
length(args)
}
f(1, 2, 3, a = 4) # 3
g <- function(...) sum(...) # pass ... through
h <- function(...) names(list(...))
h(a = 1, b = 2) # "a" "b"
...length(); ..1 # inside functions: number of / first dot-argument
missing_demo <- function(x) if (missing(x)) "no x" else x
match.arg_demo <- function(type = c("linear", "quadratic")) {
type <- match.arg(type) # validates & picks the first by default
type
}9.4 Lazy evaluation
Arguments are evaluated only when used (as promises):
f <- function(x, y) { x }
f(1, stop("never evaluated")) # 1
g <- function(x, y = x * 2) { x <- 10; y }
g(1) # 20 — default evaluated when first used
force(x) # force evaluation (important in closures/factories)9.5 Returning multiple values
stats <- function(x) list(mean = mean(x), sd = sd(x), n = length(x))
s <- stats(c(1, 2, 3))
s$mean9.6 Functions are objects
formals(add); body(add); environment(add)
do.call(add, list(a = 1, b = 2)) # call with a list of args
Reduce(`+`, 1:5) # 15
Filter(\(x) x > 2, 1:5) # 3 4 5
Map(`+`, 1:3, 4:6) # list(5, 7, 9)
compose <- function(f, g) function(x) f(g(x))9.7 Recursion
R has no tail-call optimization in general (R 4.4 adds Tailcall() experimentally); deep recursion may hit expression nesting limits — prefer iteration or vectorization.
fact <- function(n) if (n <= 1) 1 else n * fact(n - 1)
fib <- function(n) if (n < 2) n else fib(n - 1) + fib(n - 2)9.8 Infix and replacement functions
`%||%` <- function(a, b) if (is.null(a)) b else a # also in base R 4.4+
`second<-` <- function(x, value) { x[2] <- value; x }
v <- 1:3; second(v) <- 100L; v # 1 100 39.9 Documenting & validating inputs
For richer checks use checkmate, rlang::arg_match(), vctrs.
area <- function(radius) {
stopifnot(is.numeric(radius), all(radius >= 0))
pi * radius^2
}🧪 Part I Exercises
Create a vector of the first 20 square numbers and select those divisible by 3.
Write fizzbuzz(n) returning a character vector using vectorized ifelse/case_when (no loop).
Write celsius_to_fahrenheit() that works on vectors and validates numeric input.
Count how many values in rnorm(1000) fall outside ±2; compare with the expected 4.6%.
Write a function describe(x) returning a named list of mean, median, sd, min, max, NA count.
Write is_prime(n) and use it to list primes below 100.
Use repeat to simulate rolling two dice until you get a double six; count the rolls.
Write my_cumsum(x) with a loop, then compare speed with cumsum() using system.time().
Write an infix operator %between% so 5 %between% c(1, 10) is TRUE.
Write a recursive function computing the n-th Fibonacci number, then a vectorized/iterative one.