Splat array with 1 value in Ruby 1.9 vs Ruby 1.8

Discussion in 'Ruby' started by Raul Parolari, Dec 1, 2009.

  1. In porting some automation code from 1.8.7 to 1.9.1, I find that in ruby
    1.8 the following generated code (real code is unpacking binary strings
    and assigning variables, but I simplify for clarity, although the result
    may look a bit silly) worked:

    var1 = *[ val1 ] # var1 =
    val1
    var1, var2, varn = *[ val1, val2, valn ] # ,,

    (the splat is not necessary in the second case, but this allowed the
    code to be generated in the same way, regardless of array size).

    With Ruby 1.9.1-p243, instead, this happens:

    var1 = *[ val1 ] # var1 = [
    val1 ] (!)
    var1, var2, varn = *[ val1, val2, valn ] # var1 = val1

    So in the first case, var1 became an array (instead than the expected
    integer).
    Of course, this can be fixed by generating code that checks the array
    size (and doing a shift if 1, etc). But I am curious on this behavior of
    splat when the array contains one element (I read about the
    differences/new features of splat in 1.9, but they seem to have nothing
    to do with this). Does anyone know if it is a bug?

    Thanks

    Raul Parolari
     
    Raul Parolari, Dec 1, 2009
    #1
    1. Advertisements

  2. [ Sorry for the horrible indentation. I repeat the text renouncing to
    align the expressions ]

    In porting some automation code from 1.8.7 to 1.9.1, I find that in ruby
    1.8 the following worked (real code is unpacking binary strings and
    assigning variables, but I simplify for clarity):

    var1 = *[ val1 ] # => var1 = val1

    var1, var2, varn = *[ val1, val2, valn ]

    (I know that the splat is not necessary in the second case, but this
    allowed the code to be generated in the same way, regardless of array
    size).

    With Ruby 1.9.1, instead, this happens:

    var1 = *[ val1 ] # => var1 = [ val1 ] (!)

    var1, var2, varn = *[ val1, val2, valn ]

    Of course, this can be handled by generating code that checks the array
    size. But I am curious on this behavior of splat when the array contains
    one element (I read about the features of splat in 1.9, but they seem to
    have nothing to do with this). Does anyone know if it is a bug or
    intentional?

    Thanks
     
    Raul Parolari, Dec 1, 2009
    #2
    1. Advertisements


  3. Not sure exactly what you mean by generating the code, but could you use

    var1 =3D val1
    and
    var1, var2, varn =3D val1, val2, valn



    --=20
    Rick DeNatale

    Blog: http://talklikeaduck.denhaven2.com/
    Twitter: http://twitter.com/RickDeNatale
    WWR: http://www.workingwithrails.com/person/9021-rick-denatale
    LinkedIn: http://www.linkedin.com/in/rickdenatale
     
    Rick DeNatale, Dec 1, 2009
    #3
  4. As I said at the beginning of the post, the code is unpacking binary
    strings, which generates an array (else of course I would have not added
    the array, just for the fun of having to remove it). For example:

    x, y = "\x23\x00\x61".unpack('Cn') # => [35, 97]; so: x = 35; y = 97

    Now, when there is just one variable to extract, we get (as expected):

    z="\x00\x61".unpack('n') # z = [97]

    To get the scalar values in both cases, in 1.8.7 a splat operator in
    front of the right value expression (placed by the code generator) does
    the job; in 1.9.1, it does not. The solution is simple, but I was not
    asking for "a solution".

    In other words, I was just pointing out this difference of behavior:

    a = *[ 3 ] # => a =3 with Ruby 1.8.7

    a = *[ 3 ] # => a = [ 3 ] with Ruby 1.9.1

    I think that this is a bug in Ruby 1.9.

    Raul Parolari





    x =
     
    Raul Parolari, Dec 1, 2009
    #4
  5. Regardless whether it is or is not a bug (I would not be so sure of
    that), there is a way to fix it and handle it uniformly across Ruby
    versions:

    17:31:18 test$ allruby -e 'a =3D [123];b =3D *[456];c, =3D [789]; p a,b,c'
    CYGWIN_NT-5.1 padrklemme1 1.5.25(0.156/4/2) 2008-06-12 19:34 i686 Cygwin
    =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
    =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
    ruby 1.8.7 (2008-08-11 patchlevel 72) [i386-cygwin]
    [123]
    456
    789
    =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
    =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
    ruby 1.9.1p129 (2009-05-12 revision 23412) [i386-cygwin]
    [123]
    [456]
    789
    =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
    =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
    jruby 1.4.0 (ruby 1.8.7 patchlevel 174) (2009-11-02 69fbfa3) (Java
    HotSpot(TM) Client VM 1.6.0_17) [x86-java]
    [123]
    456
    789

    Look at variable "c".

    Kind regards

    robert


    --=20
    remember.guy do |as, often| as.you_can - without end
    http://blog.rubybestpractices.com/
     
    Robert Klemme, Dec 1, 2009
    #5
  6. Indeed, this solves with grace the problem in the automation (without
    the easy but disturbing question on "how many elements the array has"):

    a,b, = [ 3, 4 ] # => a = 3; b = 4
    a, = [ 3 ] # => a = 3

    On the other matter ( a = *[ 3 ] ), I still think that it is a bug in
    1.9 (else I don't understand fully what a splat operator is, and I'd
    like to know more).

    But thanks a lot, Robert

    Raul Parolari
     
    Raul Parolari, Dec 1, 2009
    #6
  7. I know there have been some things changed with regard to how the splat
    operator works, including more complex patterns:

    [email protected]:~$ ruby1.9 -e 'def f(a,*b,c)p a,b,c end;f(1, 2, 3, 4)'
    1
    [2, 3]
    4

    This did not work in 1.8:

    [email protected]:~$ ruby1.8 -e 'def f(a,*b,c)p a,b,c end;f(1, 2, 3, 4)'
    -e:1: syntax error, unexpected tIDENTIFIER, expecting tAMPER or '&'
    def f(a,*b,c)p a,b,c end;f(1, 2, 3, 4)
    ^
    [email protected]:~$

    The changes may make it necessary that 1.9 behaves the way you observed.
    That's why I said I am not sure whether it is actually a bug.
    You're welcome!

    Kind regards

    robert
     
    Robert Klemme, Dec 1, 2009
    #7
  8. Hmm.. the difference is:
    a) in 1.8 the splat operator could only be applied to the last
    left-value
    b) in 1.9 the splat operator can appear at any position in the list of
    left-values

    But the concept of the operation has remained identical: all extra
    rvalues are placed into an array

    Similarly, 1.9 adds more flexibility when the splat is done on an rvalue
    (multiple splats). But the concept is still the same (in this case, as
    Matz/Flanagan's book says "the array elements replace the array in the
    original rvalue"). That's the reason that I think the behavior of
    a = *[ 3 ] # => a = [ 3 ]
    is a bug; else, it would go against the definition just given (just
    because the array has 1 element). Just my opinion, of course.
     
    Raul Parolari, Dec 1, 2009
    #8
  9. If you believe you have found a bug, please file it so it can be taken care=
    of.
    http://redmine.ruby-lang.org/
    You're welcome!

    Kind regards

    robert

    --=20
    remember.guy do |as, often| as.you_can - without end
    http://blog.rubybestpractices.com/
     
    Robert Klemme, Dec 2, 2009
    #9
  10. Thanks for the suggestion; I just reported (what I believe to be) the
    bug, with the quote from Matz's book

    Raul Parolari
     
    Raul Parolari, Dec 2, 2009
    #10
  11. Well, so what's the solution to get the single element in the array or the
    whole array when multiple elements ?

    for exemple:

    def my_method_handling_single_or_multiple
    ...
    "str".scan(/.../)
    end

    how to make this in a beautiful way?
    I would like to have only the element if there is only one

    of course, there is:
    arr =3D "str".scan(/.../)
    arr.length =3D=3D 1 ? arr[0] : arr

    but that create a useless new variable and doesn't look good at all.

    B.D.

     
    Benoit Daloze, Dec 19, 2009
    #11
  12. Benoit, str.scan(pattern) always returns an array of matches; you then
    need to process the resulting array (for example, iterating on it,
    extracting the elements of the array). You seem to want a different
    result depending if the array size is 1 or not (that is by the way the
    opposite of what this post was about, on a different problem).

    I find that suspect; think about it: from that moment on, the rest of
    the program will have to deal with a result that is of different type
    depending on the fact that there was only 1 value or not in the array?
    this is odd, and it is usually the contrary of what you want.
    Anyhow, if you want to do such a thing, yes, test for array size and
    extract the value (with shift or [0], etc). And at that point, you will
    happily have a scalar value or an array depending on the number of
    elements in the original array.

    ---
    For clarity:
    the original problem discussed in the thread was this: the splat
    operator does not do what it used to when there is only 1 element in the
    array. Robert Klemme has indicated above a solution to this (using the
    'comma' notation at the end of left values). I posted a bug against
    Ruby 1.9.
    This problem posted by Benoit above has nothing to do with this.
     
    Raul Parolari, Dec 19, 2009
    #12
  13. [Note: parts of this message were removed to make it a legal post.]

    Humm, well, it has a very-not-easy-to-see link, but it is.

    I imagined at a moment using the splat operator, while it can of course not
    work without any variable.

    I use code above to allow dynamic parameters based on the name of the
    variable who's result of that method is assigned to that variable.
    (for exemple :
    x = var
    allow to pass a parameter "x" to the method var() )

    So I'm *scan*ning the code (probably not a good idea but ...) and then I
    want this method to return an array if I do:
    x, y = var
    or a single object like above.

    Ok, I'll just keep the ".length" code ... but I asked if there was a feature
    on Array acting like a splat operator.

    class Array
    def splat
    length == 1 ? shift : self
    end
    end

    (then in 1.8, I imagined to do: "*result" at the last line of the method.
    irb(main):001:0> def m
    irb(main):002:1> arr = [2]
    irb(main):003:1> *arr
    irb(main):004:1> end
    SyntaxError: compile error
    (irb):3: syntax error, unexpected '\n', expecting '='
    from (irb):4
    irb(main):005:0> def m
    irb(main):006:1> arr = [2]
    irb(main):007:1> return *arr
    irb(main):008:1> end
    => nil
    irb(main):009:0> m
    => 2
    For once, return is needed it seems ...)

     
    Benoit Daloze, Dec 19, 2009
    #13
  14. Please do not top post.

    I believe you are overlooking something: the very moment that you do "x
    = meth(...)" you are *expecting* a single value or you are *expecting*
    an Array with any number of elements. If you do "a,b,c = meth(...)" you
    are *expecting* multiple values, typically three. The expectation is
    expressed by the code that follows this line, i.e. if you do

    x = meth(...)
    x.each {|m| ... }

    your code expects an Array with any number of entries. If you do

    x = meth(...)
    puts Integer(x)

    your code expects a single value. If you have these two different use
    cases of your method, it makes every sense in the world to have *two*
    different methods. It's easier for the implementation and it is more
    efficient as well because when you expect a single value you can use
    String#[] or a regexp match with =~ instead of scanning the whole string.

    Now if you argue that you want to use the same regexp in both cases,
    that's not too difficult: both methods can share the regexp.

    RX = /.../

    def m1(...)
    s[RX]
    end

    def mn(...)
    s.scan RX
    end

    In fact, if the implementation is just a single line both methods might
    actually be superfluous and you could use the regular expression directly.

    Your wish may be motivated by Perl experience where a method can
    actually find out what the caller expects but Ruby is different and I
    believe it does not make sense to try to force perlisms into a Ruby program.

    Btw, there's also a different way to deal with this:

    def m(...)
    s.scan ...
    end

    a, = m(...)
    a,b,c = m(...)

    By doing that every variable receives a single value and you do can
    always return an Array.

    irb(main):001:0> def demo(items) Array.new(items) {|i| i} end
    => nil
    irb(main):002:0> demo(5)
    => [0, 1, 2, 3, 4]

    irb(main):003:0> a, = demo(5); a
    => 0
    irb(main):004:0> a,b,c = demo(5)
    => [0, 1, 2, 3, 4]
    irb(main):005:0> a
    => 0
    irb(main):006:0> b
    => 1
    irb(main):007:0> c
    => 2

    irb(main):012:0> a,b,c = demo(2)
    => [0, 1]
    irb(main):013:0> a
    => 0
    irb(main):014:0> b
    => 1
    irb(main):015:0> c
    => nil

    Kind regards

    robert
     
    Robert Klemme, Dec 20, 2009
    #14
  15. [Note: parts of this message were removed to make it a legal post.]

    And to keep it very simple I can do:

    def vars
    ... # => Array
    end
    def var
    vars.shift
    end

    Regards,
    Benoit
     
    Benoit Daloze, Dec 20, 2009
    #15
  16. That's a good approach. By that you can still change the
    implementation of #var if it proves to be too slow (e.g. if you are
    often scanning large strings and the single match is at the
    beginning).

    Kind regards

    robert


    --=20
    remember.guy do |as, often| as.you_can - without end
    http://blog.rubybestpractices.com/
     
    Robert Klemme, Dec 21, 2009
    #16
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.