Dealing with 3 or more default arguments Ruby - 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?

Related

ruby 3 array argument splat with keyword arguments

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.

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

Block with two parameters

I found this code by user Hirolau:
def sum_to_n?(a, n)
a.combination(2).find{|x, y| x + y == n}
end
a = [1, 2, 3, 4, 5]
sum_to_n?(a, 9) # => [4, 5]
sum_to_n?(a, 11) # => nil
How can I know when I can send two parameters to a predefined method like find? It's not clear to me because sometimes it doesn't work. Is this something that has been redefined?
If you look at the documentation of Enumerable#find, you see that it accepts only one parameter to the block. The reason why you can send it two, is because Ruby conveniently lets you do this with blocks, based on it's "parallel assignment" structure:
[[1,2,3], [4,5,6]].each {|x,y,z| puts "#{x}#{y}#{z}"}
# 123
# 456
So basically, each yields an array element to the block, and because Ruby block syntax allows "expanding" array elements to their components by providing a list of arguments, it works.
You can find more tricks with block arguments here.
a.combination(2) results in an array of arrays, where each of the sub array consists of 2 elements. So:
a = [1,2,3,4]
a.combination(2)
# => [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
As a result, you are sending one array like [1,2] to find's block, and Ruby performs the parallel assignment to assign 1 to x and 2 to y.
Also see this SO question, which brings other powerful examples of parallel assignment, such as this statement:
a,(b,(c,d)) = [1,[2,[3,4]]]
find does not take two parameters, it takes one. The reason the block in your example takes two parameters is because it is using destruction. The preceding code a.combination(2) gives an array of arrays of two elements, and find iterates over it. Each element (an array of two elements) is passed at a time to the block as its single parameter. However, when you write more parameters than there is, Ruby tries to adjust the parameters by destructing the array. The part:
find{|x, y| x + y == n}
is a shorthand for writing:
find{|(x, y)| x + y == n}
The find function iterates over elements, it takes a single argument, in this case a block (which does take two arguments for a hash):
h = {foo: 5, bar: 6}
result = h.find {|k, v| k == :foo && v == 5}
puts result.inspect #=> [:foo, 5]
The block takes only one argument for arrays though unless you use destructuring.
Update: It seems that it is destructuring in this case.

Left-hand side and right-hand side in multiple assignment

I'm having trouble understanding these two sections in ruby-doc:
Implicit Array Assignment
Multiple Assignment
When it says left-hand side, the splat operator is on the right side, and when it says right-hand side, the operator is on the left side. For example:
The * can appear anywhere on the right-hand side:
*a, b = 1, 2, 3
p a: a, b: b # prints {:a=>[1, 2], :b=>3}
Can anyone explain me what the meaning of left-hand side and right-hand side is in these sections? To me, examples seem contradictory.
I think this is a mistake in this v2.0.0 reference manual. Your understanding is correct.
Both have been fixed in the v2.2.0 manual (Implicit Array Assignment and Multiple Assignment).
I think it is supposed to mean what you have in mind. However, the document looks like it has mistakes. You can report this to the developers here as a documentation bug.
* will turn an argument list in to an array and visa versa:
def do_it(*args)
args
end
do_it(1, 'hello') # => [1, 'hello']
In the case of *a, b = 1, 2, 3 it is processed right to left, b is assigned 3 and the remaining arguments, 2 and 3 to a as an array, [2, 3].
In the case of a = 1, *[2,3], the array, [2, 3], is converted to arguments 2, 3. Therefore equivalent to a = 1, 2, 3.
Why a = 1, 2, 3 is valid and does not cause an error I do not know. I would guess, Ruby does an implicit splat when you supply multiple arguments to a single var assignment. So a = [1, 2, 3] is functionaly the same as a = 1, 2, 3.

Marking an unused block variable

When there is a block or local variable that is not to be used, sometimes people mark it with *, and sometimes with _.
{[1, 2] => 3, [4, 5] => 6}.each{|(x, *), *| p x}
{[1, 2] => 3, [4, 5] => 6}.each{|(x, _), _| p x}
{[1, 2, 3], [4, 5, 6]}.each{|*, x, *| p x}
{[1, 2, 3], [4, 5, 6]}.each{|_, x, _| p x}
def (x, *), *; p x; end
def (x, _), _; p x; end
def *, x, *; p x; end
def _, x, _; p x; end
What are the differences between them, and when should I use which? When there is need to mark multiple variables as unused as in the above examples, is either better?
A * means "all remaining parameters". An _ is just another variable name, although it is a bit special. So they are different, for example the following does not make sense:
[[1, 2, 3], [4, 5, 6]].each{|*, x, *| p x} # Syntax error
Indeed, how is Ruby supposed to know if the first star should get 0, 1 or 2 of the values (and the reverse)?
There are very few cases where you want to use a star to ignore parameters. An example would be if you only want to use the last of a variable number of parameters:
[[1], [2, 3], [4, 5, 6]].each{|*, last| p last} # => prints 1, 3 and 6
Ruby allows you to not give a name to the "rest" of the parameters, but you can use _:
[[1], [2, 3], [4, 5, 6]].each{|*_, last| p last} # => prints 1, 3 and 6
Typically, the number of parameters is known and your best choice is to use a _:
[[1, 2, 3], [4, 5, 6]].each{|_, mid, _| p mid} # prints 2 and 5
Note that you could leave the last paramater unnamed too (like you can when using a *), although it is less obvious:
[[1, 2, 3], [4, 5, 6]].each{|_, mid, | p mid} # prints 2 and 5
Now _ is the designated variable name to use when you don't want to use a value. It is a special variable name for two reasons:
Ruby won't complain if you don't use it (if warnings are on)
Ruby will allow you to repeat it in the argument list.
Example of point 1:
> ruby -w -e "def foo; x = 42; end; foo"
-e:1: warning: assigned but unused variable - x
> ruby -w -e "def foo; _ = 42; end; foo"
no warning
Example of point 2:
[[1, 2, 3], [4, 5, 6]].each{|unused, mid, unused| p mid}
# => SyntaxError: (irb):23: duplicated argument name
[[1, 2, 3], [4, 5, 6]].each{|_, mid, _| p mid}
# => prints 2 and 5
Finally, as #DigitalRoss notes, _ holds the last result in irb
Update: In Ruby 2.0, you can use any variable starting with _ to signify it is unused. This way the variable name can be more explicit about what is being ignored:
_scheme, _domain, port, _url = parse_some_url
# ... do something with port
I think it's mostly stylistic and programmer's choice. Using * makes more sense to me in Ruby because its purpose is to accumulate all parameters passed from that position onward. _ is a vestigial variable that rarely sees use in Ruby, and I've heard comments that it needs to go away. So, if I was to use either, I'd use *.
SOME companies might define it in their programming style document, if they have one, but I doubt it's worth most of their time because it is a throw-away variable. I've been developing professionally for over 20 years, and have never seen anything defining the naming of a throw-away.
Personally, I don't worry about this and I'd be more concerned with the use of single-letter variables. Instead of either, I would use unused or void or blackhole for this purpose.
IMO the practice makes code less readable, and less obvious.
Particularly in API methods taking blocks it may not be clear what the block actually expects. This deliberately removes information from the source, making maintenance and modification more difficult.
I'd rather the variables were named appropriately; in a short block it will be obvious it's not being used. In longer blocks, if the non-use is remarkable, a comment may elaborate on the reason.
What are the differences between them?
In the _ case a local variable _ is being created. It's just like using x but named differently.
In the * case the assignment of an expression to * creates [expression]. I'm not quite sure what it's useful for as it doesn't seem to do anything that just surrounding the expression with brackets does.
When should I use which?
In the second case you don't end up with an extra symbol being created but it looks like slightly more work for the interpreter. Also, it's obvious that you will never use that result, whereas with _ one would have to read the loop to know if it's used.
But I predict that the quality of your code will depend on other things than which trick you use to get rid of unused block parameters. The * does have a certain obscure cool-factor that I kind of like.
Note: when experimenting with this, be aware that in irb, _ holds the value of the last expression evaluated.

Resources