puts *(1..10) what's that star

puts *(1..10)

What is the star operator called and where can I get more information about it. How does it work? It's hard to search without knowing what the operator is called?

splat

http://jroller.com/dscataglini/entry/more_drunken_ruby
http://redhanded.hobix.com/bits/theSiphoningSplat.html

I’m not sure how this code works.

BOARD_MEMBERS = ['Jan
', 'Julie',
'Archie', '
Stewick']
HISTORIANS = ['Braith'
, 'Dewey',
'Eduardo']
case name
when *BOARD_MEMBERS
  "You're on the board! A congratulations is in order."
 when *HISTORIANS
  "You are busy chronicling every deft play."

 when *HISTORIANS|BOARD_MEMBERS
  "   We welcome you all to the First International
Symposium of Board Members and Historians Alike."
 end
Any explanations.

The splat "unrolls" the BOARD_MEMBERS array into a list of values.
It's the same as writing

  case name
  when 'Jan', 'Julie', 'Archie', 'Stewick'
     ...blah

quite clever, actually. If you didn't have splat, you'd have to do
something like:

  case
  when BOARD_MEMBERS.include?(name)
    ...blah

How costly is doing it the “splat” way? It’s amazing (to me) that it can be written that way. Does it actually have to create the unrolled list? And should I not fret over such things and just write it the clean way?

Seems like Array#=== ought to override Object#=== to behave more like Range#=== and test for inclusion (i.e., like an alias of Array#include?). If it did, then you could avoid the splat overhead (whatever it might be) and the “.include?(name)” duplication on each ‘when’ clause.

Is there really any good reason for Array#=== to do anything else, in other words, does anyone rely on the current behavior that mimics Array#== anyway?

-Rob

Rob Biedenharn http://agileconsultingllc.com

Rob@AgileConsultingLLC.com

Thanks guys.

I guess this is what Ruby idiom is all about. To newbies it looks strage but rubyists are comfortable using such operators.

  • Neeraj

Hi --

Which, I for one, greatly prefer to splat.

Although when used on the other side of assignments as in

   *a = 1, 2,3

   def foo(*parms)
   end

   {:a => 1, :b => 2}.each {|*assoc_array| ...}

neither name seems to fit.

Because then

   [1, 2, 3] === [1, 2, 3]
would become false, and that would probably do more harm than good.

Rather than assuming there's a huge overhead to *, or any overhead at
all, why not do some benchmarks and get back to us.

OK, include? does win over * and === comes in between (with my simple implementation).

As for ===, my curiosity is who actually relies on Array#=== since it is just Object#=== and equivalent to ==.

No one seems to confuse Ranges this way:

(1..10) == (1...10) #=> false
(1..10) == 5 #=> false
(1..10) === 5 #=> true

Anyway, here's the "proof":

require 'benchmark'
include Benchmark

MONTH_WORDS = %w[ january february march april may june july august september october november december ]
DAY_WORDS = %w[ sunday monday tuesday wednesday thursday friday saturday ]
NUMBERS = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41 ]
test_words = %w[ january july december sunday wednesday saturday ruby foo ]
test_numbers = [ 2, 4, 7, 17, 21, 29, 41, 42 ]

bm(18) do |x|
   x.report("unary unarray") { 100_000.times {
       test_words.each { |word|
         case word
         when *MONTH_WORDS : 'month'
         when *DAY_WORDS : 'day'
         else 'neither'
         end
       }
       test_numbers.each { |number|
         case number
         when *NUMBERS : 'match'
         else 'nope'
         end
       }
     } }

   x.report(".include?") { 100_000.times {
       test_words.each { |word|
         case
         when MONTH_WORDS.include?(word) : 'month'
         when DAY_WORDS.include?(word) : 'day'
         else 'neither'
         end
       }
       test_numbers.each { |number|
         case
         when NUMBERS.include?(number) : 'match'
         else 'nope'
         end
       }
     } }

   class Array
     def === obj
       include?(obj)
     end
   end
   x.report("override Array#===") { 100_000.times {
       test_words.each { |word|
         case word
         when MONTH_WORDS : 'month'
         when DAY_WORDS : 'day'
         else 'neither'
         end
       }
       test_numbers.each { |number|
         case number
         when NUMBERS : 'match'
         else 'nope'
         end
       }
     } }

end
__END__
-*- mode: compilation; default-directory: "~/code/ruby/" -*-
Compilation started at Wed Oct 24 17:44:51

ruby unarray_or_include_benchmark.rb
                         user system total real
unary unarray 5.050000 0.020000 5.070000 ( 5.182787)
.include? 3.560000 0.010000 3.570000 ( 3.629457)
override Array#=== 4.110000 0.010000 4.120000 ( 4.218315)

Compilation finished at Wed Oct 24 17:45:04

Rob Biedenharn http://agileconsultingllc.com
Rob@AgileConsultingLLC.com

Hi --

What is the star operator called and where can I get more information
about it. How does it work? It's hard to search without knowing what
the operator is called?

splat

AKA (by me, anyway) the unary unarray operator.

Which, I for one, greatly prefer to splat.

Although when used on the other side of assignments as in

  *a = 1, 2,3

The way I see it this:

*a = 1,2,3 means: assign to a that which, when unarrayed, is the
list 1,2,3 -- namely, the array [1,2,3]. A stretch, perhaps, but it
sort of makes sense :slight_smile:

Of course:

   a = 1,2,3

does the same thing. But that's just the rule about one argument on
the lhs sponging up all the things on the lhs in array form.

  def foo(*parms)
  end

Let's say you call: foo(1,2,3). That means that the bare list 1,2,3 is
equivalent to an unarrayed parms (i.e., *parms) -- so parms must be
the array [1,2,3].

  {:a => 1, :b => 2}.each {|*assoc_array| ...}

Ditto-ish, I think.

neither name seems to fit.

Probably not perfectly, but I think that overall it does behave in an
'unarray' way -- that is, *a always means an array a that corresponds
to a bare list of items. In any event, 'splat' does nothing for me.

David

OK, include? does win over * and === comes in between (with my simple
implementation).

As for ===, my curiosity is who actually relies on Array#=== since it
is just Object#=== and equivalent to ==.

Any code which uses arrays as case discriminators would break on the change.

case x
when [:fred, :ethel]
    ...
when [:ricky, :lucy]
...

No one seems to confuse Ranges this way:

(1..10) == (1...10) #=> false
(1..10) == 5 #=> false
(1..10) === 5 #=> true

Since Ruby defines Range#=== to work the way it does, existing code
works with the definition.

Anyway, here's the "proof":

...

                         user system total real
unary unarray 5.050000 0.020000 5.070000 ( 5.182787)
.include? 3.560000 0.010000 3.570000 ( 3.629457)
override Array#=== 4.110000 0.010000 4.120000 ( 4.218315)

So if such code turns out to be a performance bottleneck in a
particular application, one approach at improving the performance
would be use include? in that case.

But I don't think changing Array#=== is a good idea, nor even the best
solution to this particular performance problem. In fact it might be
fruitful to look at other approaches/algorithms such as testing with
regular expressions:

case word
when /^(jan|febru)ary|ma(rch|y)|april|ju(ne|ly)|august|(octo|(sept|nov|dec)em)ber$/
   'month'
when /^(mon|(tu|wedn)es|thurs|fri|satur|sun)day$/
  'day'
else
'neither'
end

With benchmarking to see if the 'tuned' regexps actually worked better
or worse than more straightforward ones. AND testing to make sure
that it really did what was expected.

Or hashes

WORD_MAP = Hash.new('neither).merge!({'january' => 'month', 'february'
=> 'month, ...
                                             'monday' => 'day',
'tuesday' => 'day', ... 'sunday' => 'day'})
WORD_MAP[word]

with the proper substitution for the ellipses.

But the first test should be readability, and heroic efforts at
performance optimization should be left for truly deserving cases.

That makes a lot of sense! It's similar to the way I taught myself how to
deal with * in C, in a way that works both in declarations and code;
mentally, I call it "what's at". So

  int i; /* i is an integer */
  int *ip; /* what's at ip is an integer */

  ip = &i; /* set ip to the address of i */
  *ip = 3; /* set what's at ip to 3 */

That ends up explaining (nearly?) all the oddities of C declaration syntax,
even function pointers.