35+ Years Experience Netherlands Based ⚡ Fast Response Times Ruby on Rails Experts AI-Powered Development Fixed Pricing Available Senior Architects Dutch & English 35+ Years Experience Netherlands Based ⚡ Fast Response Times Ruby on Rails Experts AI-Powered Development Fixed Pricing Available Senior Architects Dutch & English
Ruby Proc vs Lambda: De Echte Verschillen en Wanneer Je Welke Gebruikt

Ruby Proc vs Lambda: De Echte Verschillen en Wanneer Je Welke Gebruikt

TTB Software
ruby
Een praktische vergelijking van Ruby Procs en Lambdas: argumentafhandeling, return-gedrag en concrete toepassingen in Rails-applicaties. Inclusief Method-objecten en performance-benchmarks.

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 &:upcaseSymbol#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.

T

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 Touch

Share this article

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