a_proc = Proc.new {|a,b,*c| p c; c.collect {|i| i*b }}
puts a_proc[2,2,4,3]
Code above is pretty intuitive according to https://ruby-doc.org/core-2.2.0/Proc.html, a_proc[2,2,4,3] is just a syntax sugar for a_proc.call(2,2,4,3) to hide “call”
But the following (works well) confused me a lot
a=[2,2,4,3]
puts a_proc.call(a)
puts a_proc.call(*a)
It seems very different from a normal function call, cause it doesn't check the number arguments passed in.
However, as expected the method calling semantics will raise an error if using parameters likewise
def foo(a,b,*c)
c.collect{|i| i*b}
end
foo([1,2,3,4]) #`block in <main>': wrong number of arguments (given 1, expected 2+) (ArgumentError)
foo(*[1,2,3,4]) #works as expected
I do not think such an inconsistency as a design glitch, so any insights on this will be appreciated.
Blocks use different semantics than methods for binding arguments to parameters.
Block semantics are more similar to assignment semantics than to method semantics in this regard. In fact, in older versions of Ruby, blocks literally used assignment for parameter binding, you could write something like this:
class Foo; def bar=(val) puts 'setter called!' end end
some_proc = Proc.new {|$foo, @foo, foo.bar|}
some_proc.call(1, 2, 3)
# setter called!
$foo #=> 1
@foo #=> 2
Thankfully, this is no longer the case since Ruby 1.9. However, some semantics have been retained:
to_ary
message (if it isn't an Array
already) and the parameters will be bound to the elements of the Array
nil
Note: #1 is what makes Hash#each
work so beautifully, otherwise, you'd always have to deconstruct the array that it passes to the block.
In short, block parameters are bound much the same way as with multiple assignment. You can imagine assignment without setters, indexers, globals, instance variables, and class variables, only local variables, and that is pretty much how parameter binding for blocks work: copy&paste the parameter list from the block, copy&paste the argument list from the yield
, put an =
sign in between and you get the idea.
Now, you aren't actually talking about a block, though, you are talking about a Proc
. For that, you need to know something important: there are two kinds of Proc
s, which unfortunately are implemented using the same class. (IMO, they should have been two different classes.) One kind is called a lambda and the other kind is usually called a proc (confusingly, since both are Proc
s).
Procs behave like blocks, both when it comes to parameter binding and argument passing (i.e. the afore-described assignment semantics) and also when it comes to the behavior of return
(it returns from the closest lexically enclosing method).
Lambdas behave like methods, both when it comes to parameter binding and argument passing (i.e. strict argument checking) and also when it comes to the behavior of return
(it returns from the lambda itself).
A simple mnemonic: "block" and "proc" rhyme, "method" and "lambda" are both Greek.
A small remark to your question:
a_proc[2,2,4,3] is just a syntax sugar for a_proc.call(2,2,4,3) to hide “call”
This is not syntactic sugar. Rather, Proc
simply defines the []
method to behave identically to call
.
What is syntactic sugar is this:
a_proc.(2, 2, 4, 3)
Every occurrence of
foo.(bar, baz)
gets interpreted as
foo.call(bar, baz)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With