I believe what might be confusing you are some of the properties of Procs. If they are given a single array argument, they will automatically splat it. Also, ruby blocks in general have some interesting ways of handling block arguments. The behavior you're expecting is what you will get with a Lambda. I suggest reading Proc.lambda? documentation and be careful when calling a ruby block with an array.
Now, let's start with the splat operator and then move to how ruby handles block arguments:
def foo(a, b, *c)
c.map { |i| i * b } # Prefer to use map alias over collect
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
So in your argument error, it makes sense: def foo()
takes at least two arguments: a
, b
, and however many with *c
. The *
is the splat operator. It will turn an array into individual arguments, or in the reverse case here, a variable amount of arguments into an array. So when you say foo([1,2,3,4])
, you are giving foo
one argument, a
, and it is [1,2,3,4]
. You are not setting b
or *c
. What would work is foo(1, 1, 1, 2, 3, 4])
for example because you are setting a
, b
, and c
. This would be the same thing: foo(1, 1, *[1,2,3,4])
.
Now foo(*[1, 2, 3, 4])
works as expected because the splat operator (*
) is turning that into foo(1, 2, 3, 4)
or equivalently foo(1, 2, *[3, 4])
Okay, so now that we have the splat operator covered, let's look back at the following code (I made some minor changes):
a_proc = Proc.new { |a, b, *c| c.map { |i| i * b }}
a = [1, 2, 3, 4]
puts a_proc.call(a)
puts a_proc.call(*a)
Remember that if blocks/procs are given a single array
argument they will automatically splat
it. So if you have an array of arrays arrays = [[1, 1], [2, 2], [3, 3]]
and you do arrays.each { |a, b| puts "#{a}:#{b}" }
you are going to get 1:1
, 2:2
, and 3:3
as the output. As each element is passed as the argument to the block, it sees that it is an array and splats it, assigning the elements to as many of the given block variables as it can. Instead of just putting that array in a
such as a = [1, 1]; b = nil
, you get a = 1; b = 1
. It's doing the same thing with the proc.
a_proc.call([1, 2, 3, 4])
is turned into Proc.new { |1, 2, [3, 4]| c.map { |i| i * b }}
and will output [6, 8]
. It splits up the arguments automatically it's own.