CrackedRuby CrackedRuby

Overview

First-class functions represent a programming language feature where functions are treated as first-class citizens, meaning they have the same rights and capabilities as other values in the language. Functions can be assigned to variables, passed as arguments to other functions, returned as values from functions, and stored in data structures like arrays and hashes.

This concept originates from lambda calculus and functional programming languages like Lisp and Scheme, but has been adopted by many modern languages including Ruby, JavaScript, Python, and others. First-class functions enable functional programming paradigms within multi-paradigm languages.

The significance of first-class functions extends beyond theoretical computer science. They enable higher-order functions, closures, callbacks, decorators, and many design patterns that reduce code duplication and increase abstraction. Without first-class functions, developers would need to rely on more verbose patterns like the Strategy pattern or Command pattern to achieve similar flexibility.

# Functions as values
greeting = lambda { |name| "Hello, #{name}!" }
puts greeting.call("World")
# => Hello, World!

# Functions passed as arguments
def apply_twice(value, &operation)
  operation.call(operation.call(value))
end

result = apply_twice(3) { |x| x * 2 }
# => 12

Ruby implements first-class functions through three distinct mechanisms: blocks, procs, and lambdas. While blocks are syntactic constructs that cannot be stored in variables directly, procs and lambdas are true objects that can be manipulated like any other value. This distinction creates both flexibility and complexity in Ruby's approach to functional programming.

Key Principles

The foundation of first-class functions rests on treating functions as data. In languages supporting this feature, a function is not fundamentally different from an integer, string, or object. The function can be referenced, stored, and manipulated without being invoked immediately.

Assignment to Variables: Functions can be bound to variable names, allowing the same function to be referenced by different identifiers or stored for later use. This separates the definition of behavior from its invocation.

multiply = Proc.new { |a, b| a * b }
calculate = multiply  # Same function, different name
calculate.call(4, 5)
# => 20

Function Parameters: Functions can accept other functions as arguments, creating higher-order functions. This pattern enables callbacks, event handlers, and customizable behavior without inheritance hierarchies.

def retry_operation(max_attempts, &operation)
  attempts = 0
  begin
    attempts += 1
    operation.call
  rescue StandardError => e
    retry if attempts < max_attempts
    raise
  end
end

retry_operation(3) { fetch_remote_data }

Return Values: Functions can return other functions, enabling function factories, partial application, and closures that capture surrounding state. The returned function maintains access to variables in its defining scope.

def multiplier(factor)
  lambda { |n| n * factor }
end

times_three = multiplier(3)
times_three.call(10)
# => 30

Storage in Data Structures: Functions can be elements in arrays, values in hashes, or attributes of objects. This enables dispatch tables, command registries, and strategy collections.

operations = {
  add: ->(a, b) { a + b },
  subtract: ->(a, b) { a - b },
  multiply: ->(a, b) { a * b }
}

operations[:add].call(10, 5)
# => 15

Lexical Closure: When a function references variables from its surrounding scope, it captures those variables, creating a closure. The closure maintains access to these variables even after the outer function has returned.

def counter(initial = 0)
  count = initial
  lambda { count += 1 }
end

counter_a = counter(0)
counter_b = counter(100)

counter_a.call  # => 1
counter_a.call  # => 2
counter_b.call  # => 101

Each closure maintains its own binding to captured variables, creating independent state. This mechanism enables encapsulation without classes and object-oriented patterns using only functions.

Arity and Argument Handling: First-class functions have defined parameter lists (arity). Languages differ in how they handle mismatched arguments. Some enforce strict arity checking, while others allow flexible argument counts. This affects how functions can be composed and called dynamically.

The distinction between parameters (formal arguments in the definition) and arguments (actual values passed) becomes significant when functions are passed as values. A function's signature must be compatible with how it will be invoked, or the language must provide mechanisms for adapting arity.

Ruby Implementation

Ruby provides three mechanisms for first-class functions: blocks, procs, and lambdas. Each has distinct syntax, semantics, and use cases, reflecting Ruby's evolution and different programming needs.

Blocks: The most common Ruby construct, blocks are not true objects but syntactic elements. They cannot be assigned to variables without conversion but are passed implicitly to methods. Blocks use either do...end or { } syntax.

# Block passed to method
[1, 2, 3].each do |num|
  puts num * 2
end

# Block with braces (same semantics)
[1, 2, 3].map { |num| num * 2 }
# => [2, 4, 6]

# Blocks cannot be assigned directly
# block = { puts "test" }  # Syntax error

Methods receive blocks via the yield keyword or by declaring a block parameter with &block. The ampersand converts the block to a proc object.

def execute_with_timing(&block)
  start_time = Time.now
  result = block.call
  elapsed = Time.now - start_time
  puts "Execution took #{elapsed} seconds"
  result
end

execute_with_timing { expensive_computation }

Procs: Created via Proc.new or the proc method, procs are objects that encapsulate blocks. They return from the enclosing method, not just from the proc itself, which can create unexpected control flow.

def test_proc_return
  my_proc = Proc.new { return "from proc" }
  my_proc.call
  "after proc"  # Never reached
end

test_proc_return
# => "from proc"

Procs have lenient arity checking. Extra arguments are ignored, missing arguments become nil.

flexible_proc = Proc.new { |a, b, c| [a, b, c] }
flexible_proc.call(1)
# => [1, nil, nil]
flexible_proc.call(1, 2, 3, 4, 5)
# => [1, 2, 3]

Lambdas: Created via lambda or the stabby lambda syntax ->, lambdas are procs with stricter semantics. They return from themselves only, matching function behavior in other languages, and enforce arity checking.

def test_lambda_return
  my_lambda = lambda { return "from lambda" }
  result = my_lambda.call
  "after lambda: #{result}"
end

test_lambda_return
# => "after lambda: from lambda"

strict_lambda = ->(a, b) { a + b }
strict_lambda.call(1)
# => ArgumentError: wrong number of arguments (given 1, expected 2)

The stabby lambda syntax supports default arguments and explicit parameter types (though Ruby doesn't enforce types).

calculator = ->(x, y = 10) { x + y }
calculator.call(5)
# => 15

# Multi-line stabby lambda
processor = ->(data) do
  validated = validate(data)
  transformed = transform(validated)
  transformed.result
end

Method Objects: Ruby methods can be converted to objects using method or public_method, creating another form of first-class function.

class Calculator
  def add(a, b)
    a + b
  end
end

calc = Calculator.new
add_method = calc.method(:add)
add_method.call(5, 3)
# => 8

# Can be passed to higher-order functions
[1, 2, 3].map(&add_method.curry[10])
# => [11, 12, 13]

Symbol to Proc Conversion: Ruby's Symbol#to_proc enables concise higher-order function calls by converting symbols to procs that call the named method.

["apple", "banana", "cherry"].map(&:upcase)
# => ["APPLE", "BANANA", "CHERRY"]

# Equivalent to:
["apple", "banana", "cherry"].map { |s| s.upcase }

This works because &:symbol converts the symbol to a proc via to_proc, then the & operator converts the proc to a block argument.

Currying and Partial Application: Ruby procs and lambdas support currying, converting a function taking multiple arguments into a sequence of functions each taking a single argument.

multiply = ->(a, b, c) { a * b * c }
curried = multiply.curry

times_2 = curried.call(2)
times_2_and_3 = times_2.call(3)
times_2_and_3.call(4)
# => 24

# Partial application in one step
times_5 = multiply.curry.call(5)
times_5.call(2, 3)
# => 30

Common Patterns

Callback Pattern: Pass functions as arguments to be invoked at specific points in execution. This decouples the caller from the specific behavior that occurs at callback points.

class FileProcessor
  def process(filename, &on_line)
    File.foreach(filename) do |line|
      on_line.call(line.chomp)
    end
  end
end

processor = FileProcessor.new
processor.process("data.txt") do |line|
  puts "Processing: #{line}" if line.include?("error")
end

Callbacks enable inversion of control, where the framework calls application code rather than the application calling framework code. This pattern appears in event handlers, middleware chains, and plugin architectures.

Higher-Order Functions: Functions that accept other functions as parameters or return functions as results. These create abstractions over common patterns.

def compose(f, g)
  lambda { |x| f.call(g.call(x)) }
end

add_one = ->(x) { x + 1 }
double = ->(x) { x * 2 }

add_then_double = compose(double, add_one)
add_then_double.call(5)
# => 12

Common higher-order functions include map, filter, reduce, and sort_by. These abstract iteration patterns, making code more declarative.

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

result = numbers
  .select { |n| n.even? }
  .map { |n| n * n }
  .reduce(0) { |sum, n| sum + n }
# => 220

Function Factory: Return functions from functions, often creating closures that capture configuration or state.

def create_validator(min, max)
  lambda do |value|
    return false if value < min
    return false if value > max
    true
  end
end

age_validator = create_validator(0, 120)
age_validator.call(25)   # => true
age_validator.call(150)  # => false

percentage_validator = create_validator(0, 100)
percentage_validator.call(50)   # => true

This pattern creates specialized functions from general templates, reducing duplication when multiple similar functions are needed with different parameters.

Strategy Pattern: Use functions instead of strategy objects to select algorithms at runtime. This simplifies the classic object-oriented pattern.

class DataExporter
  def initialize(format_strategy)
    @format_strategy = format_strategy
  end

  def export(data)
    @format_strategy.call(data)
  end
end

json_format = ->(data) { data.to_json }
csv_format = ->(data) { 
  data.map { |row| row.join(',') }.join("\n") 
}

exporter = DataExporter.new(json_format)
exporter.export([{name: "Alice", age: 30}])

Decorator Pattern: Wrap functions with additional behavior while maintaining the same interface.

def logged(function)
  lambda do |*args|
    puts "Calling with arguments: #{args.inspect}"
    result = function.call(*args)
    puts "Result: #{result}"
    result
  end
end

def timed(function)
  lambda do |*args|
    start = Time.now
    result = function.call(*args)
    elapsed = Time.now - start
    puts "Execution time: #{elapsed}s"
    result
  end
end

expensive_operation = ->(n) { sleep(n); n * 2 }
decorated = logged(timed(expensive_operation))
decorated.call(1)

Decorators compose cleanly because they maintain the function signature, allowing multiple decorators to wrap the same function.

Lazy Evaluation: Return functions that compute values only when called, deferring expensive operations.

def lazy_load(expensive_operation)
  cached_result = nil
  lambda do
    cached_result ||= expensive_operation.call
  end
end

fetch_user = lazy_load(lambda { fetch_from_database })
# Database not yet queried

user = fetch_user.call  # Now queries database
user_again = fetch_user.call  # Uses cached result

Command Objects as Functions: Replace command objects with simple functions when no state beyond the command operation is needed.

# Traditional command pattern with classes
class Command
  def execute; raise NotImplementedError; end
end

class SaveCommand < Command
  def initialize(data); @data = data; end
  def execute; save_to_database(@data); end
end

# Functional approach
save_command = ->(data) { save_to_database(data) }
delete_command = ->(id) { delete_from_database(id) }

commands = [
  save_command.curry["user_data"],
  delete_command.curry[old_id]
]

commands.each(&:call)

Practical Examples

Event System: Build an event dispatcher using first-class functions to decouple event producers from consumers.

class EventEmitter
  def initialize
    @listeners = Hash.new { |h, k| h[k] = [] }
  end

  def on(event_name, &handler)
    @listeners[event_name] << handler
  end

  def emit(event_name, *args)
    @listeners[event_name].each do |handler|
      handler.call(*args)
    end
  end

  def off(event_name, handler)
    @listeners[event_name].delete(handler)
  end
end

emitter = EventEmitter.new

# Register multiple handlers
emitter.on(:user_login) do |user|
  puts "Logging access for #{user.name}"
end

emitter.on(:user_login) do |user|
  send_welcome_email(user)
end

emitter.on(:user_login) do |user|
  update_last_seen(user)
end

# Trigger event
emitter.emit(:user_login, current_user)

This pattern decouples event sources from handlers. New behaviors can be added by registering additional handlers without modifying the event emitter or existing handlers.

Pipeline Processing: Chain operations on data using function composition.

class Pipeline
  def initialize(value)
    @value = value
  end

  def pipe(&transformation)
    @value = transformation.call(@value)
    self
  end

  def value
    @value
  end
end

# Define transformation functions
normalize_whitespace = ->(text) { text.gsub(/\s+/, ' ').strip }
remove_punctuation = ->(text) { text.gsub(/[[:punct:]]/, '') }
downcase_text = ->(text) { text.downcase }
split_words = ->(text) { text.split(' ') }
count_words = ->(words) { words.size }

# Build and execute pipeline
text = "  Hello,  World!  How are   you?  "
word_count = Pipeline.new(text)
  .pipe(&normalize_whitespace)
  .pipe(&remove_punctuation)
  .pipe(&downcase_text)
  .pipe(&split_words)
  .pipe(&count_words)
  .value
# => 5

Pipelines make data transformations explicit and composable. Each stage is independent and testable, and stages can be reordered or removed without affecting others.

Retry Logic with Configurable Strategies: Create reusable retry mechanisms where the retry strategy is a function.

def with_retry(max_attempts: 3, delay_strategy: nil, &operation)
  delay_strategy ||= ->(attempt) { 2 ** attempt }
  
  attempts = 0
  begin
    attempts += 1
    operation.call
  rescue StandardError => e
    if attempts < max_attempts
      delay = delay_strategy.call(attempts)
      sleep(delay)
      retry
    else
      raise
    end
  end
end

# Exponential backoff
with_retry(max_attempts: 5) do
  fetch_from_unreliable_api
end

# Linear backoff
with_retry(
  max_attempts: 3,
  delay_strategy: ->(attempt) { attempt * 0.5 }
) do
  write_to_database
end

# Constant delay
with_retry(
  max_attempts: 4,
  delay_strategy: ->(_) { 1 }
) do
  send_notification
end

Memoization: Cache function results for expensive computations.

def memoize(function)
  cache = {}
  lambda do |*args|
    cache[args] ||= function.call(*args)
  end
end

fibonacci = lambda do |n|
  return n if n <= 1
  fibonacci.call(n - 1) + fibonacci.call(n - 2)
end

memoized_fibonacci = memoize(fibonacci)

# First call computes
memoized_fibonacci.call(35)  # Takes time

# Second call uses cache
memoized_fibonacci.call(35)  # Instant

The memoized function maintains a cache across invocations, trading memory for speed. This pattern works for pure functions where outputs depend only on inputs.

Builder Pattern with Fluent Interface: Use function chaining to construct complex objects.

class QueryBuilder
  def initialize
    @conditions = []
    @limit_value = nil
    @order_column = nil
  end

  def where(&condition)
    @conditions << condition
    self
  end

  def limit(n)
    @limit_value = n
    self
  end

  def order_by(column)
    @order_column = column
    self
  end

  def execute(collection)
    result = collection.select do |item|
      @conditions.all? { |condition| condition.call(item) }
    end

    result = result.sort_by { |item| item[@order_column] } if @order_column
    result = result.take(@limit_value) if @limit_value
    result
  end
end

users = [
  {name: "Alice", age: 30, role: "admin"},
  {name: "Bob", age: 25, role: "user"},
  {name: "Charlie", age: 35, role: "admin"}
]

admins_over_25 = QueryBuilder.new
  .where { |user| user[:role] == "admin" }
  .where { |user| user[:age] > 25 }
  .order_by(:age)
  .limit(10)
  .execute(users)
# => [{name: "Alice", age: 30, role: "admin"}, {name: "Charlie", age: 35, role: "admin"}]

Design Considerations

When to Use First-Class Functions: Choose first-class functions when behavior varies independently of data structure. If different algorithms need to operate on the same data, passing functions avoids creating class hierarchies for each algorithm variant.

First-class functions excel for callbacks and event handlers where the timing of execution is determined by the framework but the specific behavior is application-defined. They reduce coupling between components by eliminating direct dependencies.

Use functions for simple behaviors that don't require state beyond their parameters. When behavior needs extensive configuration, initialization, or maintains state across invocations, objects may provide better encapsulation.

Functions vs Objects: Objects bundle data and behavior, maintaining state across method calls. Functions, especially pure functions, take inputs and produce outputs without side effects. Objects work well for entities with identity and lifecycle. Functions work well for transformations and operations.

# Object-oriented approach
class UserValidator
  def initialize(min_age, banned_domains)
    @min_age = min_age
    @banned_domains = banned_domains
  end

  def valid?(user)
    return false if user.age < @min_age
    return false if @banned_domains.include?(user.email_domain)
    true
  end
end

validator = UserValidator.new(18, ["spam.com"])
validator.valid?(user)

# Functional approach
def create_user_validator(min_age, banned_domains)
  lambda do |user|
    return false if user.age < min_age
    return false if banned_domains.include?(user.email_domain)
    true
  end
end

validator = create_user_validator(18, ["spam.com"])
validator.call(user)

Both approaches capture configuration state. Objects make state mutable and provide multiple related methods. Functions create immutable closures focused on single operations.

Performance Trade-offs: Creating closures has memory overhead. Each closure maintains references to captured variables, preventing garbage collection of that context. Thousands of closures can impact memory usage.

Function calls through proc/lambda have slight overhead compared to direct method calls. For tight loops with millions of iterations, this overhead may matter. Profile before optimizing.

# Memory consideration
def create_processors(count)
  large_data = Array.new(1_000_000) { rand }
  
  count.times.map do |i|
    # Each lambda captures reference to large_data
    lambda { |x| large_data[x] * i }
  end
end

processors = create_processors(1000)
# large_data cannot be garbage collected while processors exist

Blocks vs Procs vs Lambdas: Use blocks for single-use iterations and callbacks that don't need to be stored. Use procs when lenient argument handling is needed or when the return behavior of jumping to the enclosing method is desired.

Use lambdas for functions that need to be passed around, stored, and called multiple times with strict argument checking. Lambdas behave most like functions in other languages.

# Block: Single use, not stored
collection.each { |item| process(item) }

# Proc: Stored, flexible arguments
processor = Proc.new { |a, b, c| [a, b, c].compact }
different_arg_counts.each { |args| processor.call(*args) }

# Lambda: Stored, strict arguments
validator = ->(email) { email.match?(/@/) }
emails.select(&validator)

Testing Implications: First-class functions simplify testing by isolating behavior. Test functions independently, then test higher-order functions with stub functions.

# Test the higher-order function
def test_with_retry
  call_count = 0
  operation = lambda do
    call_count += 1
    raise "Error" if call_count < 3
    "success"
  end

  result = with_retry(max_attempts: 3, &operation)
  assert_equal "success", result
  assert_equal 3, call_count
end

Functions passed as parameters become test seams. Replace real implementations with test doubles without affecting the code under test.

Common Pitfalls

Proc vs Lambda Return Behavior: The most common mistake is misunderstanding how return behaves differently in procs versus lambdas.

def test_returns
  proc_test = Proc.new { return "proc return" }
  lambda_test = lambda { return "lambda return" }
  
  proc_result = proc_test.call  # Returns from test_returns
  # This line never executes
  lambda_result = lambda_test.call
  
  "end of method"
end

test_returns
# => "proc return"

def test_lambda_only
  lambda_test = lambda { return "lambda return" }
  lambda_result = lambda_test.call
  "end of method: #{lambda_result}"
end

test_lambda_only
# => "end of method: lambda return"

Procs return from the method where they were defined, not where they're called. This can cause unexpected control flow when procs are passed between methods. Lambdas return only from themselves, making their behavior more predictable.

Closure Variable Capture: Closures capture variables by reference, not by value. This leads to unexpected behavior when variables change after closure creation.

# Common mistake
functions = []
(1..3).each do |i|
  functions << lambda { puts i }
end

functions.each(&:call)
# Prints: 3, 3, 3 (not 1, 2, 3)

# Correct approach: explicitly capture value
functions = []
(1..3).each do |i|
  functions << lambda { |n| lambda { puts n } }.call(i)
end

functions.each(&:call)
# Prints: 1, 2, 3

The closure captures the variable i, not its value at closure creation time. When closures execute later, they see the final value of i after the loop completes.

Arity Mismatches: Forgetting that lambdas enforce arity while procs don't can cause runtime errors or silent bugs.

strict_lambda = ->(a, b) { a + b }
flexible_proc = Proc.new { |a, b| a + b }

strict_lambda.call(1)    # ArgumentError
flexible_proc.call(1)    # NoMethodError: undefined method + for nil

The proc accepts the call but creates nil values for missing parameters, which may cause errors later in execution. Lambdas fail immediately with clear error messages.

Memory Leaks Through Closures: Closures maintain references to all variables in their scope, even if they don't use them all. This can prevent garbage collection.

def create_handler
  large_object = Array.new(1_000_000) { Object.new }
  small_value = 42
  
  # This closure captures both variables
  lambda { puts small_value }
  # large_object cannot be garbage collected
end

handler = create_handler
# large_object stays in memory while handler exists

Extract only needed values before creating closures:

def create_handler
  large_object = Array.new(1_000_000) { Object.new }
  small_value = large_object.size  # Extract needed value
  large_object = nil  # Allow garbage collection
  
  lambda { puts small_value }
end

Block vs Proc Confusion: Converting between blocks and procs requires the & operator. Forgetting this leads to syntax errors.

def takes_block(&block)
  block.call
end

my_proc = proc { puts "Hello" }

takes_block(my_proc)        # Wrong: passes proc as regular argument
takes_block(&my_proc)       # Correct: converts proc to block

The & operator converts procs to blocks when calling methods and converts blocks to procs when receiving them as parameters.

Ignored Return Values: Procs and lambdas return the value of their last expression, but this return value is often ignored in callback scenarios.

result = [1, 2, 3].each { |n| n * 2 }
# result is [1, 2, 3], not [2, 4, 6]

# Correct use of map to transform
result = [1, 2, 3].map { |n| n * 2 }
# result is [2, 4, 6]

Understanding whether a method uses the block's return value (like map and select) versus just calling the block for side effects (like each) prevents confusion.

Nested Block Limitations: Ruby syntax doesn't cleanly support nested blocks with the do...end or {} syntax.

# Doesn't work as expected
collection.each do |item|
  item.values.select do |value|
    value > 10
  end
end

# Use braces for inner block
collection.each do |item|
  item.values.select { |value| value > 10 }
end

# Or assign to variable
collection.each do |item|
  large_values = item.values.select { |value| value > 10 }
  process(large_values)
end

Symbol#to_proc Limitations: While &:method_name is concise, it only works for methods that take no arguments.

strings = ["hello", "world"]

# Works: no arguments
strings.map(&:upcase)
# => ["HELLO", "WORLD"]

# Doesn't work: method needs argument
strings.map(&:include?("o"))  # Syntax error

# Must use explicit block
strings.map { |s| s.include?("o") }
# => [true, true]

Reference

Block, Proc, and Lambda Comparison

Feature Block Proc Lambda
Object Status Not an object Object Object (special proc)
Assignment Cannot assign directly Assignable Assignable
Creation Syntax do...end or {} Proc.new or proc lambda or ->
Return Behavior Returns from enclosing method Returns from enclosing method Returns from lambda only
Arity Checking Flexible Flexible Strict
Parameter Declaration Pipes around params Block parameters Arrow parameters

Common Higher-Order Functions

Method Purpose Returns Example
map Transform elements New array [1, 2].map { abs }
select Filter elements New array [1, 2].select { abs > 1 }
reject Inverse filter New array [1, 2].reject { abs.zero? }
reduce Accumulate value Single value [1, 2].reduce(0) { abs + abs }
each Iterate for side effects Original [1, 2].each { puts abs }
sort_by Sort by computed value New array users.sort_by { abs.age }
group_by Group by computed key Hash users.group_by { abs.role }
find First matching element Element or nil [1, 2].find { abs > 1 }
all? Test all elements Boolean [1, 2].all? { abs > 0 }
any? Test any element Boolean [1, 2].any? { abs > 1 }

Function Conversion Syntax

Operation Syntax Result
Block to Proc &block Proc object
Proc to Block &proc Block argument
Method to Proc object.method(:name) Method object
Symbol to Proc &:symbol Proc calling method
Lambda Creation lambda {} or ->() {} Lambda object
Proc Creation Proc.new {} or proc {} Proc object

Arity Methods

Method Purpose Returns
arity Number of parameters Integer
parameters Parameter details Array of [type, name]
lambda? Check if strict lambda Boolean
curry Convert to curried form Curried proc
call Invoke function Return value
=== Call (for case statements) Return value

Closure Behavior

Aspect Behavior Implication
Variable Capture By reference Changes visible
Scope Lexical Sees defining context
Lifetime With function object Prevents GC
Mutability Captured vars mutable State can change
Sharing Multiple closures share Side effects visible

Performance Characteristics

Operation Relative Cost Notes
Block yield Fastest Direct method call
Lambda call Fast Small overhead
Proc call Fast Small overhead
Method object call Slower Additional indirection
Symbol to proc Moderate Conversion overhead
Curry One-time cost Per partial application

Common Patterns

Pattern Use Case Key Benefit
Callback Async operations Decoupling
Factory Create configured functions Reusability
Decorator Add behavior Composition
Strategy Algorithm selection Flexibility
Command Encapsulate operations Undo/redo support
Pipeline Sequential transformations Clarity
Memoization Cache expensive results Performance
Lazy evaluation Defer computation Efficiency