0

I'm practicing ducktyping now in Ruby, and am trying to modify an argument's value based on another argument passed to the same method. However, it just doesn't work.

For example:

  class Duck

  attr_reader :foo, :bar

  def initialize
    @foo = false
    @bar = false
  end

  def duck_foo
    ducktype(@foo, @bar)
  end

  def duck_bar
    ducktype(@bar, @foo)
  end

  def ducktype(duck1, duck2)
    p duck1 #=> false
    p duck2 #=> false
    puts "foo: #{foo} bar: #{bar}" #=> "foo: false bar: false"
    duck1 = true if duck2 == false #<= For #duck_foo: I want to make @foo = true if @bar == false. But it only changes duck1 to true. / For #duck_bar: I want to make @bar = true.
    p duck1 #=> true
    p duck2 #=> false
    puts "foo: #{foo} bar: #{bar}" #=> "foo: false bar: false" => @foo is still false. I want it true!
  end

end

duck = Duck.new
duck.duck_foo
duck.duck_bar

The output for #duck_foo, I expect to see is @foo becomes true. However, it only changes duck1 to true and @foo is still false.

How can I make it work?

In essence, I'm trying to make:

 def duck_foo
    p foo
    p bar
    puts "foo: #{foo} bar: #{bar}"
    @foo = true if @bar == false #=> change @foo to true.
    p foo
    p bar
    puts "foo: #{foo} bar: #{bar}"
  end

  def duck_bar
    p foo
    p bar
    puts "foo: #{foo} bar: #{bar}"
    @bar = true if @foo == false #=> change @bar to true.
    p foo
    p bar
    puts "foo: #{foo} bar: #{bar}"
  end

Into:

  def duck_foo
    ducktype(@foo, @bar)
  end

  def duck_bar
    ducktype(@bar, @foo)
  end

  def ducktype(duck1, duck2)
    #whatever code necessary to preserve the original methods' behavior.
  end

So the code is cleaner and easier to maintain.

Hope this makes sense. Thanks everyone!

2
  • 1
    Where does duck typing come in to picture here? I see nobody walking or quacking like a duck..Check this: en.wikipedia.org/wiki/Duck_typing#In_Ruby , however your question is about pass-by-value or pass-by-reference of the method arguments. PinnyM's answer explains that though. Commented Feb 24, 2014 at 19:04
  • Hey thanks for the link! Yeah, I guess I don't really understand what ducktyping really means. I originally wanted to say that #duck_foo and #duck_bar are almost the same, so they're essentially the same duck. But I guess I was using the wrong term lol Commented Feb 24, 2014 at 19:14

2 Answers 2

1

The problem here is that you are trying to modify the value held by an instance variable by assignment to a local variable reference. Sorry, but this isn't going to work in ruby - assigning to a local variable will simply make the local variable point to the new object without affecting the first object.

However, you can do what you need fairly simply with a small modification (though you'll want to use attr_accessor instead of attr_reader to keep away from ugly eval blocks):

class Duck

  attr_accessor :foo, :bar

  def initialize
    @foo = false
    @bar = false
  end

  def duck_foo
    ducktype(:foo, :bar)
  end

  def duck_bar
    ducktype(:bar, :foo)
  end

  def ducktype(duck1, duck2)
    p send(duck1) #=> false
    p send(duck2) #=> false
    puts "foo: #{foo} bar: #{bar}" #=> "foo: false bar: false"
    send(:"#{duck1}=", true) if send(duck2) == false 
    p send(duck1) #=> true
    p send(duck2) #=> false
    puts "foo: #{foo} bar: #{bar}" #=> "foo: true bar: false"
  end
end

In this implementation, instead of passing instance variables (which lose context when passed on to a local variable), we pass our intent as symbol 'messages'. We can then use these symbols to interact with our object as needed.

Sign up to request clarification or add additional context in comments.

1 Comment

This is the solution I was searching for! I didn't know you could do #{duck1}=, this is BEAUTIFUL. Thank you so much!
0

One way is to check the object_id:

  def ducktype(duck1, duck2)
    puts "foo: #{foo} bar: #{bar}" #=> "foo: false bar: false"
    if duck1.object_id == @foo.object_id
        @foo = true if !duck2
    elsif duck1.object_id == @bar.object_id
        @bar = true if !duck2
    end
    puts "foo: #{foo} bar: #{bar}" #=> "foo: true bar: false"
  end

Since you are passing in Booleans, you cannot change their value from within the function, like you can with Strings, using str.replace('true'), for example.


When do you need to check the object_id?

If instead @foo and @bar were Strings instead of Booleans, you could do:

  def ducktype(duck1, duck2)
    puts "foo: #{foo} bar: #{bar}" #=> "foo: false bar: false"
    duck1.replace('true') if duck2 == 'false'
    puts "foo: #{foo} bar: #{bar}" #=> "foo: true bar: false"
  end

The reason duck1 = true doesn't work regardless of its type is because a new instance is assigned to it. I.e:

duck1 = true # => true
duck1.object_id # => 2
duck1 = false # => false
duck1.object_id # => 0  <-- id changes
duck1 = 'true' # => "true"
duck1.object_id # => 6563940
duck1.replace('false') # => "false"
duck1.object_id # => 6563940 <-- id stays the same

Therefore if the data type doesn't possess a member function that can change its own value, then it seems one must resort to checking the object_id as above to accomplish this.

1 Comment

Awesome! Using object_id is a brilliant solution and thanks for explaining why duck1=true doesn't work. I'm definitely still too new to Ruby.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.