In Progress
Unit 1, Lesson 1
In Progress

# Coercion Video transcript & code

Once again we will be working on our slowly evolving class for representing measurements in feet. As a quick review: so far our `Feet` class can represent a number of feet. It is immutable, like Ruby's core numeric classes. And `Feet` objects can compare themselves to other `Feet` objects to see if they are equivalent, greater than, or lesser.

Since the last episode I've also completed the `Feet` class' set of basic arithmetic methods. We can now add, subtract, multiply, and divide quantities of feet.

``````class Feet
include Comparable

def initialize(magnitude)
@magnitude = magnitude.to_f
freeze
end

def to_s
"#{@magnitude} feet"
end

def inspect
"#<Feet:#{@magnitude}>"
end

def +(other)
Feet.new(@magnitude + other.magnitude)
end

def -(other)
Feet.new(@magnitude - other.magnitude)
end

def *(other)
Feet.new(@magnitude * other.magnitude)
end

def /(other)
Feet.new(@magnitude / other.magnitude)
end

def <=>(other)
other.is_a?(Feet) && magnitude <=> other.magnitude
end

def hash
[magnitude, Feet].hash
end

alias_method :eql?, :==
end
``````

For most arithmetic operations, we would expect both values involved to be `Feet` objects. And in fact that was our justification for building this class in the first place: we wanted to make sure that `Feet` were never inadvertently mixed with other units of measure. So for most operations we are satisfied with the fact that the operation will only work if the values on both sides of the operator are `Feet` objects.

But multiplication is an interesting case. We might reasonably want to multiply a quantity of `Feet` by a raw number. For instance, if we have 23 spools of cable, each of which contains 50 feet of cable, what's the total length of the cable?

``````require "./feet"

Feet.new(50) * 23              # => #<Feet:1150.0>
``````

Somewhat shockingly, this works. As it turns out, it works entirely by accident. And it's not really a happy accident either.

All Ruby numeric objects have a `#magnitude` method. This method is an alias for the `#abs` method, which returns the absolute value of a number. So the magnitude of 42 is 42, and the magnitude of -42 is also 42.

``````42.magnitude                    # => 42
-42.magnitude                   # => 42
``````

By using the method name `#magnitude`, we've inadvertently set ourselves up for some potentially nasty defects down the road. Let's try multiplying our `Feet` object by a negative number:

``````require "./feet"

Feet.new(50) * -23              # => #<Feet:1150.0>
``````

We get the same answer as with the positive number, which is clearly not right.

Let's fix this with a case statement. We'll check the type of the other value. If it is numeric we will use that object as the multiplicand. If it is another `Feet` object we will use that object's magnitude. Otherwise, we'll raise a type error to indicate that we don't know what to do with the other object.

``````class Feet
include Comparable

def initialize(magnitude)
@magnitude = magnitude.to_f
freeze
end

def to_s
"#{@magnitude} feet"
end

def inspect
"#<Feet:#{@magnitude}>"
end

def +(other)
Feet.new(@magnitude + other.magnitude)
end

def -(other)
Feet.new(@magnitude - other.magnitude)
end

def *(other)
multiplicand = case other
when Numeric then other
when Feet then other.magnitude
else
raise TypeError, "Don't know how to multiply by #{other}"
end
Feet.new(@magnitude * multiplicand)
end

def /(other)
Feet.new(@magnitude / other.magnitude)
end

def <=>(other)
other.is_a?(Feet) && magnitude <=> other.magnitude
end

def hash
[magnitude, Feet].hash
end

alias_method :eql?, :==
end
``````

This version works much better. We can multiply by a positive integer, and see a result in `Feet`. We can multiply by a negative floating point number, and get the expected result. We can multiply by another `Feet` object. But when we try to multiply by a string, we get an error.

``````require "./feet2"

Feet.new(50) * 23               # => #<Feet:1150.0>
Feet.new(50) * -23.5            # => #<Feet:-1175.0>
Feet.new(50) * Feet.new(2)      # => #<Feet:100.0>
Feet.new(50) * "a hojillion"    # =>
# ~> /home/avdi/Dropbox/rubytapas/206-coercion/feet2.rb:32:in `*': Don't know how to multiply by a hojillion (TypeError)
# ~>    from -:6:in `<main>'
``````

So far, so good. But multiplication is also supposed to be commutative. That means we should be able to switch around the operands and get the same answer. Unfortunately, this is not true of our `Feet` objects. When we turn an operation around and put the raw numeric type first, we get an exception: "Feet can't be coerced into Fixnum"

``````require "./feet2"

23 * Feet.new(50)              # =>
# ~> -:3:in `*': Feet can't be coerced into Fixnum (TypeError)
# ~>    from -:3:in `<main>'
``````

In programming languages, the word "coercion" usually refers to the compiler or the runtime automatically converting from one numeric type to another. Now, as we've talked about before, Ruby is a language where types are almost never automatically converted to other types without our explicitly asking for it. So what is this talk of "coercion"?

Well, it turns out that Ruby makes some exceptions to its usual strictness about types when it comes to numbers.

Let's say we try to multiply the integer 10 times the ratio 2/3.

Integers know how to multiply themselves by other integers. And rationals know how to multiply themselves by other rationals. But how can Ruby multiply these two different types of number?

Here's how Ruby resolves the situation. Knowing that an integer can't multiply itself by a `Rational`, it takes the second operand. It asks that operand: "do you respond to the `#coerce` method?

If the answer is "yes", it sends the `#coerce` method to that second operand, with the first operand as its argument.

The `#coerce` method has a very specific responsibility: it must take the operand it is given, and find a data type which is compatible with both self and the other operand.

In the case of our `Rational` object and its integer multiplier, the solution it comes up with is to keep itself as a `Rational`, and convert the integer to a `Rational` as well. The result of `#coerce` is a two-element array, containing both converted operands in their original order.

Ruby takes these two converted operands, and then turns around and applies the original operator to them. The result is another `Rational` number.

All this goes on behind the scenes when we attempt to multiply an integer by a `Rational`.

``````require "rational"

10 * Rational(2, 3)
Rational(2, 3).respond_to?(:coerce) # => true
Rational(2, 3).coerce(10)           # => [(10/1), (2/3)]
Rational(10, 1) * Rational(2, 3)    # => (20/3)
10 * Rational(2, 3)                 # => (20/3)
``````

The cool thing about this mechanism is that we are free to hook our own classes into it. All we have to do is to provide the `#coerce` method.

We start our method by checking that the other operand is a `Numeric` type. We don't yet have a plan for converting other types, so we want to make sure to fail early if the type isn't recognized.

Then we construct a two-element array consisting of a new `Feet` object constructed from the numeric operand, and this `Feet` object in the second position.

``````class Feet
include Comparable

def initialize(magnitude)
@magnitude = magnitude.to_f
freeze
end

def to_s
"#{@magnitude} feet"
end

def inspect
"#<Feet:#{@magnitude}>"
end

def +(other)
Feet.new(@magnitude + other.magnitude)
end

def -(other)
raise TypeError unless other.is_a?(Feet)
Feet.new(@magnitude - other.magnitude)
end

def *(other)
multiplicand = case other
when Numeric then other
when Feet then other.magnitude
else
raise TypeError, "Don't know how to multiply by #{other}"
end
Feet.new(@magnitude * multiplicand)
end

def /(other)
Feet.new(@magnitude / other.magnitude)
end

def <=>(other)
other.is_a?(Feet) && magnitude <=> other.magnitude
end

def hash
[magnitude, Feet].hash
end

def coerce(other)
unless other.is_a?(Numeric)
raise TypeError, "Can't coerce #{other}"
end
[Feet.new(other), self]
end

alias_method :eql?, :==
end
``````

Now when we multiply an integer by `Feet`, we get the same result we got when the operands were in the opposite order. Our multiplication operation is now commutative..

``````require "./feet3"

23 * Feet.new(50)              # => #<Feet:1150.0>
``````

Unfortunately, our changes have some less than desirable side effects. Because Ruby automatically applies coercion regardless of which arithmetic operator is being used, we can now perform other arithmetic operations between numerics and `Feet`. This defeats our original intention of preventing this kind of mixing of core types and quantity types. If the "23" in this scenario represented a quantity of meters instead of feet, this bit of math would represent an undetected bug.

``````require "./feet3"

23 - Feet.new(15)                # => #<Feet:8.0>
``````

We're going to tackle this problem next. But first, a disclaimer: the solution we're about to go over is kind of ugly. If you're watching this and you have an idea for a better way to handle this, I'd love to hear from you.

We start off by introducing a new nested class expressly for this scenario, called `CoercedNumber`. This class exists solely to be a holder of raw numbers which are involved in coercion. It defines all of the arithmetic operations that `Feet` does, but the definitions of most of them simply raise a `TypeError`. Only the multiplication operation is allowed, and it is implemented by taking the operands, flipping them around, and delegating to the right-hand operand. Which will be a `Feet` object.

Then we update the `#coerce` method to return wrap the raw number operand in a `CoercedNumber` object before returning it as the first element of the array.

``````class Feet
include Comparable

CoercedNumber = Struct.new(:value) do
def +(other) raise TypeError; end
def -(other) raise TypeError; end
def *(other)
other * value
end
def /(other) raise TypeError; end
end

def initialize(magnitude)
@magnitude = magnitude.to_f
freeze
end

def to_s
"#{@magnitude} feet"
end

def inspect
"#<Feet:#{@magnitude}>"
end

def +(other)
Feet.new(@magnitude + other.magnitude)
end

def -(other)
raise TypeError unless other.is_a?(Feet)
Feet.new(@magnitude - other.magnitude)
end

def *(other)
multiplicand = case other
when Numeric then other
when Feet then other.magnitude
else
raise TypeError, "Don't know how to multiply by #{other}"
end
Feet.new(@magnitude * multiplicand)
end

def /(other)
Feet.new(@magnitude / other.magnitude)
end

def <=>(other)
other.is_a?(Feet) && magnitude <=> other.magnitude
end

def hash
[magnitude, Feet].hash
end

def coerce(other)
unless other.is_a?(Numeric)
raise TypeError, "Can't coerce #{other}"
end
[CoercedNumber.new(other), self]
end

alias_method :eql?, :==
end
``````

Here's the upshot of these changes. We can still multiply a `Feet` object by a raw integer. We can multiply a raw integer by a `Feet` object. But when we try to perform another operation—such as addition—on a raw integer and a `Feet` object, we get a `TypeError` thanks to our new `CoercedNumber` value holder.

``````Feet.new(50) * 23               # => #<Feet:1150.0>
23 * Feet.new(50)               # => #<Feet:1150.0>
23 + Feet.new(50)               # =>
# ~> -:5:in `+': TypeError (TypeError)
# ~>    from -:71:in `+'
# ~>    from -:71:in `<main>'
``````

Again, this solution is a bit of a kludge and if you have a better idea I'm all ears.

We still have one more loophole to close. Remember earlier, we saw that because core numeric objects happen to also support the `#magnitude` message, we can do things like add the integer negative five to 50 `Feet`. This not only goes against our intentions for the `Feet` class, but the answer that results is wrong.

``````require "./feet4"

Feet.new(50) + -5                # => #<Feet:55.0>
``````

To fix this, we go through the class, adding type checks to the other operators and raising `TypeError` unless the other operand is also a `Feet` object.

By the way, you might notice that I'm being lazy and not adding an error message to these raises. I don't normally omit the message. But on the other hand, this is a case where taking a quick look at the line of code the exception is raised from should make it immediately obvious what the problem is, so I don't feel too bad about it.

``````class Feet
include Comparable

CoercedNumber = Struct.new(:value) do
def +(other) raise TypeError; end
def -(other) raise TypeError; end
def *(other)
other * value
end
def /(other) raise TypeError; end
end

def initialize(magnitude)
@magnitude = magnitude.to_f
freeze
end

def to_s
"#{@magnitude} feet"
end

def inspect
"#<Feet:#{@magnitude}>"
end

def +(other)
raise TypeError unless other.is_a?(Feet)
Feet.new(@magnitude + other.magnitude)
end

def -(other)
raise TypeError unless other.is_a?(Feet)
Feet.new(@magnitude - other.magnitude)
end

def *(other)
multiplicand = case other
when Numeric then other
when Feet then other.magnitude
else
raise TypeError, "Don't know how to multiply by #{other}"
end
Feet.new(@magnitude * multiplicand)
end

def /(other)
raise TypeError unless other.is_a?(Feet)
Feet.new(@magnitude / other.magnitude)
end

def <=>(other)
raise TypeError unless other.is_a?(Feet)
other.is_a?(Feet) && magnitude <=> other.magnitude
end

def hash
[magnitude, Feet].hash
end

def coerce(other)
unless other.is_a?(Numeric)
raise TypeError, "Can't coerce #{other}"
end
[CoercedNumber.new(other), self]
end

alias_method :eql?, :==
end
``````

Today we've learned that controlling when number-like objects are and are not automatically converted to other number-like objects is a tricky business. But we've also learned that thanks to Ruby's coercion mechanism, with a little thought and ingenuity it's possible to make our own objects work almost exactly like Ruby's built-in numeric types. Which, if we are hoping to perform all of our application math with objects that model real-world quantities, is a very good thing.

Editor's Note: An essential discussion of multiplying Feet by Feet is found in Episode #225.