Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the Ruby splat not work for array coercion in conditional assignment?

Tags:

ruby

Although the splat (*) construct is commonly referred to as the splat operator, it is clear that it is a different beast, compared to other unary operators like the negation (!) operator.

The splat works fine on it's own (i.e. not wrapped in brackets) when used in assignment (=), but produces an error when used with conditional assignment (||=). Example:

a = *(1..3)
#=> [1, 2, 3]

b ||= *(1..3)
SyntaxError: (irb):65: syntax error, unexpected *

I am not looking for alternative ways of doing the same thing, but looking for someone with a better understanding of the Ruby internals to explain why this usage of the splat construct works in the first case but not in the second.

like image 216
Drenmi Avatar asked Apr 25 '15 11:04

Drenmi


People also ask

What does splat do Ruby?

Splat operator or start (*) arguments in Ruby define they way they are received to a variable. Single splat operator can be used to receive arguments as an array to a variable or destructure an array into arguments. Double splat operator can be used to destructure a hash.

What is a splat operator?

What is the Splat Operator? The * (or splat) operator allows a method to take an arbitrary number of arguments and is perfect for situations when you would not know in advance how many arguments will be passed in to a method.

What does Asterisk do in Ruby?

The * is the splat operator. It expands an Array into a list of arguments, in this case a list of arguments to the Hash.


1 Answers

Here's my understanding of the practical goal of splat. This is for Ruby 2.2 MRI/KRI/YARV.

Ruby splat destructures an object into an array during assignment.

These examples all provide the same result, when a is falsey:

a = *(1..3)
a = * (1..3)
a =* (1..3)
a = *1..3
a = * 1..3
a = * a || (1..3)
a = * [1, 2, 3]
=> [1, 2, 3]

The splat does the destructuring during the assigment, as if you wrote this:

a = [1, 2, 3]

(Note: the splat calls #to_a. This means that when you splat an array, there's no change. This also means that you can define your own kinds of destructuring for any class of your own, if you wish.)

But these statements fail:

*(1..3)
* 1..3
* [1,2,3]
false || *(1..3)
x = x ? x : *(1..3) 
=> SyntaxError

These statements fail because there's no assignment happening exactly when the splat occurs.

Your question is this special case:

b ||= *(1..3)

Ruby expands this to:

b = b || *(1..3)

This statement fails because there's no assignment happening exactly when the splat occurs.

If you need to solve this in your own code, you can use a temp var, such as:

b ||= (x=*(1..3))

Worth mentioning: there's an entirely different use of splat when it's on the left hand side of the expression. This splat is a low-priority greedy collector during parallel assignment.

Examples:

*a, b = [1, 2, 3]  #=> a is [1, 2], b is 3
a, *b = [1, 2, 3]  #=> a is 1, b is [2, 3]

So this does parse:

*a = (1..3)  #=> a is (1..3)

It sets a to all the results on the right hand side, i.e. the range.

In the rare case that the splat can be understood as either a destructurer or a collector, then the destructurer has precendence.

This line:

x = * y = (1..3)

Evaluates to this:

x = *(y = (1..3)) 

Not this:

x = (*y = (1..3)) 
like image 163
joelparkerhenderson Avatar answered Sep 21 '22 07:09

joelparkerhenderson