trufflep
trufflep

Reputation: 45

Weird Behavior from Redefining Ruby Method

Why is the output of the program:

require 'set'

class Set
  alias :old_add :add
  def add(arg)
    arg = 5
    old_add arg
  end
end

s = Set.new ([1,2])
s.add(3)

puts s.inspect

being

#<Set: {5}>

as opposed to

#<Set: {1,2,5}>

The method add is redefined to run with argument 5.

Upvotes: 0

Views: 66

Answers (2)

Arup Rakshit
Arup Rakshit

Reputation: 118271

Looking at the source of Set::new:

# File set.rb, line 80
def initialize(enum = nil, &block) # :yields: o
  @hash ||= Hash.new

  enum.nil? and return

  if block
    do_with_enum(enum) { |o| add(block[o]) }
  else
    # you did not supply any block, when you called `new`
    # thus else part will be executed here
    merge(enum)
  end
end

it seems that Set.new calls method #add internally. In the OP's example, block is nil, thus #merge is called:

# File set.rb, line 351
def merge(enum)
  if enum.instance_of?(self.class)
    @hash.update(enum.instance_variable_get(:@hash))
  else
    # in your case this else part will be executed.
    do_with_enum(enum) { |o| add(o) }
  end

  self
end

Hence add is called for each element of enum ([1,2]). Here, you overrode the original #add method, and within that method, you are calling the old #add method with the argument 5.

Set implements a collection of unordered values with no duplicates. Thus even if you added 5 twice, you are getting only one 5. This is the reason you are not getting #<Set: {1,2}>, but #<Set: {5}>. As below, when you call Set.new, the object #<Set: {5}> is created just as I explained above:

require 'set'

class Set
  alias :old_add :add
  def add(arg)
    arg = 5
    old_add arg
  end
end

s = Set.new ([1,2])
s # => #<Set: {5}>

When you called s.add(3), your overridden add method was called, and it again passed the 5 to the old add method. As I said earlier, Set doesn't contain duplicate values, thus the object will still be the same as the earlier #<Set: {5}>.

Upvotes: 2

Jakob S
Jakob S

Reputation: 20125

When you instantiate a new set with given members, the members are added using the add method. This can be shown by investigating the set after creating it:

Set.new([1,2])
# => #<Set: {5}>

Thus, when you do s.add(3) you basically add 5 again, which has already been added twice to the set and therefore the method doesn't actually change the set.

Upvotes: 0

Related Questions