Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do Ruby procs/blocks with splat arguments behave differently than methods and lambdas?

Why do Ruby (2.0) procs/blocks with splat arguments behave differently than methods and lambdas?

def foo (ids, *args)
  p ids
end
foo([1,2,3]) # => [1, 2, 3]

bar = lambda do |ids, *args|
  p ids
end
bar.call([1,2,3]) # => [1, 2, 3]

baz = proc do |ids, *args|
  p ids
end
baz.call([1,2,3]) # => 1

def qux (ids, *args)
  yield ids, *args
end
qux([1,2,3]) { |ids, *args| p ids } # => 1

Here's a confirmation of this behavior, but without explanation: http://makandracards.com/makandra/20641-careful-when-calling-a-ruby-block-with-an-array

like image 667
Jordan Avatar asked May 30 '14 01:05

Jordan


People also ask

What is the main difference between procs and lambdas?

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.

What is the difference between block proc and lambda?

A Proc object is an encapsulation of a block of code, which can be stored in a local variable, passed to a method or another Proc, and can be called. Lambdas are anonymous functions, objects of the class Proc, they are useful in most of the situations where you would use a proc.

What are procs and lambdas 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 splat operator in Ruby?

A parameter with the splat operator converts the arguments to an array within a method. The arguments are passed in the same order in which they are specified when a method is called. A method can't have two parameters with splat operator.


2 Answers

There are two types of Proc objects: lambda which handles argument list in the same way as a normal method, and proc which use "tricks" (Proc#lambda?). proc will splat an array if it's the only argument, ignore extra arguments, assign nil to missing ones. You can partially mimic proc behavior with lambda using destructuring:

->((x, y)) { [x, y] }[1]         #=> [1, nil]
->((x, y)) { [x, y] }[[1, 2]]    #=> [1, 2]
->((x, y)) { [x, y] }[[1, 2, 3]] #=> [1, 2]
->((x, y)) { [x, y] }[1, 2]      #=> ArgumentError
like image 51
Victor Moroz Avatar answered Oct 19 '22 05:10

Victor Moroz


Just encountered a similar issue!

Anyways, my main takeaways:

  1. The splat operator works for array assignment in a predictable manner

  2. Procs effectively assign arguments to input (see disclaimer below)

This leads to strange behavior, i.e. the example above:

baz = proc do |ids, *args|
  p ids
end
baz.call([1,2,3]) # => 1

So what's happening? [1,2,3] gets passed to baz, which then assigns the array to its arguments

ids, *args = [1,2,3]
ids = 1
args = [2,3]

When run, the block only inspects ids, which is 1. In fact, if you insert p args into the block, you will find that it is indeed [2,3]. Certainly not the result one would expect from a method (or lambda).

Disclaimer: I can't say for sure if Procs simply assign their arguments to input under the hood. But it does seem to match their behavior of not enforcing the correct number of arguments. In fact, if you give a Proc too many arguments, it ignores the extras. Too few, and it passes in nils. Exactly like variable assignment.

like image 31
phil-ociraptor Avatar answered Oct 19 '22 06:10

phil-ociraptor