ruby picture

RCR 291: Consistent *splat and "," for calls, returns, and assignment

Submitted by itsme213 (Fri Jan 14 13:01:25 UTC 2005)

Abstract

This RCR makes the rules for *splat and "," (for binding variables to multiple values or arrays) more consistent for calls, returns, and assignments.

Problem

Currently * and "," act inconsistently for assignment lhs/rhs, vs. method-params/call in cases where the variable binding pattern appears identical. In 1.8.2:
 multiv = 1, 2, 3 # multiv=expr = [1, 2, 3]
 def f multiv; multiv; end
 f 1, 2, 3 #=> ArgumentError

 unsplat = *[1, 2, 3] # unsplat=expr = [1, 2, 3]
 def f unsplat; unsplat; end
 f *[1, 2, 3] #=> ArgumentError

 *splat = 1, 2, 3 # splat=expr = [1, 2, 3]
 def f *splat; splat; end
 f 1, 2, 3 #=> splat=expr = [1, 2, 3]

 firstvalue, = 1, 2, 3 # firstvalue = 1; expr = [1, 2, 3]
 def f(firstvalue,); firstvalue; end
 #=> syntax error

Proposal

Multi-Valued Objects

Assume m_v constructs a multi-valued object, distinct from an array. m_v objects have special rules for parameter passing and assignment-based variable bindings.

 m_v(1, 2, 3) # multi-valued object with 1, 2, 3

No nesting rule: m_v objects automatically un-nest.

 m_v(1, m_v(2, 3), 4) == m_v(1, 2, 3, 4)

Rvalues

A * on the rhs of assignment, or on an arg value passed in to a call, or on the return value of a method, always does unsplat i.e. converts array following the * to multi-valued object. If in the middle of another multi-value, it is inserted flat (see no nesting rule).

 *[1, 2, 3] # expr = m_v(1, 2, 3)
 f(*[1, 2, 3]) # f m_v(1, 2, 3), same as f(1,2,3)
 f; return *[1, 2, 3]; end # return m_v(1, 2, 3)
 x = *1 #=> ArgumentError, can only unsplat Array
 1, *[2, 3], 4 #=> m_v(1, 2, 3, 4)

Lvalues with *

A * on the last position on lhs of assignment, or the last positional param list of a method or proc or block, always does a splat i.e. multi-values to array + bind var to that array (or to []}. The expression value (where needed) is the original RVALUE single object or m_v.

 x, *y = 1, 2, 3 #=> x=1, y=[2,3], expr=m_v(1, 2, 3)
 x, *y = 1 # x=1, y=[], expr=1
 def f(x,*y); puts x, y; end
 f(1,2,3) # x=1, y= [2, 3]
 x, y, z = 1, *[2, 3], 4
 # x=1, y=2, z=3, expr = m_v(1, 2, 3, 4)
 x, y, z = 1, [2,3], 4
 # x=1, y=[2,3], z=4, expr = m_v(1, [2,3], 4)
 def g(x,y,z,u,v,*spl); [x,y,z,u,v,spl]; end
 def h; return 1, 2; end
 g(0, h, 3) #=> [0, 1, 2, 3, nil, []]

Lvalues without *

Without a * on a lhs variable (or the param list of a method), that variable is always bound to the corresponding position in a mutli-value object (with default bind to nil for assignment, argument error for params). Expression value (where needed) is the original single value or m_v.

 x = 1, 2, 3 # x =1; expr = m_v(1, 2, 3)
 def f x; x; end
 f 1 #=> 1
 f 1, 2 #=> ArgumentError

Comma

A "," on a rhs of assignment, or on actual params to a call constructs a multi-valued object. A "," on lhs or param-list might construct something like a multi-valued-binder (something that will bind multiple variables). Unless *splatted, element [i] of a multi-valued binder is always bound to element[i] of the multi-valued object. If splatted, element[i] of a multi-valued binder is bound to an array with elements[i] to elements[length-1] of the multi-valued object.

"," at end of m_v or m_v binder is either a No-Op, or is not allowed.

When are m_v objects directly accessible?

In many cases, such m_v object is not directly accessible, and can be optimized by Ruby.

 e.g. x = (y, z = 1, 2) 
 # x = m_v (1, 2)
 # x = 1
 # m_v not accessible
 y, x = 1, 2
 # y = 1, x = 2, m_v not accessible

Revised behavior of examples from "Problem" section


 multiv = 1, 2, 3 # multiv=1; expr = m_v(1, 2, 3)
 def f multiv; multiv; end
 f 1, 2, 3 #=> ArgumentError

 unsplat = *[1, 2, 3] # unsplat=1; expr=m_v(1, 2, 3)
 def f unsplat; unsplat; end
 f *[1, 2, 3] #=> ArgumentError

 *splat = 1, 2, 3 # splat=[1,2,3]; expr=m_v(1, 2, 3)
 def f *splat; splat; end
 f 1, 2, 3 #=> splat=[1,2,3]; expr=[1, 2, 3]

 firstvalue,=1,2,3 # firstvalue=1;expr=m_v(1, 2, 3)
    # or syntax error
 def f(firstvalue,); firstvalue; end
 #=> syntax error

Analysis

Pro: Make "*" and "," consistent for treatment of multiple values and variable-length arguments in assignemt, method calls, and returns.

Can return multiple values without clients having to be aware of it unless they want to (Just as client does not have to pass in certain parameter values unless they want to).

Con: Not compatible with 1.8.2 behavior.

Implementation

Could be simulated with Florian's Binding.of_caller stuff, I suppose.
ruby picture
Comments Current voting
I would support separation of Multivals and Arrays.


I'd support this. But, I'm sure many would complain of the incompatibilities. Along with this RCR, I'd also say that methods should be able to return this type of thing:

def test;return(1,2,3);end

x,y,z = test # x=1, y=2, z=3

Also, I was thinking that when one of these tuples is in the context of a simple expression, it should return the first element:

test+4 # 1+4 # 5 x = test # x = 1

This could also be used to do post operation:

x, (x+=1) # equivalent to begin; tmp=x;x+=1;tmp; end

Doing it this way, you'd never need to expose these tuples as real objects.

Also, I guess the * operator could generate a tuple using the each method of the object it is splatting. Then you could splat anything instead of just arrays.

But, all of this will create incompatibilities...


Strongly opposed 1
Opposed 0
Neutral 2
In favor 1
Strongly advocate 0
ruby picture
If you have registered at RCRchive, you may now sign in below. If you have not registered, you may sign up for a username and password. Registering enables you to submit new RCRs, and vote and leave comments on existing RCRs.
Your username:
Your password:

ruby picture

Powered by .