Reputation: 30145
What are the rules in Ruby regarding the number of parameters for the various function-like constructs and how they are called?
e.g. I noticed that when blocks with multiple parameters, get passed a single array parameter, it gets expanded, and this does not seem to apply to methods. I often see this with Enumerable module methods on a Hash object.
{a: 5}.map{|x| x} # [[:a, 5]]
{a: 5}.map{|k, v| [k, v]} # [[:a, 5]]
[[:a, 5]].map{|x| x} # [[:a, 5]]
[[:a, 5]].map{|k, v| [k, v]} # [[:a, 5]]
proc1 = Proc.new{|x| x}
proc1.call 5 # 5
proc1.call 5, 6 # 5
proc1.call [5, 6] # [5, 6]
proc2 = Proc.new{|k, v| [k, v]}
proc2.call 5 # [5, nil]
proc2.call 5, 6 # [5, 6]
proc2.call [5, 6] # [5, 6], not [[5, 6], nil]
def f(k, v); [k, v] end
f 5 # ArgumentError
f 5, 6 # [5, 6]
f [5, 6] # ArgumentError
def g(*vargs); vargs end
g 5 # [5]
g 5, 6 # [5, 6]
g [5, 6] # [[5, 6]]
However the documentation for Proc.call does not seem to mention this.
Then there is also lambda's that are slightly different, methods as Proc's using &:name
, and maybe some others. And I am not entirely sure Proc.new{|x| x}.call
is exactly the same as the yield
in a method_that_takes_a_block{|x| x}
.
Upvotes: 4
Views: 413
Reputation: 369478
This is leftover from how block arguments used to work before 1.9.
Before Ruby 1.9, blocks used basically assignment semantics for parameter binding. So, if you had a block like
{|a, b, c|}
and something like
yield foo, bar, baz
it would literally be treated like
a, b, c, = foo, bar, baz
And when I say "literally", I mean it. For example, the following would be a legal and fully working implementation of an initialize
method:
define_method(:initialize) do |@foo, @bar| end
Now, you might say, wait a minute, the method doesn't do anything! It is empty! Yes, you are right, the method doesn't do anything, but because blocks use assignment semantics, when I now do
Foo.new(23, 42)
this will literally be interpreted as
@foo, @bar = 23, 42
You could actually do something even crazier than that: you could actually use setter methods as block parameters!
{|foo.bar, baz.quux|}
Now, when I yield
something to that block, then it will actually invoke foo.bar=
and baz.quux=
.
This "feature" was removed in Ruby 1.9+, however. You can no longer use instance variables, global variables, class variables, or setter methods as block parameters. But, there is one useful application of this feature that the designers of Ruby wanted to preserve: automatic destructuring of key-value pairs in Hash#each
.
So, even though nowadays, blocks don't use actual assignment for argument binding, the semantics are still pretty close. This means, for example, that you can ignore arguments at the end, by having fewer parameters. (This means, in turn, when designing an API, you should order block arguments by importance!) Or that you can destructure a single array.
In this regard, Proc
s behave live blocks, and lambdas behave like methods. (The same is true for the handling of return
, BTW: in blocks and Proc
s, return
returns from the enclosing method, in methods and lambdas from the method and lambda itself.) Useful mnemonic: block rhymes with Proc
, and method and lambda are both Greek.
Upvotes: 0
Reputation: 1633
The reason behind this is multiple variable assignment and auto-splat
Let's take your proc2 exemple (with an additional intersting use case) :
proc2 = Proc.new{|k, v| [k, v]}
proc2.call 5 # [5, nil]
proc2.call 5, 6 # [5, 6]
proc2.call [5, 6] # [5, 6], not [[5, 6], nil]
proc2.call [5, 6, 7] # [5, 6]
with ruby you can do a multiple variable assignement:
k, v= 5 # => k=5, v=nil
k, v= 5, 6 # => k=5, v=6
k, v= 5, 6, 7 # => k=5, v=6, 7 is not assigned
You can also expand an array with the splat operator:
k, v= *[5, 6] # => k=5, v=6
You can also pack multiple variable in an array with the splat operator:
k, *v= *[5, 6, 7] # => k=5, v=[6, 7]
ruby can auto splat when suitable:
k, v= [5, 6] # => k=5, v=6
k, v= [5, 6, 7] # => k=5, v=6, 7 is not assigned
as far as I know auto-splat only applies on variables and Proc
parameters assignment
Upvotes: 7