Chapter 56 Function Factories & Operators
What You’ll Learn:
- Function factories
- Function operators
- Closures
- Practical applications
Difficulty: ⭐⭐⭐ Advanced
56.2 Practical Example
💡 Key Insight: Custom Validators
# Create validators
in_range <- function(min, max) {
function(x) {
x >= min & x <= max
}
}
is_percentage <- in_range(0, 100)
is_probability <- in_range(0, 1)
is_percentage(50) # TRUE
#> [1] TRUE
is_percentage(150) # FALSE
#> [1] FALSE
is_probability(0.5) # TRUE
#> [1] TRUE
is_probability(1.5) # FALSE
#> [1] FALSE56.3 Function Operators
Functions that take functions and return modified functions:
# Add logging to any function
add_logging <- function(f) {
function(...) {
cat("Calling function\n")
result <- f(...)
cat("Function returned:", result, "\n")
result
}
}
# Wrap existing function
logged_sqrt <- add_logging(sqrt)
logged_sqrt(4)
#> Calling function
#> Function returned: 2
#> [1] 2
# Add error handling
safe_function <- function(f, default = NULL) {
function(...) {
tryCatch(
f(...),
error = function(e) {
message("Error occurred: ", e$message)
default
}
)
}
}
safe_log <- safe_function(log, default = NA)
safe_log(10)
#> [1] 2.302585
safe_log(-1) # Returns NA instead of error
#> Warning in f(...): NaNs produced
#> [1] NaN56.4 Memoization
🎯 Best Practice: Cache Results
# Memoize slow functions
memoize <- function(f) {
cache <- new.env(parent = emptyenv())
function(...) {
key <- paste(..., sep = "_")
if (!exists(key, envir = cache)) {
cache[[key]] <- f(...)
}
cache[[key]]
}
}
# Slow function
fibonacci <- function(n) {
if (n <= 1) return(n)
fibonacci(n - 1) + fibonacci(n - 2)
}
# Fast version with memoization
fibonacci_memo <- memoize(fibonacci)
system.time(fibonacci(30))
#> user system elapsed
#> 0.797 0.005 0.833
system.time(fibonacci_memo(30)) # Much faster on repeat
#> user system elapsed
#> 0.777 0.005 0.83256.5 Real-World Applications
# 1. Create family of similar functions
make_adder <- function(n) {
function(x) x + n
}
add_10 <- make_adder(10)
add_100 <- make_adder(100)
add_10(5)
#> [1] 15
add_100(5)
#> [1] 105
# 2. Configure behavior
make_filter <- function(pattern) {
function(x) {
grep(pattern, x, value = TRUE)
}
}
filter_r <- make_filter("^r")
filter_r(c("apple", "rose", "banana", "ruby"))
#> [1] "rose" "ruby"
# 3. Delayed computation
make_lazy <- function(expr) {
computed <- FALSE
value <- NULL
function() {
if (!computed) {
value <<- expr
computed <<- TRUE
}
value
}
}
# Computation only happens when called
get_data <- make_lazy({
cat("Computing...\n")
rnorm(1000)
})
# First call computes
data <- get_data()
#> Computing...
# Subsequent calls use cached value
data2 <- get_data() # No "Computing..." message56.6 Common Patterns
🎯 Best Practice: Common Factory Patterns
# 1. Partial application
partial <- function(f, ...) {
args <- list(...)
function(...) {
do.call(f, c(args, list(...)))
}
}
# Create specialized version
divide_by_10 <- partial(`/`, e2 = 10)
divide_by_10(100)
#> [1] 0.1
# 2. Compose functions
compose <- function(f, g) {
function(...) {
f(g(...))
}
}
# sqrt(abs(x))
sqrt_abs <- compose(sqrt, abs)
sqrt_abs(-16)
#> [1] 4
# 3. Negate predicate
negate <- function(f) {
function(...) {
!f(...)
}
}
is_not_na <- negate(is.na)
is_not_na(c(1, NA, 3))
#> [1] TRUE FALSE TRUE56.7 Summary
Key Takeaways:
- Function factories - Functions that create functions
- Closures - Capture environment
- Function operators - Modify function behavior
- Practical uses - Validation, logging, memoization
- Composition - Build complex from simple
Quick Reference:
# Factory
make_power <- function(n) {
function(x) x^n
}
# Operator
add_logging <- function(f) {
function(...) {
cat("Calling\n")
f(...)
}
}
# Composition
compose <- function(f, g) {
function(...) f(g(...))
}
# Partial application
partial <- function(f, ...) {
args <- list(...)
function(...) do.call(f, c(args, list(...)))
}
# Memoization
memoize <- function(f) {
cache <- new.env()
function(...) {
key <- paste(...)
if (!exists(key, cache)) cache[[key]] <- f(...)
cache[[key]]
}
}Common Applications:
56.8 Exercises
📝 Exercise 1: Temperature Converter Factory
Create a factory that makes temperature converters:
- to_fahrenheit() - Celsius to Fahrenheit
- to_celsius() - Fahrenheit to Celsius
- to_kelvin() - Celsius to Kelvin
📝 Exercise 2: Timing Function Operator
Create a function operator that times how long a function takes: - Prints execution time - Returns the result - Works with any function
56.9 Exercise Answers
Click to see answers
Exercise 1:
# Temperature converter factory
make_temp_converter <- function(formula) {
function(temp) {
formula(temp)
}
}
# Create converters
to_fahrenheit <- make_temp_converter(function(c) c * 9/5 + 32)
to_celsius <- make_temp_converter(function(f) (f - 32) * 5/9)
to_kelvin <- make_temp_converter(function(c) c + 273.15)
# Test
to_fahrenheit(0) # 32
#> [1] 32
to_celsius(32) # 0
#> [1] 0
to_kelvin(0) # 273.15
#> [1] 273.15Exercise 2:
# Timing operator
add_timing <- function(f) {
function(...) {
start <- Sys.time()
result <- f(...)
end <- Sys.time()
cat("Execution time:",
format(difftime(end, start, units = "secs")), "\n")
result
}
}
# Test
slow_sum <- function(n) {
total <- 0
for (i in 1:n) total <- total + i
total
}
timed_sum <- add_timing(slow_sum)
timed_sum(1000000)
#> Execution time: 0.01747298 secs
#> [1] 500000500000