Have you ever had to implement arithmetic operations for an object? If so, there is one little method you probably should be aware of, and it's called coerce.

As an example let's imagine we are building a system that manages money transactions, and for that we want to have a Money class to handle all the intricacies of dealing with different currencies, cents, etc...

The Money class

To simplify let's build a class that can only handle one currency (euros) so that we don't have to care about conversions. In order to create an instance of such a class, we need only to pass in the amount in cents.

class Money
  attr_reader :amount_in_cents
  def initialize(amount_in_cents)
    @amount_in_cents = amount_in_cents.to_i
  end

  def amount
    amount_in_cents / 100.0
  end

  def inspect
    "%.2f €" % [amount]
  end
end

That's enough for us to create an print Money instances, but we cannot do anything else with them, like sum or multiply, which was kind of the point.

Arithmetic operations

Ruby makes it extremely easy to reimplement seemingly core methods, such as + or *. That's just what we'll do.

class Money
  ...

  def +(other)
    Money.new(amount_in_cents + other.amount_in_cents)
  end

  def -(other)
    Money.new(amount_in_cents - other.amount_in_cents)
  end

  def *(factor)
    Money.new(amount_in_cents * factor)
  end

  def /(dividend)
    Money.new(amount_in_cents / dividend)
  end
end

Note: that the + and - methods take an instance of Money, but the * and / don't. This will be important for a minute.

This is now a fully (kind of) working implementation of Money, and here's how you'd use it:

Money.new(100) + Money.new(3000) # => 31.00 €
Money.new(100) * 3               # =>  3.00 €

That's great! At this point, you might be wondering what does coerce have to do with this, we've implemented all of this without even thinking about it. That's true, but what happens if we reverse the order of the operands in our example above? The commutative property of both the sum and the multiplication should apply, right?

Money.new(3000) + Money.new(100) # => 31.00 €
3 * Money.new(100)               # =>  Money can't be coerced into Fixnum (TypeError)

Well, it turns out it doesn't, and the error we get is that Fixnum is trying to coerce the instance of Money but can't. How can we solve this?

Coercion

Enter coercion, more specifically type coercion. As per the Wikipedia definition, type coercion is:

In computer science, type conversion, typecasting, and coercion are different

ways of, implicitly or explicitly, changing an entity of one data type into another.

Plainly put it's a way of taking a data type and converting it to another, usually when this is done implicitly by the compiler or interpreter of a language. If it is done explicitly by the programmer it's usually referred to as conversion or casting.

Getting back to our example, what this means is that we need to let Fixnum know how to interact with Money to perform these operations. That is done by implementing the coerce method.

class Money
  ...

  def coerce(num)
    [self, num]
  end
end

In this situation, num represents the Fixnum and we're telling it that in order to call *, it need to reverse the order of the operands, so in reality when you call 3 * Money, it's being interpreted as Money * 3, which we know works. So, if we re-run the examples everything should work.

Money.new(3000) + Money.new(100) # => 31.00 €
3 * Money.new(100)               # =>  3.00 €

The coerce method

Now that you know what it does, a little more on how it does it. In Ruby, the coerce method should always return an array, in which the first element is an object that knows how to do the operation on the argument, and the second is the argument itself. For both the object and the argument, you can substitute them with equivalents that do know how to perform the operations. For instance, you can cast a String into a Fixnum in order to perform the operation.

For Numeric objects Ruby calls for this coercion if it cannot do anything else to try and accomplish the operation. However, you can call for other classes to have coerce implemented in order to interact with your class.

def *(arg)
  if (arg is not recognized)
    self_equiv, arg_equiv = arg.coerce(self)
    self_equiv * arg_equiv
  end
end

There are a lot of examples of places where coerce is used, in the Real World™:

This is probably not a problem you will face very often, but when you do, it's a nice tool to have at hand and understand.

More Ruby Bits

If you've enjoyed this Ruby Bit you should really subscribe to our newsletter, where other Ruby Bits and more great articles are shared every week.

If you want to meet us and/or see some talks on Ruby topics, come join us at RubyConf Portugal 2016. For making all the way to the end of this article you get 25% off the price of the ticket.