Ruby Proc vs Lambda: De Echte Verschillen en Wanneer Je Welke Gebruikt
Procs en lambdas zijn allebei closures in Ruby, maar ze gedragen zich op twee punten fundamenteel anders: hoe ze omgaan met argumenten en hoe return werkt. Kies de verkeerde en je krijgt stille bugs die lastig te traceren zijn.
De korte versie: gebruik standaard lambdas. Gebruik procs alleen als je bewust hun soepelere argumentafhandeling of non-local return-gedrag nodig hebt.
De Twee Verschillen Die Ertoe Doen
1. Argumentafhandeling
Lambdas controleren het aantal argumenten strikt. Procs niet.
strict = lambda { |a, b| "#{a} en #{b}" }
loose = Proc.new { |a, b| "#{a} en #{b}" }
strict.call(1) # ArgumentError: wrong number of arguments (given 1, expected 2)
loose.call(1) # "1 en " — geen fout, b is nil
strict.call(1, 2, 3) # ArgumentError: wrong number of arguments (given 3, expected 2)
loose.call(1, 2, 3) # "1 en 2" — extra argument wordt stilletjes genegeerd
Dit weerspiegelt een ontwerpkeuze: lambdas gedragen zich als methoden, procs als blocks. Blocks zijn altijd soepel geweest met argumentaantallen — denk aan [1,2,3].each { |x| puts x } waar het block niet klaagt als er ook een index zou zijn.
2. Return-gedrag
Hier schuilen de bugs. Een return in een lambda verlaat de lambda. Een return in een proc verlaat de omsluitende methode.
def lambda_test
l = lambda { return 10 }
result = l.call
"Lambda gaf #{result} terug, en we zijn er nog"
end
def proc_test
p = Proc.new { return 10 }
p.call
"Deze regel wordt nooit uitgevoerd"
end
lambda_test # => "Lambda gaf 10 terug, en we zijn er nog"
proc_test # => 10 (de methode zelf retourneert 10)
De return van de proc slaat door en verlaat proc_test volledig. Als de proc langer leeft dan de omsluitende methode (opgeslagen in een variabele en later aangeroepen), krijg je een LocalJumpError:
def create_proc
Proc.new { return "boom" }
end
p = create_proc
p.call # LocalJumpError: unexpected return
Aanmaken: De Syntaxopties
Ruby biedt meerdere manieren om callable objecten te maken:
# Lambda-syntax (voorkeur in modern Ruby)
short = ->(x) { x * 2 }
verbose = lambda { |x| x * 2 }
# Proc-syntax
p1 = Proc.new { |x| x * 2 }
p2 = proc { |x| x * 2 } # Dit maakte in Ruby 1.8 een lambda,
# maar in Ruby 1.9+ een Proc. Ja, echt.
# Controleer wat je hebt
short.lambda? # => true
p1.lambda? # => false
De stabby-lambda-syntax (->) werd geïntroduceerd in Ruby 1.9 en is nu de standaard manier om lambdas te schrijven.
Method-objecten: Het Derde Callable Type
Ruby heeft een derde callable type dat vaak over het hoofd wordt gezien: Method-objecten. Je maakt ze met method():
def double(x)
x * 2
end
m = method(:double)
m.call(5) # => 10
m.arity # => 1
m.class # => Method
# Werkt met map
[1, 2, 3].map(&m) # => [2, 4, 6]
Method-objecten controleren argumentaantallen zoals lambdas en hebben hetzelfde return-gedrag. Ze zijn handig als je een bestaande methode als callable wilt doorgeven.
Performance: Maakt Het Uit?
Benchmark-resultaten op Ruby 3.3.0 (M2 MacBook Pro):
lambda: 18.2M i/s
proc: 18.1M i/s
method: 14.7M i/s
Lambdas en procs zijn in snelheid praktisch identiek. Method-objecten zijn circa 1.24x langzamer door de extra indirectie. In de praktijk is dit verschil irrelevant tenzij je miljoenen keren aanroept in een strakke loop. Zie onze YJIT-productiegids voor meer over YJIT-optimalisatie.
Praktijkpatronen in Rails
Callbacks en Conditionele Logica
Lambdas werken goed als inline-condities in Rails:
class Order < ApplicationRecord
scope :recent, ->(days = 7) { where("created_at > ?", days.days.ago) }
validates :discount_code, presence: true,
if: ->(order) { order.total > 100 }
end
Strategy Pattern met Callables
class PricingEngine
STRATEGIES = {
standard: ->(base) { base },
premium: ->(base) { base * 1.5 },
discount: ->(base) { base * 0.8 }
}.freeze
def calculate(base_price, strategy_name)
strategy = STRATEGIES.fetch(strategy_name) do
raise ArgumentError, "Onbekende strategie: #{strategy_name}"
end
strategy.call(base_price)
end
end
Voor complexere logica met state of meerdere methoden, gebruik echte objecten — zie onze service-objecten-gids.
De &-operator en to_proc
De &-operator in methodeargumenten converteert tussen blocks en procs:
def capture(&block)
block.call
block.class # => Proc
block.lambda? # => false — blocks worden procs, geen lambdas
end
# De andere richting: & converteert een proc/lambda naar een block
multiply = ->(x) { x * 3 }
[1, 2, 3].map(&multiply) # => [3, 6, 9]
Elk object dat to_proc implementeert kan met & worden doorgegeven. Daarom werkt &:upcase — Symbol#to_proc retourneert een proc die die methode aanroept.
Closures en Variabelebinding
Procs en lambdas zijn closures — ze vangen de binding (lokale variabelen) op van waar ze gedefinieerd zijn:
def make_counter
count = 0
incrementer = -> { count += 1 }
getter = -> { count }
[incrementer, getter]
end
inc, get = make_counter
inc.call # => 1
inc.call # => 2
get.call # => 2 — beide delen dezelfde count-variabele
Let op bij het aanmaken van closures in loops: gebruik each of map (die nieuwe block-scopes creëren) in plaats van while-loops.
Currying: Partiële Toepassing
Procs en lambdas ondersteunen currying sinds Ruby 2.0:
add = ->(a, b) { a + b }
add_five = add.curry.(5)
add_five.call(3) # => 8
Currying komt vaker voor in functioneel-achtige Ruby-code. In Rails-applicaties zie je het af en toe in form builders en custom validators.
FAQ
Wat is het verschil tussen Proc.new en proc?
In Ruby 1.9+ maken proc {} en Proc.new {} allebei een Proc (geen lambda). In Ruby 1.8 maakte proc {} een lambda, wat voor verwarring zorgde. Het huidige gedrag is stabiel sinds 2009. Gebruik Proc.new voor duidelijkheid of proc als je beknoptheid prefereert.
Kan ik een proc naar een lambda converteren of andersom?
Er is geen ingebouwde conversie. Je kunt er wel eentje in de ander wrappen: converted = lambda { |*args| my_proc.call(*args) }. Maar dit verandert argumentafhandeling en return-gedrag. Als je lambda-semantiek nodig hebt, maak dan direct een lambda.
Moet ik call, .(), of [] gebruiken om een callable aan te roepen?
Alle drie werken: my_lambda.call(x), my_lambda.(x), my_lambda[x]. Gebruik .call() voor leesbaarheid. De .() syntax is gebruikelijk in functionele code. [] bestaat om historische redenen. Kies er één en wees consistent binnen je codebase.
Wanneer gebruik ik een Method-object in plaats van een lambda?
Gebruik method(:name) als je al een benoemde methode hebt en die als callable wilt doorgeven — meestal met map, select, of andere enumerable-methoden. Het voorkomt dat je een bestaande methode in een overbodige block wrapt.
Zijn procs en lambdas thread-safe in Rails-applicaties?
Ja, zolang ze geen gedeelde state muteren. Procs en lambdas zijn objecten zoals alle andere in Ruby — dezelfde thread-safety-regels gelden. Als een lambda een variabele opvangt die meerdere threads benaderen, heb je synchronisatie nodig.
About the Author
Roger Heykoop is een senior Ruby on Rails ontwikkelaar met 19+ jaar Rails ervaring en 35+ jaar ervaring in softwareontwikkeling. Hij is gespecialiseerd in Rails modernisering, performance optimalisatie, en AI-ondersteunde ontwikkeling.
Get in TouchRelated Articles
Need Expert Rails Development?
Let's discuss how we can help you build or modernize your Rails application with 19+ years of expertise
Schedule a Free Consultation