Coercion in Ruby
Between the strong and weak typing
Slide 2
Slide 2 text
Grzegorz Witek
Slide 3
Slide 3 text
Strong vs. weak typing
$> 3 + “a”
Python: unsupported operand type(s) for +: 'int' and 'str'
Ruby: String can't be coerced into Fixnum
Javascript: “3a”
Slide 4
Slide 4 text
Strong vs. weak typing
$> 3 + “a”
Python: unsupported operand type(s) for +: 'int' and 'str'
Ruby: String can't be coerced into Fixnum
Javascript: “3a”
Slide 5
Slide 5 text
Coercion in Ruby
class Money < Struct.new(:amount)
def *(value)
amount * value
end
end
Slide 6
Slide 6 text
How do I Ruby?
money = Money.new(3)
money * 2 # => 6
2 * money # => ERROR U FAIL
Slide 7
Slide 7 text
Bad solution
class Fixnum
alias :old_multiply :*
def *(val)
if defined?(Money) && val.is_a?(Money)
return self * val.amount
else
old_multiply(val)
end
end
end
Slide 8
Slide 8 text
Bad solution
Pros: works
Cons: it’s wrong on as many levels as you can imagine
Slide 9
Slide 9 text
Good solution
class Money < Struct.new(:amount)
def *(value)
amount * value
end
def coerce(other)
[self, other]
end
end
Slide 10
Slide 10 text
Good solution
def coerce(other)
[self, other]
end
Slide 11
Slide 11 text
Good solution
def coerce(other)
[other, amount]
end
Slide 12
Slide 12 text
How does it work?
Short answer:
when Ruby can’t handle the param type, it calls
arg.coerce(self)
it gets 2 elements array, and calls array[0].method(array[1])
Slide 13
Slide 13 text
How does it work?
Fixnum#*(Money) => omg, what to do?
Slide 14
Slide 14 text
How does it work?
Fixnum#(Money)* => omg, what to do?
Money#coerce => [Money, Fixnum]
Slide 15
Slide 15 text
How does it work?
Fixnum#*(Money) => omg, what to do?
Money#coerce => [Money, Fixnum]
Money#*(Fixnum) => I know how to handle it!