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 lambda

9.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$mean

9.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 3

9.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.