YKY
YKY

Reputation: 2683

Ruby's bitwise shift operator "<<" confusion in conjuction with array

I am studying Ruby and can't figure this out. I have an exercise where I have to add a method into a Library class which contains games. Each game is an instance of a class Game. So the solution is the following:

class Library
  attr_accessor :games

  def initialize(games)
    self.games = games
  end

  def has_game?(search_game)
    for game in games
      return true if game == search_game
    end
    false
  end

  def add_game(game)
    @games << game
  end
end

I can't understand how << works in this case. Is this a bitwise left shift? Does Library class just assumes that games is an array, I believe I can pass anything to the Library class when I am initialising, single game or an array of games?

Upvotes: 3

Views: 2643

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110685

When you have:

@games << game

<< is actually a method. If it is a method, you ask, why isn't it written in the normal way:

@games.<<(game)

? You could, in fact, write it that way and it would work fine. Many Ruby methods have names that are symbols. A few others are +, -, **, &, || and %. Ruby knows you'd prefer writing 2+3 instead of 2.+(3), so she let's you do the former (and then quietly converts it to the latter). This accommodation is often referred to as "syntactic sugar".

<< is one of @games' methods (and game is <<'s argument), because @games is the receiver of the method << (technically :<<). Historically, it's called the "receiver" because with OOP you "send" the method to the receiver.

To say that << is a method of @games means that << is an instance method of @games's class. Thus, we have:

@games.methods.include?(:<<) #=> true
@games.class.instance_methods.include?(:<<) #=> true

I expect @games is an instance of the class Array. Array's instance methods are listed here. For example:

@games = [1,2,3]
@games.class     #=> [1,2,3].class => Array
@games << 4      #=> [1,2,3,4]
@games.<<(5)     #=> [1,2,3,4,5]

On the other hand, suppose @games were an instance of Fixnum. For example:

@games = 7
@games.class     #=> 7.class => Fixnum

which in binary looks like this:

@games.to_s(2)   #=> "111"

Then:

@games << 2      #=> 28
28.to_s(2)       #=> "11100"

because << is an instance method of the class Fixnum.

As a third example, suppose @games were a hash:

@games = { :a => 1 }
@games.class     #=> { :a => 1 }.class => Hash

Then:

@games << { :b => 2 }
  #=> NoMethodError: undefined method `<<' for {:a=>1}:Hash

The problem is clear from the error message. The class Hash has no instance method <<.

One last thing: consider the method Object#send. Recall that, at the outset, I said that methods are sent to receivers. Instead of writing:

@games = [1,2,3]
@games << 4         #=> [1,2,3,4]

you could write:

@games.send(:<<, 4) #=> [1, 2, 3, 4] 

This is how you should think of methods and receivers. Because send is an instance method of the class Object, and all objects inherit Object's instance methods, we see that send is "sending" a method (:<<, which can alternatively be expressed as a string, "<<") and its arguments (here just 4) to the receiver.

There are times, incidentally, when you must use send to invoke a method. For one, send works with private and protected methods. For another, you can use send when you want to invoke a method dynamically, where the name of the method is the value of a variable.

In sum, to understand what method does in:

receiver.method(*args)

look for the doc for the instance method method in receiver's class. If you Google "ruby array", for example, the first hit will likely be the docs for the class Array.

Upvotes: 7

Aleksey Shein
Aleksey Shein

Reputation: 7482

It's not a bitwise left shift, it's "shovel" operator. You, probably, know that in Ruby operators are implemented as methods, i.e. when you write

1 + 1

what is actually going on:

1.+(1)

The same is true for "shovel" (<<) operator, it's just a method on Array class, that appends its arguments to the end of the array and returns the array itself, so it can be chained:

>> arr = []
=> []
>> arr << 1
=> [1]
>> arr
=> [1]
>> arr << 2 << 3
=> [1, 2, 3]

Upvotes: 3

Richard Hamilton
Richard Hamilton

Reputation: 26444

It looks like @games is an array. The shift operator for an array adds an element to the end of the array, similar to array#push.

ary << obj → ary

Append—Pushes the given object on to the end of this array. This expression returns the array itself, so several appends may be chained together.

Upvotes: 1

Related Questions