Three levels to compose R functions

Intro to my functional programming series

Introducing the 3 ways to compose functions in R. These are all my discoveries
R
data-science
tidyverse
purrr
functional-programming
fp
Author

Joshua Marie

Published

December 15, 2025

Maybe few people who use R have forgotten already that R is functional by heart. R has Python dogma OO system, thanks to Reference Class (RC) and R6. R functions can be treated as Lisp’s macros, where it can let you meddle the function and abstract syntax tree (AST) of the function call.

R has few ways to compose a function, divided by three (3) levels:

  1. Manual Typing
  2. Higher-order Functions
  3. Programmatic

1 Level 1: Manual Composition

2 Level 2: Using Higher-order Functions

How about we comprises the existing functions and give birth to another function? We can easily make that with purrr::compose(). How about we create a function out of the function? That’s function operators in action.

Higher-order functions create new functions by combining or modifying existing ones, reducing manual code writing.

2.1 Function Composition with purrr::compose()

The nice thing about this is that it takes multiple functions and creates a single new function that applies them in sequence (the default direction is backwards).

So, instead of writing this manually:

transform = function(x) {
    sqrt(mean(log(x)))
}
transform(1:5)
[1] 0.9785184

We can compose it through purrr::compose():

purrr::compose(sqrt, mean, log)(1:5)
[1] 0.9785184

Furthermore, this is also (almost) as readable as using pipe:

1:5 |> log() |> mean() |> sqrt()
[1] 0.9785184

I have a different blog mentioning on how bad can the nested function call go.

This function can be read from left to right (by default, it is read vice-versa):

purrr::compose(sqrt, mean, log, .dir = "forward")(1:5)
[1] 0.5166883

Which is an equivalent of:

1:5 |> sqrt() |> mean() |> log()
[1] 0.5166883

2.2 Partial Application with purrr::partial()

2.3 Function Operators in general

3 Level 3: Programmatic Approach

Swear, this is not easy to do — you need at least better understanding on how to build / generate expressions in R, and this involves understanding metaprogramming in R — Yes, most of the part on manipulating ASTs.

The rlang::new_function() provides an API that programmatically constructs a function expression (yes, it does creates formals, body, and an environment — 3 main components of R functions). Keep in mind that the construction of the function expression with rlang::new_function() happens in runtime.

3.1 Start with the basic first

The new_function() function takes three arguments: formal arguments, the function body, and optionally an environment. Let’s start with a function that squares the number:

box::use(
    rlang[new_function, expr, caller_env, call2]
)

square = new_function(
    args = list(x = expr()), 
    body = call2("{", quote(x^2)), 
    env = caller_env()
)
square(5)
[1] 25

Or instead of list() in args parameter, how about using pairlist2() instead?

Alright, you might be asking: What’s the point of using new_function() if we can just use function() instead? The purpose of this

And the thing is, you can automatically generate a function expression with this function.

4 Resources