R Functional Programming

Functional programming treats functions as first-class objects — you pass them as arguments, return them from other functions, and store them in lists. R supports functional programming deeply through the purrr package and base R tools. This style produces shorter, more testable, and more composable code than imperative loops.

Core Functional Programming Concepts

Concept              Description
──────────────────────────────────────────────────────────────────
First-class functions Functions stored in variables, passed as args
Higher-order functions Functions that take/return other functions
Pure functions        Same input → same output, no side effects
Immutability         Do not modify inputs; return new values
Function composition  Chain small functions into larger ones

map() — Apply a Function to Each Element

library(purrr)

numbers <- list(1, 4, 9, 16, 25)

map(numbers, sqrt)          # returns list of results
map_dbl(numbers, sqrt)      # returns double vector: 1 2 3 4 5
map_chr(numbers, as.character)  # returns character vector
map_lgl(numbers, \(x) x > 5)   # returns logical vector
map() family:
  map()     → always returns a list
  map_dbl() → returns numeric vector
  map_int() → returns integer vector
  map_chr() → returns character vector
  map_lgl() → returns logical vector
  map_df()  → returns data frame (bind rows)

map2() and pmap() — Multiple Inputs

# map2: iterate over two vectors in parallel
lengths <- c(4, 6, 8)
widths  <- c(3, 5, 2)

map2_dbl(lengths, widths, \(l, w) l * w)
# [1] 12 30 16

# pmap: iterate over any number of lists
params <- list(
  mean = c(0, 5, 10),
  sd   = c(1, 2, 3),
  n    = c(100, 200, 150)
)
pmap(params, rnorm)    # generates random samples for each parameter set

reduce() — Combine Elements Cumulatively

# Reduce a list to a single value
reduce(c(1,2,3,4,5), `+`)          # 15 (like sum)
reduce(c(2,3,4), `*`)              # 24 (like prod)
reduce(list(df1, df2, df3), rbind) # stack data frames

# With accumulate — see intermediate steps
accumulate(c(1,2,3,4,5), `+`)
# [1]  1  3  6 10 15

Filter Functions

data <- list(4, -2, 9, -7, 0, 15, -3)

keep(data, \(x) x > 0)    # keep positive: 4 9 15
discard(data, \(x) x > 0) # discard positive: -2 -7 0 -3
some(data, \(x) x > 10)   # TRUE (any > 10?)
every(data, \(x) x > 0)   # FALSE (all positive?)
detect(data, \(x) x > 8)  # 9 (first match)

Function Factories (Closures)

# Returns a new function customized by the argument
make_power <- function(n) {
  function(x) x^n
}

square <- make_power(2)
cube   <- make_power(3)

square(4)   # 16
cube(3)     # 27

# Practical: make a percentage formatter
make_formatter <- function(prefix="", suffix="", digits=1) {
  function(x) paste0(prefix, round(x, digits), suffix)
}

pct_format <- make_formatter(suffix="%")
inr_format <- make_formatter(prefix="₹", digits=0)

pct_format(87.534)   # "87.5%"
inr_format(45678)    # "₹45678"

Function Composition

library(purrr)

# compose() combines functions right-to-left
clean_text <- compose(trimws, tolower)
clean_text("  HELLO WORLD  ")   # "hello world"

# Chain with pipes instead
"  HELLO WORLD  " |> tolower() |> trimws()

walk() — Side Effects Without Return

# Like map() but used when you care about side effects, not the return
files <- c("report1.csv", "report2.csv", "report3.csv")

walk(files, function(f) {
  cat("Processing:", f, "\n")
  # ... process each file
})

Practical: Process Multiple Datasets

datasets <- list(
  sales   = data.frame(region="N", rev=c(100,200,150)),
  returns = data.frame(region="S", rev=c(50,80,60)),
  refunds = data.frame(region="E", rev=c(30,40,35))
)

# Compute summary stats for each dataset
summaries <- map(datasets, function(df) {
  list(
    total  = sum(df$rev),
    mean   = mean(df$rev),
    rows   = nrow(df)
  )
})

map_dbl(summaries, "total")   # extract "total" from each
# sales returns refunds
#   450    190     105

Functional programming makes complex data pipelines from simple, reusable building blocks. The purrr package brings consistent, predictable map-reduce patterns to R. As your analyses grow in complexity, the ability to compose small pure functions into powerful pipelines becomes one of R's biggest advantages over spreadsheet-based workflows.

Leave a Comment

Your email address will not be published. Required fields are marked *