coerce未実装のクラスと数値を演算すると TypeError になる
自作クラスのインスタンスを 数値 + 自作オブジェクト の順で演算すると、Integer側の+が呼ばれます。Integerは自作クラスの値をどう数値として扱えばよいか分からないため、そのクラスにcoerceメソッドが定義されていない限りTypeErrorになります。
エラーメッセージの読み方
main.rb:11:in `+': Money can't be coerced into Integer (TypeError)
main.rb- ファイル名
11- 行番号 — 実際にクラッシュした行
+- 発生場所 — <main> ならトップレベル、メソッド名ならそのメソッドの中
TypeError- 例外クラス — 何が起きたか。ここを検索するのが最短です
Money can't be coerced into Integer- 詳細メッセージ — どの値が問題だったか
このエラーが出る典型パターン
パターン1
1 class Money 2 attr_reader :amount 3 def initialize(amount) 4 @amount = amount 5 end 6 def +(other) 7 Money.new(amount + other.amount) 8 end 9 end 10 11 total = 5 + Money.new(50) ^ 12 puts total.amount
数値を左に置いた演算はInteger側の+が呼ばれます。Moneyにcoerceを実装していない限り、数値がMoneyをどう扱えばよいか分かりません。
直し方: 5 + Money.new(50) を Money.new(50) + Money.new(5) にします。
パターン2
1 class Weight 2 attr_reader :grams 3 def initialize(grams) 4 @grams = grams 5 end 6 def +(other) 7 Weight.new(grams + other.grams) 8 end 9 end 10 11 total = 5 + Weight.new(50) ^ 12 puts total.grams
自作クラスに+を定義しても、それが呼ばれるのはレシーバが自作クラス側にある場合だけです。
直し方: 5 + Weight.new(50) を Weight.new(50) + Weight.new(5) にします。
パターン3
1 class Length 2 attr_reader :cm 3 def initialize(cm) 4 @cm = cm 5 end 6 def +(other) 7 Length.new(cm + other.cm) 8 end 9 end 10 11 total = 5 + Length.new(50) ^ 12 puts total.cm
数値を左に置いた式でも自作クラスの計算を成立させたい場合は、coerceメソッドの実装が必要です。
直し方: 5 + Length.new(50) を Length.new(50) + Length.new(5) にします。
パターン4
1 class Volume 2 attr_reader :ml 3 def initialize(ml) 4 @ml = ml 5 end 6 def +(other) 7 Volume.new(ml + other.ml) 8 end 9 end 10 11 total = 5 + Volume.new(50) ^ 12 puts total.ml
演算の左側のオブジェクトが優先されるというRubyの規則を知らないと、この順序依存のエラーは謎に見えます。
直し方: 5 + Volume.new(50) を Volume.new(50) + Volume.new(5) にします。
パターン5
1 class Score 2 attr_reader :points 3 def initialize(points) 4 @points = points 5 end 6 def +(other) 7 Score.new(points + other.points) 8 end 9 end 10 11 total = 5 + Score.new(50) ^ 12 puts total.points
自作クラスを数値と組み合わせる演算は、どちらを左に置くかで呼ばれるメソッドが変わることを意識する必要があります。
直し方: 5 + Score.new(50) を Score.new(50) + Score.new(5) にします。
よくある誤解
「自作クラスに+を定義しておけば、数値を左に置いた演算でもそれが呼ばれるはず」という思い込みは誤りです。演算子は左側のオブジェクトのメソッドが優先されるため、数値を左に置いた場合は自作クラス側ではなくInteger側の+が呼ばれ、coerceによる変換手順を用意しない限り失敗します。
まとめ
coerce未実装のクラスと数値を演算すると TypeError になるは上級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。