前回の振り返り
前回は、数値を表すNumberExprとダイスを表すDiceExprを作りました。どちらもevalメソッドを持つ「終端式」です。
今回は、「2d6+3」のような足し算を表現するためのAddExprクラスを作ります。
足し算をオブジェクトで表現する
足し算には、左辺(left)と右辺(right)があります。「2d6+3」なら、左辺が「2d6」、右辺が「3」です。

graph TD
A[AddExpr +] --> B[DiceExpr 2d6]
A --> C[NumberExpr 3]
この構造をクラスで表現します。
| |
AddExprのevalメソッドは、左辺と右辺それぞれのevalを呼び出し、結果を足して返します。
実際に使ってみる
「2d6+3」を表現してみましょう。
| |
実行するたびに異なる結果が得られます。
| |
非終端式という考え方
AddExprは、他の式(left、right)を内部に持っています。evalを呼ぶと、まず子の式を評価し、その結果を使って自分の値を計算します。
このような式を「非終端式(NonterminalExpression)」と呼びます。
classDiagram
class Expression {
<<interface>>
+eval(): Int
}
class NumberExpr {
+value: Int
+eval(): Int
}
class DiceExpr {
+count: Int
+sides: Int
+eval(): Int
}
class AddExpr {
+left: Expression
+right: Expression
+eval(): Int
}
Expression <|.. NumberExpr
Expression <|.. DiceExpr
Expression <|.. AddExpr
AddExpr o-- Expression : left
AddExpr o-- Expression : right
- 終端式: それ自体で値が決まる(NumberExpr、DiceExpr)
- 非終端式: 子の式を組み合わせて値を決める(AddExpr)
入れ子の式も表現できる
非終端式の素晴らしいところは、入れ子にもできることです。
「1d6+2d8+5」を表現してみましょう。
| |
このように、式をツリー構造で表現できます。
graph TD
A[AddExpr +] --> B[AddExpr +]
A --> C[NumberExpr 5]
B --> D[DiceExpr 1d6]
B --> E[DiceExpr 2d8]
evalメソッドが呼ばれると、ツリーを再帰的に辿って評価が行われます。
再帰的な評価の流れ
「1d6+2d8+5」の評価がどう進むか見てみましょう。

- 最上位のAddExpr.evalが呼ばれる
- left(内側のAddExpr)のevalを呼ぶ
- その中でleft(1d6)のevalを呼ぶ → 例: 4
- right(2d8)のevalを呼ぶ → 例: 12
- 4 + 12 = 16を返す
- right(5)のevalを呼ぶ → 5
- 16 + 5 = 21を返す
どんなに複雑な式でも、同じパターンで評価できます。
今回のまとめ
今回は、足し算を表すAddExprクラスを作りました。
- AddExprは左辺と右辺の式を持つ
- evalメソッドは子の式を再帰的に評価して加算
- これが「非終端式」という概念
次回は、すべての式クラスが共通のインターフェースを持つよう、Moo::Roleを使って整理します。
