Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why does ruby need so many different types of closure?

Tags:

ruby

As far as I can tell, there are essentially three different kinds of closure in Ruby; methods, procs and lambdas. I know that there are differences between them, but could we not just get away having one type that accommodates all possible use-cases?

Methods can already be passed around like procs and lambdas by calling self.method(method_name), and the only significant differences that I'm aware of between procs and lambdas is that lambdas check arity and procs do crazy things when you try to use return. So couldn't we just merge them all into one and be done with it?

like image 632
Doydle Avatar asked Aug 09 '13 12:08

Doydle


People also ask

What about closures in Ruby What are they?

In Ruby, closure is a function or a block of code with variables that are bound to the environment that the closure is called. Or in other words, closure can be treated like a variable that can be assigned to another variable or can be pass to any function as an argument.

Are Ruby blocks closures?

Ruby doesn't have first-class functions, but it does have closures in the form of blocks, procs and lambdas. Blocks are used for passing blocks of code to methods, and procs and lambda's allow storing blocks of code in variables.

What is proc and lambda in Ruby?

Blocks are syntactic structures in Ruby; they are not objects, and cannot be manipulated as objects. It is possible, however, to create an object that represents a block. Depending on how the object is created, it is called a proc or a lambda.

What is a lambda in Ruby?

In Ruby, a lambda is an object similar to a proc. Unlike a proc, a lambda requires a specific number of arguments passed to it, and it return s to its calling method rather than returning immediately.


1 Answers

As far as I can tell, there are essentially three different kinds of closure in Ruby; methods, procs and lambdas.

No, there are two: methods aren't closures, only procs and lambdas are. (Or at least can be, most of them aren't.)

There are two ways of packaging up a piece of executable code for reuse in Ruby: methods and blocks. Strictly speaking, blocks aren't necessary, you can get by with just methods. But blocks are meant to be extremely light-weight, conceptually, semantically and syntactically. That's not true for methods.

Because they are meant to be light-weight and easy to use, blocks behave different from methods in some respects, e.g. how arguments are bound to parameters. Block parameters are bound more like the left-hand side of an assignment than like method parameters.

Examples:

Passing a single array to multiple parameters:

def foo(a, b) end
foo([1, 2, 3]) # ArgumentError: wrong number of arguments (1 for 2)

a, b = [1, 2, 3]
# a == 1; b == 2

[[1, 2, 3]].each {|a, b| puts "a == #{a}; b == #{b}" }
# a == 1; b ==2

Passing less arguments than parameters:

def foo(a, b, c) end
foo(1, 2) # ArgumentError

a, b, c = 1, 2
# a == 1; b == 2; c == nil

[[1, 2]].each {|a, b, c| puts "a == #{a}; b == #{b}; c == #{c}" }
# a == 1; b == 2; c == 

Passing more arguments than parameters:

def foo(a, b) end
foo(1, 2, 3) # ArgumentError: wrong number of arguments (3 for 2)

a, b = 1, 2, 3
# a == 1; b == 2

[[1, 2, 3]].each {|a, b| puts "a == #{a}; b == #{b}" }
# a == 1; b == 2

[By the way: none of the blocks above are closures.]

This allows, for example, the Enumerable protocol which always yields a single element to the block to work with Hashes: you just make the single element an Array of [key, value] and rely on the implicit array destructuring of the block:

{one: 1, two: 2}.each {|k, v| puts "#{key} is assigned to #{value}" }

is much easier to understand than what you would have to otherwise write:

{one: 1, two: 2}.each {|el| puts "#{el.first} is assigned to #{el.last}" }

Another difference between blocks and methods is that methods use the return keyword to return a value whereas blocks use the next keyword.

If you agree that it makes sense to have both methods and blocks in the language, then it is just a small step to also accept the existence of both procs and lambdas, because they behave like blocks and methods, respectively:

  • procs return from the enclosing method (just like blocks) and they bind arguments exactly like blocks do
  • lambdas return from themselves (just like methods) and they bind arguments exactly like methods do.

IOW: the proc/lambda dichotomy just mirrors the block/method dichotomy.

Note that there are actually quite a lot more cases to consider. For example, what does self mean? Does it mean

  • whatever self was at the point the block was written
  • whatever self is at the point the block is run
  • the block itself

And what about return? Does it mean

  • return from the method the block is written in
  • return from the method the block is run in
  • return from the block itself?

This already gives you nine possibilities, even without taking into account the Ruby-specific peculiarities of parameter binding.

Now, for reasons of encapsulation, #2 above are really bad ideas, so that reduces our choices somewhat.

As always, it's a matter of taste of the language designer. There are other such redundancies in Ruby as well: why do you need both instance variables and local variables? If lexical scopes were objects, then local variables would just be instance variables of the lexical scope and you wouldn't need local variables. And why do you need both instance variables and methods? One of them is enough: a getter/setter pair of methods can replace an instance variable (see Newspeak for an example of such a language) and first-class procedures assigned to instance variables can replace methods (see Self, Python, JavaScript). Why do you need both classes and modules? If you allow classes to be mixed-in, then you can get rid of modules and use classes both as classes and mixins. And why do you need mixins at all? If everything is a method call, classes automatically become mixins anyway (again, see Newspeak for an example). And of course, if you allow inheritance directly between objects you don't need classes at all (see Self, Io, Ioke, Seph, JavaScript)

like image 79
Jörg W Mittag Avatar answered Dec 06 '22 17:12

Jörg W Mittag