Skip to content

Lambda

What is a Lambda?

A Lambda is an anonymous function stored as a value. Unlike a def function which is declared and called by name, a lambda can be assigned to a variable, stored in a vector or hash, and passed to other functions. Lambdas bring first-class function behaviour to Frankie.

double = ->(x) { x * 2 }
puts double.call(5)   # 10

Creating Lambdas

Single-expression body — braces { }

double  = ->(x) { x * 2 }
add     = ->(a, b) { a + b }
greet   = ->(name) { "Hello, #{name}!" }
square  = ->(x) { x * x }
is_even = ->(x) { x % 2 == 0 }

Multi-statement body — do...end

clamp = ->(n, lo, hi) do
  return lo if n < lo
  return hi if n > hi
  n
end

describe = ->(v) do
  avg = mean(v)
  sd  = stdev(v)
  "mean=#{round(avg,2)}, stdev=#{round(sd,2)}"
end

No parameters

timestamp = ->() { now().format("%H:%M:%S") }
puts timestamp.call   # 14:23:07

Default parameters

greet = ->(name, prefix = "Hello") { "#{prefix}, #{name}!" }
puts greet.call("Alice")         # Hello, Alice!
puts greet.call("Bob", "Hi")     # Hi, Bob!

Calling a Lambda

Use .call(args):

add = ->(a, b) { a + b }

puts add.call(3, 4)    # 7
puts add.call(10, 20)  # 30

Lambdas as Values

Because lambdas are values, they can be stored and passed anywhere a value can go.

Stored in a variable

transform = ->(x) { x * 3 + 1 }
puts transform.call(5)   # 16

Stored in a vector

pipeline = [
  ->(x) { x + 1 },
  ->(x) { x * 2 },
  ->(x) { x ** 2 }
]

pipeline.each do |fn|
  puts fn.call(5)
end
# 6
# 10
# 25

Stored in a hash

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

puts ops["add"].call(10, 3)       # 13
puts ops["subtract"].call(10, 3)  # 7
puts ops["multiply"].call(10, 3)  # 30

Passed to a function

def apply(fn, value)
  fn.call(value)
end

square = ->(x) { x * x }
puts apply(square, 7)   # 49

def apply_all(fns, value)
  fns.reduce(value) do |acc, fn|
    fn.call(acc)
  end
end

steps = [->(x) { x + 1 }, ->(x) { x * 3 }, ->(x) { x - 2 }]
puts apply_all(steps, 4)   # (4+1)*3-2 = 13

Lambdas vs def Functions

def Lambda
Has a name Yes No (stored in a variable)
Storable as value No Yes
Passable to functions No Yes
Default parameters Yes Yes
Multi-statement body Yes Yes (do...end)
Implicit return Yes Yes
Explicit return Yes Yes
Recursion Yes Via the variable name

Use def for named, reusable functions. Use lambdas when you need to pass behaviour as data.


Higher-order Patterns

Lambdas enable higher-order programming — functions that operate on other functions.

Map with a lambda

double = ->(x) { x * 2 }

result = [1, 2, 3, 4, 5].map do |x|
  double.call(x)
end
puts result   # [2, 4, 6, 8, 10]

Function composition

def compose(f, g)
  ->(x) { f.call(g.call(x)) }
end

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

add1_then_double = compose(double, add1)
puts add1_then_double.call(5)   # (5+1)*2 = 12

double_then_add1 = compose(add1, double)
puts double_then_add1.call(5)   # (5*2)+1 = 11

Memoisation

def memoize(fn)
  cache = {}
  ->(x) do
    if cache[x] == nil
      cache[x] = fn.call(x)
    end
    cache[x]
  end
end

slow_square = ->(x) do
  sleep(0.001)   # simulate slow computation
  x * x
end

fast_square = memoize(slow_square)
puts fast_square.call(10)   # computed
puts fast_square.call(10)   # cached — instant

Strategy pattern

def sort_by_strategy(items, strategy)
  items.sort_by do |item|
    strategy.call(item)
  end
end

words = ["banana", "fig", "apple", "cherry", "kiwi"]

by_length  = ->(w) { w.length }
by_alpha   = ->(w) { w }
by_reverse = ->(w) { w.chars.reverse.join("") }

puts sort_by_strategy(words, by_length)   # [fig, kiwi, apple, banana, cherry]
puts sort_by_strategy(words, by_alpha)    # [apple, banana, cherry, fig, kiwi]

Closures

Lambdas close over variables from the scope where they are created — they can read and remember values that were in scope at definition time.

multiplier = 10
scale = ->(x) { x * multiplier }

puts scale.call(5)   # 50

# The lambda captures the variable, not just its value
multiplier = 3
puts scale.call(5)   # 15 — uses the current value of multiplier

Recursion with Lambdas

A lambda can call itself by referencing its own variable:

factorial = ->(n) do
  return 1 if n <= 1
  n * factorial.call(n - 1)
end

puts factorial.call(10)   # 3628800

Quick Reference

# Define
fn = ->(x) { x * 2 }
fn = ->(a, b) { a + b }
fn = ->(x, n = 1) { x + n }    # default param
fn = ->(x) do                   # multi-line
  y = x * 2
  y + 1
end

# Call
fn.call(5)
fn.call(3, 4)

# Store
pipeline = [fn1, fn2, fn3]
ops = {double: fn1, triple: fn2}

# Pass
def apply(fn, val)
  fn.call(val)
end

# Use in iterators
[1,2,3].map do |x| fn.call(x) end