Skip to Content
Course content

189: Function Factories in R

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've likely written plenty of functions that take arguments and return values. But as your R projects grow, you'll find yourself writing the same function over and over, just changing one constant value. For example, maybe you need five different functions to calculate tax for five different states, or three different scaling functions for different datasets.

The instinct for many is to use a loop to generate these functions. It seems efficient, but R has a quirk called "lazy evaluation" that often bites people right here. Take a look at this attempt to create a set of "power" functions.

# I want to create functions: pow2, pow3, pow4, pow5
funcs <- list()
for (i in 2:5) {
  funcs[[paste0("pow", i)]] <- function(x) {
    x^i
  }
}

# Now let's test them
funcs$pow2(10) # Expected 100
funcs$pow3(10) # Expected 1000

The Lazy Evaluation Trap

If you run that code, you'll notice something unsettling: funcs$pow2(10) doesn't return 100. It returns 100,000. In fact, every single function in that list returns 10^5. Why?

In R, the inner function doesn't "grab" the value of i at the moment the function is created. Instead, it remembers that it needs to look for a variable named i in the surrounding environment. By the time you actually call funcs$pow2(10), the loop has already finished, and the value of i in the environment is 5. Every function is looking at the same i, which is now 5.

Capturing State with Closures

To fix this, we need a Function Factory. A factory is simply a function that returns another function. The key is that the inner function "closes over" the environment of the outer function. This creates a "closure," effectively freezing the value of the argument at the moment the factory was called.

# This is our factory
make_power_func <- function(exponent) {
  # This inner function is what gets returned
  function(x) {
    x^exponent
  }
}

# Now we create our specific functions
pow2 <- make_power_func(2)
pow3 <- make_power_func(3)

pow2(10) # Returns 100
pow3(10) # Returns 1000

Here's what happened: when we called make_power_func(2), R created a unique environment for that specific call where exponent was 2. The function returned by the factory carries that environment with it. When you call pow2(10), it looks back at its original birthplace and finds exponent = 2, regardless of what's happening elsewhere in your script.

When to Actually Use Factories

I'll be honest: if you only need two versions of a function, just write two functions. But factories are incredibly powerful when you're building APIs or packages. They allow you to create "configurable" behavior. Instead of passing a configuration parameter into a function every single time you call it in a loop, you configure the function once using the factory, and then pass that specialized function into other higher-order functions like lapply() or purrr::map().




📋 Practical Task

Building a Custom Currency Converter Factory

Imagine you are building a financial reporting tool. You need several functions that convert various currencies into USD, but the exchange rates change daily. Instead of hard-coding rates into every function, you will build a factory.

Your Task:

  • Create a function factory called make_converter that takes one argument: rate (the value of 1 unit of foreign currency in USD).
  • The factory should return a function that takes a amount argument and returns the converted USD value (amount * rate).
  • Use your factory to create two specific functions: euro_to_usd (using a rate of 1.08) and gbp_to_usd (using a rate of 1.27).
  • Test both functions to ensure they return the correct values for an input of 100.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.