Reputation: 65
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!
Upvotes: 0
Views: 969
Reputation: 35541
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.
Upvotes: 1
Reputation: 20796
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.
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.
Upvotes: 0