here be icon

LowEffortBlog

Under Construction Warning

Content Incomplete or Under Rewriting


1923 words of #history #ruby, updated on 2024-08-25

History of Ruby's Lambda and Proc

Table of Content

Got really confused by Ruby having both lambda and proc one day. They are the same thing but with different semantics, right? If so, what's reasoning behind this miserable design? If not so, what's the real miserable difference between them?

Author of this post is neither using Ruby at $work, an OK C programmer, a experienced history digger nor even a fluent English writer. So please take everything with a grain of salt. Appreciate that!

The Primitive Era (1994)

At the beginning of time labeled git history, v0.49, Ruby already had pretty solid iterator implementation. There were also for in and do end syntaxes of their early forms for utilizing iterators1.

Under the hood, both for in and do end are implemented using a special data structure called BLOCK2, which contains code, can accept arguments and captures its surrounding environment, making it essentially closure in today's term.

The BLOCK structure was a hardcoded in C back then. However, hero's journey always starts off small.

Here's a relic sample3 found in v0.49 from 1994. The do end block looks so strange yet so familiar. Fascinating!

# tt is a function that calls yield()
test = do tt() using i
  if i == 3; break end
  println("ttt: ", i);
end

However the syntax was quickly changed to the brace style4 few months later in v0.515, making it look a lot more like today's block. Although its internal name was continued to be called DO.

# same tt as above
test = tt{i|
  if i == 3; break end
  println("ttt: ", i);
}

Matz even expressed himself about this in the changelog6:

* parse.y:「do expr using var ... end」形式はなくなった. 寂しい気もする. *BACKWARD INCOMPATIBILITY*

"do expr using var ... end" syntax is no longer a thing. (I) feel a bit lost. *BACKWARD INCOMPATIBILITY*

Few releases later in version 0.53 or 0.54, DO was renamed to ITER internally. The implementation seems to be unchanged.

At this moment, in the cold winter of 1994, neither did proc nor lambda exist yet.

The Era of Lost History (1995)

Born of Block

Proc wasn't born from nothing.

December 19 1994, v0.62 was released. An object Block was introduced7, with a singleton method new and an instance method do8.

Unlike the BLOCK C structure, this is an object that exists in the Ruby object hierarchy. Calling Block.new in the place of an iterator, e.g. in a function, will create an object which holds both the reference to the iterator's environment and the codes passed to it. The do method later can be called to execute the code. It's basically the internal BLOCK structure exposed to the Ruby environment.

Here's an sample from v0.629:

def foo()
  $block = Block.new
end

foo(){i | print "i = ", i, "\n"}
$block.do(2)

As the sun of 1994 sets, this change is like a shooting star, shines through the freezing night sky, followed by the dawn of 1995, and also the dawn of modern Ruby.

v0.7x, First Half of 1995

Few notable things happened in this period of time.

  • Dict was renamed Hash10.

  • Block { |param| [body] } now uses two vertical bars to denote the parameters11. Ruby is finally Ruby.

So few changes were made across v0.7x releases. It was the calm before the storm.

From Block to Proc, The Lost History of 1995

Archaeology always has tragedy.

All the release of v0.8x up until v0.95 are lost, which is essentially the second half of 1995. What left is few words from the changelog that records the reflection of Ruby's transformation towards modern.

Few interesting bits found reading the changelog:

  • protect resque ensure end was renamed to begin resque ensure end in v0.6612. Yes, resque instead of rescue. But it was corrected to rescue in v0.83. Matz was ashamed about this typo in the changelog lol13.

  • ! and ? are allowed after method names. Core methods were adopted to use them14.

The twin proc and lambda, the main characters this article is tracking, were born under the shadow of history. It almost seems, this tragedy is purposefully set up for them.

On December 14, 1995, during the development of v0.94, lambda was introduced as a Kernel method, with changes to the scoping rule of block.

The next day, December 15, proc was introduced as a friendly alias to lambda.

Three days later, December 18, the object Block was renamed Proc, and v0.94 was released.

All these happened in few lines of changelog15. And coincidentally, the release date was December 18, 1995. It was only few hours to the exact same day, December 19, on which v0.62 was released, the release where Block was added.

History sometimes scares me.

End of 1995

lambda and proc are here. Although they hadn't started to diverge yet, as shown by this snippet from eval.c of v0.95 of CRuby source code16:

rb_define_private_method(cKernel, "lambda", f_lambda, 0);
rb_define_private_method(cKernel, "proc", f_lambda, 0);

To my surprise, lambda actually is the elder brother of proc, and both of them dated back to 1995. However, proc the name became such a signature of Ruby instead of its brother.

Stepping into the next era, things will get complicated very quickly. But first take a quick break.

Break Time: Life of do end Syntax

do end is a very iconic syntax of Ruby. But it was neither born this way nor always stayed the same through the history of Ruby.

It seems that Ruby had do using end syntax very early on, and may even be the first of few keywords of Ruby. But it was changed to brace style { param| [body] } in v0.51, Oct 1994, meaning there were no do end syntax for a period of time.

With the release of v0.76, May 1995, two vertical bars were required to quote the parameters { |param| [body] }. This remained as one form of today's block.

Advancing time, during the development of v0.99, in Aug 1996, the removed do end syntax was finally welcomed back. Here's the related changelog17:

  • parse.y (expr): イテレータの新形式に「method do .. end」形式を採用した.もちろん昔の形式も有効.
"method do .. end" was adopted as the new syntax for iterator. Of course the old syntax is still valid.

where the "old syntax" refers to { |param| [body] }.

Well, that'd be The History of Ruby's do end and {||} Block. Nice title for an article isn't it?

The Ruby 1.0 Era

Passing Ruby v1.0 through v1.7, these two methods remained the same.

Ruby didn't have a VM back then, and it's really cute to see it doing GC based on AST :)

Things started to change in v1.8.0-preview3, where a new type of block BLOCK_LAMBDA were introduced and it differs from normal blocks for enforcing the right number of arguments just like methods, and it remained as a trait of today's lambda.

During the lifespan of Ruby v1.8, the two global function proc and lambda create the stricter proc described above, where Proc.new instead creates a looser one with no argument checking. The ambiguity is hardening its stem.

[!NOTE] Not sure about the behavior of control flows (return, break etc.) inside proc at that moment because I can't understand the prize-winning usage of setjmp/longjmp coupled with unhelpful variable names.

The YARV Era

The complexity of

Then the big v1.9.0 came where the YARV virtual machine replaced the old interpreter, alongside with loads of language changes, proc and lambda started to differ from each other for the first time.

Firstly, their definition became different which the VM was aware of. Secondly, the VM gained the ability to allow control flow in lambda, although that "lambda" is not the lambda being discussed on. The ambiguity is having its leaves grown bigger and more.

Enter the Ruby 2.0 era. Feature #8693 was added to Ruby 2.1, which nailed the lambda and proc as they are today.

The ambiguity is fully grown, casting its shadow over all Ruby land.

One last problem is that, there were one huge commit that YARV got merged from sources out-of-tree, so consequently history commits are lost. Worse, the yarv-dev mailing list is long gone and non indexable, ultimately I can't answer the question of the title :( Asking Matz himself might be the last resort.

Today (2024 Sep.) and Future

The relationship between the twin reads like stories of ancient Roman Empire, and also serves as a warning to every programming language designer about the danger of ambiguity.

So why does Ruby have both lambda and proc doing almost the same thing?

Dunno. To my guess, maybe "because we want and we can" and "no one wants to clean up" and "everyone will be mad about breaking changes".

Another thing worth mentioning is that, a lot of interesting histories are buried in the changelog.

information in the changelog, reading through it is like watching a documentary of animal evolution history (reword)

Editing Notes and Thoughts on This Article

The article was

Diving through the history commits of Ruby, seeing it evolves gives me a strange feeling.

Footnotes

  1. v0.49 Spec: https://github.com/ruby/ruby/blob/v0_49/spec#L507 ↩︎

  2. https://github.com/ruby/ruby/blob/v0_49/eval.c#L36-L41 ↩︎

  3. https://github.com/ruby/ruby/blob/v0_49/sample/t2.rb ↩︎

  4. Ruby already had hash (called Dict though) at this moment, which had {a=>b,...} syntax. From the parser's perspective, the vertical bar in block {param| ...body} seems to be the only thing that distinguish them. This syntax choice will hunt the Ruby parser(s) for decades... ↩︎

  5. v0.51 Spec https://github.com/ruby/ruby/blob/v0_51/spec#L550 ↩︎

  6. Matz's words in changelog: https://github.com/ruby/ruby/blob/d450f9d6a28f01b7ca6030a925921dbf35cee439/doc/ChangeLog/ChangeLog-0.50_to_0.60#L336 ↩︎

  7. Related changelog: https://github.com/ruby/ruby/blob/76e0ea28ea5f39913ed51c3628afa1a1fa679554/doc/ChangeLog/ChangeLog-0.60_to_1.1#L3933 ↩︎

  8. Spec: https://github.com/ruby/ruby/blob/v0_62/spec#L1415 ↩︎

  9. https://github.com/ruby/ruby/blob/v0_62/sample/blk.rb ↩︎

  10. Dict->Hash changelog: https://github.com/ruby/ruby/blob/76e0ea28ea5f39913ed51c3628afa1a1fa679554/doc/ChangeLog/ChangeLog-0.60_to_1.1#L3557 ↩︎

  11. Changelog: https://github.com/ruby/ruby/blob/76e0ea28ea5f39913ed51c3628afa1a1fa679554/doc/ChangeLog/ChangeLog-0.60_to_1.1#L3478 ↩︎

  12. https://github.com/ruby/ruby/blob/76e0ea28ea5f39913ed51c3628afa1a1fa679554/doc/ChangeLog/ChangeLog-0.60_to_1.1#L3724 ↩︎

  13. resque typo changelog: https://github.com/ruby/ruby/blob/76e0ea28ea5f39913ed51c3628afa1a1fa679554/doc/ChangeLog/ChangeLog-0.60_to_1.1#L3185 ↩︎

  14. https://github.com/ruby/ruby/blob/76e0ea28ea5f39913ed51c3628afa1a1fa679554/doc/ChangeLog/ChangeLog-0.60_to_1.1#L3100-L3117 ↩︎

  15. v0.94 Changelog: https://github.com/ruby/ruby/blob/76e0ea28ea5f39913ed51c3628afa1a1fa679554/doc/ChangeLog/ChangeLog-0.60_to_1.1#L2754-L2774 ↩︎

  16. eval.c: https://github.com/ruby/ruby/blob/v0_95/eval.c#L3017C1-L3027C2 ↩︎

  17. https://github.com/ruby/ruby/blob/76e0ea28ea5f39913ed51c3628afa1a1fa679554/doc/ChangeLog/ChangeLog-0.60_to_1.1#L1856 ↩︎