Vol. 2 · № 02 · Un atelier RubyA Ruby workshop

Proc vs Lambda, ou la fonction et le fragment Proc vs Lambda, the function and the fragment

Capturer un bloc dans un objet donne un proc ou une lambda. Même classe Proc, même .call — et deux comportements opposés : la lambda exige la bonne arité et son return est local ; le proc s'arrange de tout et son return sort de la méthode englobante. Une fonction, ou un fragment ? Choisis. Capturing a block in an object gives a proc or a lambda. Same Proc class, same .call — and two opposite behaviors: the lambda demands the right arity and its return is local; the proc shrugs everything off and its return exits the enclosing method. A function, or a fragment? Choose.

AudienceAudience
Dev à l'aise avec les blocs, prêt à les capturer en objets Dev comfortable with blocks, ready to capture them as objects
Format
Self-paced
ChapitresChapters
5
Date
Jan 2026 Jan 2026
≈ 18 min ●●○○ BlocProcLambda
SommaireContents ·
№ 01CadrageFraming3 min

Un bloc qu'on garde sous la main devient un objet qu'on rappelle.A block you keep around becomes an object you call back.

Au numéro précédent, le bloc partait avec l'appel et la méthode le rappelait via yield. Mais on peut aussi le capturer dans un objet : une lambda (->) ou un proc. Cet objet se range dans une variable, se passe en argument, se rappelle plus tard avec .call. Deux façons de l'écrire, qui semblent interchangeables — et qui, on va le voir, ne le sont pas du tout.Last issue, the block left with the call and the method called it back via yield. But you can also capture it in an object: a lambda (->) or a proc. That object goes into a variable, gets passed as an argument, gets called later with .call. Two ways to write it, seemingly interchangeable — and, as we'll see, not at all.

Un objet-fonction, plusieurs façons de l'appelerA function-object, several ways to call it
add = ->(a, b) { a + b }   # une lambda : un bloc capturé dans un objet
add.call(1, 2)             # => 3   — la forme explicite
add.(1, 2)                 # => 3   — le sucre .()
add[1, 2]                  # => 3   — et même []

square = proc { |x| x * x }  # un proc : l'autre objet-fonction
square.call(5)               # => 25

add.class                  # => Proc   — les deux sont des Proc
add.lambda?                # => true   — mais celui-ci se sait lambda
Deux objets, une même classeTwo objects, one class
lambda
->(x) { … }
se sait lambdaknows it's a lambda
proc
proc { |x| … }
un Proc « simple »a plain Proc

Les deux ont pour classe Proc. La différence n'est pas de type — elle est de comportement, et elle ne se voit qu'à l'appel.Both have class Proc. The difference isn't of type — it's of behavior, and it only shows at call time.

Le réflexe du volumeThe volume's reflex

« Ce comportement, je le passe en bloc — mais ici je le garde dans un objet. » La question devient : ai-je besoin d'une vraie petite fonction (stricte, autonome) ou d'un simple fragment de code souple ? Le choix lambda / proc répond exactement à ça — et se paie si on se trompe."This behavior, I pass it as a block — but here I keep it in an object." The question becomes: do I need a genuine little function (strict, self-contained) or just a loose code fragment? The lambda / proc choice answers exactly that — and costs you if you get it wrong.

Tout est objet, même une fonction.Everything is an object, even a function.

On boucle sur le Vol 1 : un morceau de comportement est, lui aussi, un objet. On l'assigne, on le passe, on lui envoie le message .call. Ce n'est pas une construction à part du langage — c'est une instance de Proc, avec une classe et des méthodes comme tout le reste.We loop back to Vol 1: a piece of behavior is, too, an object. You assign it, pass it, send it the .call message. It's not a separate language construct — it's an instance of Proc, with a class and methods like everything else.

№ 02La surpriseThe surprise4 min

Même classe, même syntaxe d'appel — et deux comportements opposés.Same class, same call syntax — and two opposite behaviors.

proc et lambda se ressemblent à s'y méprendre : même classe Proc, même .call. La différence tient en deux points, et ils décident lequel choisir. L'arité : la lambda exige le bon nombre d'arguments, le proc s'en arrange. Le return : dans une lambda il sort de la lambda, dans un proc il sort de la méthode qui l'entoure. Ce ne sont pas des détails — ce sont deux outils différents.proc and lambda look deceptively alike: same Proc class, same .call. The difference comes down to two points, and they decide which to pick. Arity: the lambda demands the right number of arguments, the proc shrugs it off. return: in a lambda it exits the lambda, in a proc it exits the enclosing method. These aren't details — they're two different tools.

Arité : stricte (lambda) vs laxiste (proc)Arity: strict (lambda) vs lax (proc)
lam  = ->(a, b) { [a, b] }
prc  = proc { |a, b| [a, b] }

lam.call(1)        # ArgumentError: wrong number of arguments (given 1, expected 2)
lam.call(1, 2, 3)  # ArgumentError aussi — la lambda EXIGE la bonne arité

prc.call(1)        # => [1, nil]   — manquant ? rempli avec nil, sans broncher
prc.call(1, 2, 3)  # => [1, 2]     — en trop ? ignoré, sans broncher
# le proc est laxiste : c'est pratique… jusqu'à ce que ça masque un bug

La lambda se comporte comme une méthode : le mauvais nombre d'arguments lève une ArgumentError, tout de suite. Le proc avale tout — un argument manquant devient nil, un argument en trop disparaît. Souple, mais silencieux.The lambda behaves like a method: the wrong number of arguments raises ArgumentError, right away. The proc swallows everything — a missing argument becomes nil, an extra one vanishes. Flexible, but silent.

La lambda est une fonction : arité stricte, return local. Le proc est un fragment du corps qui l'entoure. Choisis lequel tu veux.The lambda is a function: strict arity, local return. The proc is a fragment of the body around it. Choose which you want.
№ 03La mécaniqueThe mechanics5 min

Le & est un traducteur : entre « bloc » et « objet Proc ».The & is a translator: between "block" and "Proc object".

Un bloc n'est pas un objet ; un Proc, si. Le & fait le pont entre les deux. En paramètre (&block), il capture le bloc passé dans un Proc nommé. À l'appel (map(&doubler)), il reconvertit un Proc en bloc. Et &:symbol n'est qu'un cas particulier : il demande au Symbol de se transformer en Proc. Trois usages, une seule idée — convertir.A block isn't an object; a Proc is. The & bridges the two. As a parameter (&block), it captures the passed block into a named Proc. At the call site (map(&doubler)), it converts a Proc back into a block. And &:symbol is just a special case: it asks the Symbol to turn itself into a Proc. Three uses, one idea — convert.

Quatre façons de fabriquer un ProcFour ways to make a Proc
->(x) { x }lambda
lambda { |x| x }lambda
proc { |x| x }proc
Proc.new { |x| x }proc

Les deux premières sont des lambdas (strictes) ; les deux dernières, des procs (laxistes). Le mot exact que tu choisis décide du comportement vu au № précédent.The first two are lambdas (strict); the last two, procs (lax). The exact word you choose decides the behavior seen in the previous chapter.

Une méthode aussi devient un Proc.A method becomes a Proc too.

method(:puts) renvoie un objet Method, et [1,2].each(&method(:puts)) le transmet comme bloc. Le pont du Vol 1 (les méthodes sont des objets) rejoint ici la conversion &.method(:puts) returns a Method object, and [1,2].each(&method(:puts)) passes it as a block. The Vol 1 bridge (methods are objects) meets the & conversion here.

Capturer et transmettre, avec &Capture and pass, with &
# & dans les DEUX sens : capturer le bloc, ou transmettre un Proc

def measure(&block)        # & capture le bloc passé DANS un Proc nommé block
  start = Time.now
  block.call               # on l'appelle comme un objet
  Time.now - start
end
measure { sleep 0.1 }      # => ~0.1

doubler = ->(x) { x * 2 }
[1, 2, 3].map(&doubler)    # & RECONVERTIT le Proc en bloc pour map → [2, 4, 6]
&:symbol, déplié&:symbol, unwrapped
# &:symbol — l'idiome le plus courant, et ce qu'il cache
[1, 2, 3].map(&:to_s)      # => ["1", "2", "3"]

# &:to_s appelle Symbol#to_proc, qui fabrique :
->(x) { x.to_s }
# donc map(&:to_s) est exactement map { |x| x.to_s }
%w[a b].map(&:upcase)      # => ["A", "B"]

map(&:to_s) n'est pas une syntaxe magique : le & demande à :to_s de devenir un Proc (Symbol#to_proc), qui envoie simplement le message to_s à chaque élément. Concis tant que le bloc tient en un seul message sans argument — au-delà, il faut revenir au bloc explicite.map(&:to_s) isn't magic syntax: the & asks :to_s to become a Proc (Symbol#to_proc), which simply sends the to_s message to each element. Concise as long as the block is a single argument-less message — beyond that, you must return to an explicit block.

№ 04Le piègeThe trap4 min

Prendre un proc pour une fonction, c'est hériter d'un return qui ne t'appartient pas.Taking a proc for a function means inheriting a return that isn't yours.

Quand on veut un objet-fonction — qu'on stocke, qu'on passe, qu'on rappelle plus tard — le réflexe est d'écrire proc { … }. C'est le mauvais outil. Le return d'un proc veut sortir de la méthode qui l'a créé ; appelé après coup, il lève LocalJumpError. Et son arité laxiste avale les erreurs d'appel au lieu de les signaler. Une fonction autonome veut une lambda : return local, arité stricte.When you want a function-object — stored, passed, called back later — the reflex is to write proc { … }. Wrong tool. A proc's return wants to exit the method that created it; called afterward, it raises LocalJumpError. And its lax arity swallows call errors instead of flagging them. A self-contained function wants a lambda: local return, strict arity.

Le proc stocké qui explose à l'appelThe stored proc that blows up on call
# Le réflexe : "un objet-fonction, je prends proc"
def make_validator
  proc { |x| return false if x.nil?; x > 0 }
end

check = make_validator      # make_validator a déjà rendu la main…
check.call(5)
# LocalJumpError: unexpected return
# … le return du proc cherche à sortir d'une méthode qui n'existe plus
La lambda : return local, arité stricteThe lambda: local return, strict arity
# Le correctif : une fonction autonome, c'est une LAMBDA
def make_validator
  ->(x) { return false if x.nil?; x > 0 }
end

check = make_validator
check.call(nil)   # => false   — le return sort de la lambda, proprement
check.call(5)     # => true
check.call        # ArgumentError — et l'arité stricte attrape l'appel fautif
Le contre-exemple obligatoire.The mandatory counter-example.

Choisir proc « par habitude » pour un objet qu'on rappellera ailleurs, c'est s'exposer au return qui fuit et à l'arité qui ment. La question n'est pas « proc ou lambda, lequel je connais », mais ai-je besoin d'une fonction (lambda) ou d'un fragment du corps environnant (proc) ?. Le proc se réserve aux cas où l'on veut justement qu'un return agisse sur la méthode — rare, et alors assumé.Choosing proc "out of habit" for an object you'll call elsewhere exposes you to the leaking return and the lying arity. The question isn't "proc or lambda, which do I know," but do I need a function (lambda) or a fragment of the surrounding body (proc)?. The proc is reserved for cases where you actually want a return to act on the method — rare, and then deliberate.

Une fonction qu'on garde sous la main est une lambda. Le proc n'est pas une fonction — c'est un morceau du corps qui l'a vu naître.A function you keep around is a lambda. A proc isn't a function — it's a piece of the body that birthed it.
№ 05Bilan & éditoWrap-up & editorial2 min

Une fonction, ou un fragment ? Choisis — ne devine pas.A function, or a fragment? Choose — don't guess.

proc et lambda ont la même classe et la même façon de s'appeler, mais ce sont deux outils. La lambda est une fonction : arité stricte, return local. Le proc est un morceau du corps qui l'entoure : arité souple, return qui sort de la méthode. Le réflexe n'est pas de retenir lequel — c'est de savoir lequel tu veux.proc and lambda share a class and a call syntax, but they're two tools. The lambda is a function: strict arity, local return. The proc is a piece of the surrounding body: loose arity, return that exits the method. The reflex isn't to memorize which — it's to know which you want.

lambda ->(x) { }lambda ->(x) { }proc proc { |x| }proc proc { |x| }
AritéArityStricte : mauvais compte → ArgumentError.Strict: wrong count → ArgumentError.Laxiste : manquant → nil, en trop → ignoré.Lax: missing → nil, extra → ignored.
returnreturnSort de la lambda — comme une fonction.Exits the lambda — like a function.Sort de la méthode englobante (ou LocalJumpError).Exits the enclosing method (or LocalJumpError).
QuandWhenUn objet-fonction qu'on stocke, passe, rappelle ailleurs.A function-object you store, pass, call elsewhere.Un fragment de contrôle lié à la méthode courante.A control fragment tied to the current method.
Le défautDefaultÀ privilégier dès qu'on parle de « fonction ».Prefer it whenever you say 'function'.Rare et assumé — quand le return doit agir sur la méthode.Rare and deliberate — when return must act on the method.
Le & traduit entre bloc et objet ; &:symbol n'est qu'un Symbol qui se fait Proc. Aucune magie — que des conversions.The & translates between block and object; &:symbol is just a Symbol turning into a Proc. No magic — only conversions.
Mot de l'éditeurFrom the editor

Deux objets jumeaux, une question.Two twin objects, one question.

Il serait facile de présenter proc et lambda comme un piège gratuit de Ruby — deux choses presque identiques qui se comportent différemment « pour rien ». C'est l'inverse. Leur ressemblance dit qu'ils partagent une nature (un bloc capturé dans un objet) ; leur différence dit qu'ils répondent à deux besoins distincts (une vraie fonction, ou un fragment du corps environnant). Le jour où tu écris -> sans réfléchir parce que tu veux une fonction, et proc seulement quand tu veux que le return morde sur la méthode, le « piège » disparaît : il n'y avait qu'un choix à nommer.It would be easy to frame proc and lambda as a gratuitous Ruby gotcha — two near-identical things behaving differently 'for no reason'. It's the opposite. Their likeness says they share a nature (a block captured in an object); their difference says they answer two distinct needs (a real function, or a fragment of the surrounding body). The day you write -> without thinking because you want a function, and proc only when you want return to bite on the method, the 'gotcha' vanishes: there was only a choice to name.

Prochain numéro : les closures. proc et lambda ne capturent pas que du code — ils capturent l'environnement où ils sont nés. On verra comment un bloc se souvient des variables autour de lui, d'où vient sa puissance, et le piège de la variable mutée qu'on croyait figée.Next issue: closures. proc and lambda capture not just code — they capture the environment they were born in. We'll see how a block remembers the variables around it, where its power comes from, and the trap of the mutated variable you thought was frozen.