create array with iterative assignment
Asked Answered
H

1

6

Would like to exploit the following behaviour in Ruby

ary = Array.new(5) { |i|
   [i, j=2*i, k=j+1]
}

p ary #> [[0, 0, 1], [1, 2, 3], [2, 4, 5], [3, 6, 7], [4, 8, 9]]

It works for my purposes, but I couldn't find in the language definition whether this is legal Ruby. Is it? Or is it likely to break in the future?

[Edit] A smaller working example raising the same issue is

i = 1
ary = [i, j=2*i, k=j+1]

p ary #> [1, 2, 3]

But of course this example only has theoretical relevance contrary to the first, which does have practical relevance.

Higginbotham answered 11/7, 2020 at 20:52 Comment(21)
What does the documentation for the Ruby Array class say about the new method?Doting
Ruby returns value of last evaluated statement. What makes you think it's not legal?Subeditor
@lurker: it's the inner array creation that the Ruby array spec seems unclear about.Higginbotham
@bravemaster: because specs seem inconclusive in this respect.Higginbotham
Not a problem. More generally, [i, j=m1(i), m2(i,j)] is fine for any methods m1 and m2. That wouldn't be in the doc for Array::new because it is fundamental Ruby behavior (that is common to other languages).Rigsdaler
The Array#new constructor with a block uses the block's return values to determine the contents of the array. Since an array can contain anything, there's no reason why this wouldnt work. You can't expect to have specs / documentation covering every singe form of array you can build. And by the way, the j= and k= in the block are pointless, just return the values directly without assigning them to these temporary variablesMunmro
Oh, sorry, I misunderstood what you were doing there. The temporary variables are not pointless.Munmro
@lurker: the relevant question is about the guarantees for the order of evaluation of array literal elements, which would not be documented in the API docs, it is part of the language specification. And it is by no means obvious. I have read the ISO Ruby Language Specification, and still couldn't say off the top of my head whether there are any guarantees or not.Iberia
@bravemaster: the relevant question is about the guarantees for the order of evaluation of array literal elements. And it is by no means obvious. I have read the ISO Ruby Language Specification, and still couldn't say off the top of my head whether there are any guarantees or not.Iberia
@CarySwoveland: the question is, where is this guaranteed? I can't remember off the top of my head having seen it in the ISO Ruby Language Specification, for example. And I can imagine legitimate reasons for not wanting to make a guarantee such as automated vectorization. (Not that I believe the Ruby core team would use that reasoning.)Iberia
@JörgWMittag ah yes, I see what you mean. It's not the definition of the new block evaluation itself that's unclear, but perhaps the evaluation of the sub-array elements. This question has nothing to do with Array#new at all, but is about the evaluation of the [i, j=2*i, k=j+1] expression itself.Doting
@lurker: I wouldn't be surprised if left-to-right is guaranteed, but I can't say that it is without diving into the specs. I usually prefer not to write code that requires having the spec printed out on your desk, therefore I don't know such things by heart.Iberia
@JörgWMittag I was looking at the details given for ::[] in the Array documentation. It looks like it's equivalent to the question of whether a method call would evaluate it's arguments in order. So does foo(a, b, c) evaluate a, then b, then c before calling foo?Doting
if [i, j=2*i, k=j+1] is specifically what you're asking about in your question, you should be more explicit. It's not obvious from reading the questionMunmro
@lurker: An array literal is not a message send, though. I believe argument binding in message send argument lists is implicitly guaranteed to be left-to-right, sind there is an assertion somewhere buried deep within ruby/spec that uses values of parameter bindings to the left as default argument values for optional positional parameters to the right, but I would have to dig that up, too.Iberia
@CarySwoveland I like the answer but I don't get it. I don't get the "it is fundamental Ruby behaviour".Higginbotham
@maxpleaner: I've edited the question to an almost MWE.Higginbotham
Your new example is not theoretical at all. It's a very practical example that can occur in programming and is the essence of your question.Doting
Koert and @Jörg, I know, merely from experience, that Ruby evaluates method arguments left-to-right. [i=0,i+1] #=> [0,1], but [i+1,i] #=> NameError (undefined local variable or method 'i' for main:Object). Is this a specification of the language? I don't know, but I expect that legions of Ruby coders have over many years written millions of lines of code that depend on arguments being evaluated left-to-right. One can debate whether that is good programming practice, but we can agree that a new version of Ruby that does not evaluate arguments left-to-right would cause havoc...Rigsdaler
...The same issue may occur if, for argument-less methods m1 and m2 when [m1, m2] evaluated. If m1 is evaluated first we obtain m1 #=> v1 and then m2 #=> v2, but if m2 were evaluated first m2 and m1 may return values different than v2 and v1. It might be advantageous to have parallel processing of method arguments baked into the language (as opposed to creating threads). That would require new coding norms, however, so it would effectively be a new language. Putting that aside, if arguments are evaluated sequentially, I see no reason the Ruby monks would deviate from...Rigsdaler
...the current actual or de facto specification that they are evaluated left-to-right, and if it is merely de facto I think there's a good argument to make it a formal specification.Rigsdaler
S
0

Perhaps a safer way would be to explicitly define the methods? eg:

def m1(i)
  2*i
end
def m2(i)
  m1(i) + 1
end

ary = Array.new(5) { |i|
   [i, m1(i), m2(i)]
}
Selfopinionated answered 12/8 at 5:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.