Ruby implicit coercion
Let's imagine this scenario: your billing system has an InvoiceValue class which has n entries, one for each purchased product.
Your InvoiceValue::Entry, exposes a method subtotal which is the product of price and quantity.
def subtotal
price * quantity
end
So that, to calculate the InvoiceValue total you'd use:
def total
@entries.inject(0) {|result, e| result += e.subtotal }
end
Which is a bit weird: if you imagine InvoiceValue::Entry as a pure value, it should be easy to be used directly in the mathematical expressions, such as +=.
The first thing you're tempted to do is to override Numeric#+, in order to handle this special case, but this pollutes a lot the behavior of that method.
Luckily, Ruby is such a beautiful language, which allows implicit coercion between types. All you need to to is to implement a method:
protected
def coerce(other)
[other, subtotal]
end
There are a few things that need an explanation. First of all, #coerce requires to return an array with two elements: the object received as argument and the coerced value of the same type.
The other here represents the other object we're trying to cast against (in this case a Fixnum) and subtotal is the value we want to use in the arithmetic expressions.
This leads to simplify a lot our implementation:
def total
@entries.inject(0, :+)
endWritten by Luca Guidi
Related protips
3 Responses
Nice tip! It's also possible to use this syntax:
@entries.inject(:+)
no need to pass the first value (0 in this case), because it's already used by default.
@apecox In this case, if you omit 0, you're forced to implement #+ in InvoiceValue::Entry. This because the loop don't have an initial value and tries to call #+ on all the entries.
Instead, with that explicit argument the VM tries to do 0.+(entry), that means a Fixnum with an InvoiceValue::Entry so asks to the last one to coerce itself, and the trick works.
So, there is a win in having that 0.
If you have to call a method to coerce it it is not implicit...