Functions in R
Learn how to define and use functions in R, including default arguments, scope, recursion, closures, and the functional apply family with Docker-ready examples
Functions are the heart of R. Almost everything you do in R—from print() to sum() to fitting a regression model—is a function call, and the language treats functions themselves as ordinary values you can store, pass around, and return. This first-class treatment of functions is a direct inheritance from R’s Scheme and Lisp ancestry, and it makes R far more functional in spirit than its data-analysis reputation suggests.
In this tutorial you’ll learn how to define your own functions, supply default and named arguments, understand R’s lexical scoping rules, write recursive functions, and use closures and the apply family to write expressive, vectorized code. Because R is a multi-paradigm language, we’ll emphasize the functional patterns that R programmers reach for in practice rather than forcing imperative loops where a single higher-order function will do.
By the end you’ll be comfortable writing reusable functions and thinking the way experienced R users do: in terms of transformations applied to whole vectors and data structures.
Defining and Calling Functions
In R you create a function with the function keyword and assign it to a name using the standard assignment operator <-. A function automatically returns the value of its last evaluated expression, so an explicit return() is optional.
Create a file named functions_basic.R:
| |
Because R has no native tuple type, returning a named list is the idiomatic way to hand back more than one value. The caller then pulls out fields with the $ operator.
Default and Named Arguments
R has a flexible argument-matching system. Parameters can declare default values, callers can pass arguments by name in any order, and the special ... parameter captures a variable number of arguments.
Create a file named functions_args.R:
| |
Named arguments make function calls self-documenting, which is why so much of R’s built-in API relies on them. The ... mechanism is how flexible functions like paste(), c(), and cat() accept any number of inputs.
Variable Scope
R uses lexical (static) scoping. Variables assigned inside a function are local to that function and do not leak out. To deliberately modify a variable in an enclosing scope, R provides the “super-assignment” operator <<-.
Create a file named functions_scope.R:
| |
Notice that ordinary assignment inside increment() never touches the global counter, while <<- inside make_deposit() does. Reaching for <<- is uncommon in day-to-day analysis code, but it is the foundation of stateful closures, which we’ll see shortly.
Recursion
Functions in R can call themselves, making recursion a natural fit for problems with self-similar structure such as factorials and the Fibonacci sequence.
Create a file named functions_recursion.R:
| |
R is not optimized for deep recursion—it has a call-stack limit and no tail-call optimization—so for performance-critical numeric work you’d usually prefer R’s vectorized built-ins (R actually ships a fast factorial() function). Recursion remains valuable for tree-like and divide-and-conquer problems where it expresses the logic most clearly.
Higher-Order Functions and Closures
This is where R’s functional heritage shines. Functions are first-class values: you can pass them as arguments, return them from other functions, and create them anonymously. Instead of writing explicit loops, idiomatic R applies functions over vectors with the apply family and the Filter/Map/Reduce trio.
Create a file named functions_higher_order.R:
| |
The closure multiplier() “remembers” the factor value from the scope in which it was created, so triple permanently multiplies by 3. The \(x) syntax introduced in R 4.1 is a concise lambda shorthand equivalent to function(x). Reaching for sapply, Filter, and Reduce instead of for loops produces shorter code and aligns with how R evaluates whole vectors at once.
Running with Docker
Run each example using the official R image—no local R installation required.
| |
Expected Output
# functions_basic.R
[1] "Hello, Ada"
[1] 81
Total: 20
Mean: 5
# functions_args.R
[1] 25
[1] 1024
Grace is 30 years old and lives in Auckland
[1] 15
# functions_scope.R
Inside function: 11
Outside function: 10
After deposit: 15
# functions_recursion.R
[1] 120
[1] 0 1 1 2 3 5 8 13
# functions_higher_order.R
[1] 20
[1] 1 4 9 16 25
[1] 1 8 27 64 125
[1] 30
[1] 2 4
[1] 15
Key Concepts
- Functions are values: Defined with
function(...) { ... }and assigned with<-, functions are first-class objects you can pass, store, and return. - Implicit return: A function returns its last evaluated expression automatically;
return()is only needed for early exits. - Return multiple values with a list: R has no tuple type, so bundle several outputs in a named
listand access them with$. - Flexible arguments: Parameters support default values, name-based matching in any order, and the
...catch-all for variadic functions. - Lexical scope and
<<-: Assignments inside a function are local; the super-assignment operator<<-reaches into the enclosing scope and powers stateful closures. - Closures capture their environment: A function returned from another function remembers the variables it was created with.
- Think functionally, not imperatively: Prefer
sapply,Filter,Map, andReduceover explicit loops—they’re more concise and match R’s vectorized evaluation model. - Concise lambdas: R 4.1+ supports the
\(x)shorthand as a drop-in replacement forfunction(x).
Comments
Loading comments...
Leave a Comment