ruby 3 array argument splat with keyword arguments - ruby

Before ruby 3 it was possible to do sth like this
def test a, **o
p a, o
end
t = [:ok, **{ok: 2}]
test *t
it would properly assign
:ok to a
and {ok: 2} to o
invoking in ruby 3
you will get
ArgumentError (wrong number of arguments (given 2, expected 1))
Is there work around to splat array argument that holds keyword argument on second position?

If you need to send the second parameter as a hash you need to do next:
def test a, o
p a, o
end
t = [:ok, {ok: 2}]
test *t
because of the separating keyword and positional arguments.

Related

Dealing with 3 or more default arguments Ruby

I've seen a few examples of passing default arguments when creating methods, but none of them seem to address if you want to substitute only the first and third argument... here's an example
def foo(a = 1, b = 2, c = 3)
puts [a, b, c]
end
foo(1, 2)
#=> [1, 2, 3]
When I try to assign a=5 and c=7 and keep b its default value like this:
foo(a=5,c=7)
I get
=> 5,7,3
but I expect 5,2,7
what is the correct way to accomplish this?
Using keyword arguments?
def foo(a: 1, b: 2, c: 3)
puts [a, b, c]
end
foo(a: 5, c: 7)
I've seen a few examples of passing default arguments when creating methods, but none of them seem to address if you want to substitute only the first and third argument...
That's because it is impossible.
Default arguments get bound left-to-right. I wrote more about how arguments get bound to parameters in an answer to these questions:
Mixing keyword with regular arguments in Ruby?
Why can I have required parameters after a splat in Ruby but not optional ones?

Why can I assign two variables corresponding to an array in Ruby?

After about a year of Ruby, I just saw this somewhere and my mind is blown. Why in the world does this work?
>> words = ['uno', 'dos']
=> ["uno", "dos"]
>> first, second = words
=> ["uno", "dos"]
>> first
=> "uno"
>> second
=> "dos"
Specifically, how does this work:
>> first, second = ['uno', 'dos']
Why can I do this? It makes no syntactical sense!
It makes no syntactical sense
But this is part of Ruby's syntax! In the Ruby docs it is known as array decomposition:
Like Array decomposition in method arguments you can decompose an
Array during assignment using parenthesis:
(a, b) = [1, 2]
p a: a, b: b # prints {:a=>1, :b=>2}
You can decompose an Array as part of a larger multiple assignment:
a, (b, c) = 1, [2, 3]
p a: a, b: b, c: c # prints {:a=>1, :b=>2, :c=>3}
Since each decomposition is considered its own multiple assignment you
can use * to gather arguments in the decomposition:
a, (b, *c), *d = 1, [2, 3, 4], 5, 6
p a: a, b: b, c: c, d: d
# prints {:a=>1, :b=>2, :c=>[3, 4], :d=>[5, 6]}
Edit
as Stefan points out in the comments, the docs don't mention that array decomposition also occurs implicitly (i.e. without parenthesis) if there is only one value on the right-hand side:
a, b = [1, 2] works like (a, b) = [1, 2]
Why can I do this? It makes no syntactical sense!
It makes a perfect sense. It is an example of parallel assignment.
When you use = what is happening is each of the list of variables on the left of = are assigned to each of the list of expressions on the right of =.
first, second = ['uno', 'dos']
# is equivalent to
first, second = 'uno', 'dos'
If there are more variables on the left, than expressions on the right, those left variables are assigned with nil:
first, second = 'uno'
first #=> 'uno'
second #=> nil
As to
words = ['uno', 'dos']
first, second = words
first #=> 'uno'
second #=> 'dos'
It is not assigning the whole words array to first leaving second with nil, because while parallel assignment Ruby tries to decompose the right side expression, and does so if it is an instance of Array.
[TIL] Moreover, it attempts to call to_ary on the right side expression, and if it responds to the method, decomposes accordingly to that object's to_ary implementation (credits to #Stefan):
string = 'hello world'
def string.to_ary; split end
first, second = string
first #=> 'hello'
second #=> 'world'
This is called multiple assignment, handy to assign multiple variables at once.
example
one, two = 1,2
puts one #=>1
puts two #=>2
one, two = [1,2] # this makes sense
one, two = 1 # obviously this doesn't it will assign nil to two
Hope its bit clear now

How does the compose (*) function work in Ruby (from The Ruby Programming Language)?

Excerpt The Ruby Programming Language:
module Functional
def compose(f)
if self.respond_to?(:arity) && self.arity == 1
lambda {|*args| self[f[*args]] }
else
lambda {|*args| self[*f[*args]] }
end
end
alias * compose
end
class Proc; include Functional; end
class Method; include Functional; end
f = lambda {|x| x * 2 }
g = lambda {|x, y| x * y}
(f*g)[2, 3] # => 12
What is the difference between f and *f in the if/else clause?
The * either collects all the items into an array, or explodes an array into individual elements--depending on the context.
If args = [1, 2, 3], then:
f[args] is equivalent to f[ [1, 2, 3] ] #There is one argument: an array.
f[*args] is equivalent to f[1, 2, 3] #There are three arguments.
If f[*args] returns [4, 5, 6], then:
self[f[*args]] is equivalent to self[ [4, 5, 6] ] #self is called with 1 arg.
self[*f[*args]] is equivalent to self[4, 5, 6] #self is called with 3 args.
An example of * being used to collect items into an Array is:
lambda {|*args| ....}
You can call that function with any number of arguments, and all the arguments will be collected into an array and assigned to the parameter variable args.
* (also known as splat) allows you to call a method with an array of arguments rather than passing the values individually. If you left off the splat then ruby would just pass a single value (that happens to be an array). This unary * is not the * operation that this code defines as an alias of compose
In the if branch self has an arity of 1, then f should be returning a single value and so there is no need for the splat.
In the else branch self takes more than one argument, it follows that f should return an array of values, and the splat is used to call self with those arguments.

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
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
Just encountered a similar issue!
Anyways, my main takeaways:
The splat operator works for array assignment in a predictable manner
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.

What does a * in front of a string literal do in ruby?

This code seems to create an array with a range from a to z but I don't understand what the * does. Can someone please explain?
[*"a".."z"]
It's called splat operator.
Splatting an Lvalue
A maximum of one lvalue may be splatted in which case it is assigned an Array consisting of the remaining rvalues that lack corresponding lvalues. If the rightmost lvalue is splatted then it consumes all rvalues which have not already been paired with lvalues. If a splatted lvalue is followed by other lvalues, it consumes as many rvalues as possible while still allowing the following lvalues to receive their rvalues.
*a = 1
a #=> [1]
a, *b = 1, 2, 3, 4
a #=> 1
b #=> [2, 3, 4]
a, *b, c = 1, 2, 3, 4
a #=> 1
b #=> [2, 3]
c #=> 4
Empty Splat
An lvalue may consist of a sole asterisk (U+002A) without any associated identifier. It behaves as described above, but instead of assigning the corresponding rvalues to the splatted lvalue, it discards them.
a, *, b = *(1..5)
a #=> 1
b #=> 5
Splatting an Rvalue
When an rvalue is splatted it is converted to an Array with Kernel.Array(), the elements of which become rvalues in their own right.
a, b = *1
a #=> 1
b #=> nil
a, b = *[1, 2]
a #=> 1
b #=> 2
a, b, c = *(1..2), 3
a #=> 1
b #=> 2
c #=> 3
The splat operator expands the range into an array.
Huh, fun fact. When you do this:
*(0..50)
you get an error.
The splat operator, in this case, requires a receiver in order to work. So don't fool yourself into thinking its broken in irb by just trying it without a receiver.

Resources