Skip to content(if available)orjump to list(if available)

What Is the Difference Between a Block, a Proc, and a Lambda in Ruby? (2013)

HellzStormer

For those unaware, Ruby blocks (and procs) are more flexible than anonymous functions as most language implement them. The article briefly goes over that, mentioning that lambda (regular anonymous functions) and procs (blocks) don't treat execute `return` the same way. There are also particularities with regard to next (continue) and break.

I made a post about the niceties of blocks: https://maxlap.dev/blog/2022/02/10/what-makes-ruby-blocks-gr...

chowells

Flexible in a way, sure. But non-locality is generally a bad property, not good. Adding it is a workaround for all the enumeration methods using blocks in a way that makes people think they're weird looping syntax instead of a fundamentally different idea.

People want to do early returns from looping over a collection, so take the easy solution of adding more messy language semantics instead of finding a semantically simple solution instead. (For that matter, have fun working out the semantics of break and next when used in a block that isn't an argument to an enumeration method. How do you as a method author opt in to distinguishing between the two after yielding to a block?)

This is generally the case with Ruby everywhere. Why does that thing have an edge case with weird semantics? To work around the edge case with weird semantics somewhere else. It's all fine if you want to just try things until something works. But if you want to really understand what's going on and write code that's a first-class participant in the language features, it gets really frustrating to try to deal with it all.

vidarh

It's really not that complex.

For my (buggy, unfinished, languishing without updates) prototype Ruby compiler, lambda and proc (and blocks) are all implemented nearly the same way, with the exception that for proc/blocks, return/break/next will act as if having unwound the stack to the scope where the proc was defined first (or throw an error if escaped from there).

The distinction is very obvious when you think of it in that way - a proc acts as if called in the context/scope it was defined, while a lambda acts as if called in the scope it is called in.

> How do you as a method author opt in to distinguishing between the two after yielding to a block?

You don't. A block acts as a proc, not a lambda. If you want lambda semantics, then take a lambda as an argument - don't take a block/proc and be surprised it doesn't act like something it isn't.

chowells

Given that you completely ignored what I said I wanted to do and gave an answer for some other question, I'm pretty sure it's more complicated than you think.

I want to write a method that takes a block and distinguishes between next and break, exactly like the methods of enumeration do. It's obviously possible because a super common interface does it.

Last time I looked, that interface does it by being written in native code that interfaces with the interpreter. That is, it's not part of the language semantics. It's a special case with weird rules unlike what anything else gets to do.

Or at least it was. Maybe the language has actually made it accessible since then, but I'm not optimistic. That's not the ruby way.

drnewman

It seems you're pretty upset about your experience with Ruby. I'm sorry that's been the case for you.

However, in Ruby blocks aren't just about flexibility, more importantly they're about generality. They're not there to resolve an edge case at all (Ruby also has keywords for loops). They're a generalization of control flow that is just slightly less general than continuations. In practical use they provide an alterative to Lisp macros for many use cases.

These were some of the problems Matz was trying to sort out with his design of Ruby--creating a language that was fun and easy to use for day-to-day programming, with as much of the meta-programming power of Lisp and Smalltalk as possible. Blocks are one of his true innovatations that came from trying to balance that tension.

igouy

> Blocks are one of his true innovatations …

How do they differ from Smallalk blocks? (I don't know.)

https://drcuis.github.io/TheCuisBook/Block-syntax.html

jbverschoor

Which is exactly what I don’t like about them. They should’ve used a different keyword or something.

This runs you into problems similar to missing a break in such statements with many language

vidarh

Separating out blocks this way is pretty meaningless. Blocks are a syntactical construct that can be optimized a certain way, and is in MRI.

You can obtain a value of a block by naming it, and when you do, what you obtain is a proc.

A Ruby implementation could if it chooses make any block a proc, because you shouldn't be able to tell the difference without extensive contortions (e.g. you could iterate over ObjectSpace and show that a block causes the creation - or not - of an object of the Proc class). And in-fact my (woefully buggy and incomplete) Ruby compiler prototype does just that.

jez

I think this is the important point. Something I've mentioned about blocks in Ruby before: if you’re prototyping a new interpreted language and don’t want to have to build a JIT that can optimize code, but still want some semblance of good performance, the block approach Ruby picks can be pretty useful.

Ruby doesn’t haven to allocate a full-on, garbage-collected, closure object every time a function accepts a block: it only has to do this if the block gets stored to a variable. If the block is only ever yield’d to, the allocation can be skipped.

And when your language’s primary looping mechanism is done with blocks, the difference adds up:

    xs.each do |ys|
      # with normal closures and no fancy JIT,
      # the VM has to allocate a closure once per loop:
      ys.each do |y|
      end
    end
Ruby was able to get away with its closure-heavy standard library APIs without a JIT for almost 3 decades because of the affordances that blocks provide over procs/lambdas.

endorphine

> Lamdas check the number of arguments, while procs do not

FWIW, Matz himself called this difference "a design mistake".

jez

Do you have a source for this?

weaksauce

which way would he have liked to be the "correct way"

kubb

What happens when you return from a proc when the enclosing function has already returned?

lcnPylGDnU4H9OF

You can't call return from inside a Proc inside a method; you will encounter a LocalJumpError.

vidarh

You can call return from inside a proc all you want. You can't do so if you return the proc to an outside scope without getting LocalJumpError.

E.g, given:

    def foo = proc do 42 end
    def bar = proc do return 42 end
Then `foo.call` is fine, but `bar.call` will indeed give a LocalJumpError as you say.

But a return in a proc that hasn't escaped its defining scope is fine:

    def baz = proc do return 42 end.call
Calling `baz` here will just return 42 to the surrounding scope.

vidarh

This example:

    def foo
      proc do
        return 42
      end.call
    end

    def bar
      proc do
        return 42
      end
    end
    
    p foo
    p bar.call
Will produce:

    42
    test.rb:11:in `block in bar': unexpected return (LocalJumpError)
from test.rb:16:in `<main>'

So in other words, it's handled.

whalesalad

This is the biggest wart on the Ruby language by far.

paulddraper

tl;dr

A block is a part of the AST. Like a pair of braces.

A proc is a function.

A lambda is a proc that treats args and returns differently.