ExampleChap7

「ExampleChap7」の編集履歴(バックアップ)一覧はこちら

ExampleChap7」(2011/02/24 (木) 08:38:55) の最新版変更点

追加された行は緑色になります。

削除された行は赤色になります。

#co(){ Chapter 7 Case Classes and Pattern Matching Say, we want to write an interpreter for arithmetic expressions. To keep things simple initially, we restrict ourselves to just numbers and + operations. Such expressions can be represented as a class hierarchy, with an abstract base class Expr as the root, and two subclasses Number and Sum. Then, an expression 1 + (3 + 7) would be represented as } * 第 7 章 ケースクラスとパターンマッチング ところで、数式のインタプリタを書きたいとしましょう。はじめは話を単純にするために、単に数と + 演算だけに制限します。そのような式はあるクラス階層、ルートの抽象基底クラス Expr と、2つのサブクラス Number と Sum を用いて表現できます。すると、式 1 + (3 + 7) は次のように表現されます。 new Sum(new Number(1), new Sum(new Number(3), new Number(7))) #co(){ Now, an evaluator of an expression like this needs to know of what form it is (either Sum or Number) and also needs to access the components of the expression. The following implementation provides all necessary methods. } さて、このような式評価器は、それがどの形式であるか (Sum か Number か) を知る必要があり、式の要素にアクセスする必要もあります。次は必要なメソッドすべての実装です。 abstract class Expr { def isNumber: Boolean def isSum: Boolean def numValue: Int def leftOp: Expr def rightOp: Expr } class Number(n: Int) extends Expr { def isNumber: Boolean = true def isSum: Boolean = false def numValue: Int = n def leftOp: Expr = error("Number.leftOp") def rightOp: Expr = error("Number.rightOp") } class Sum(e1: Expr, e2: Expr) extends Expr { def isNumber: Boolean = false def isSum: Boolean = true def numValue: Int = error("Sum.numValue") def leftOp: Expr = e1 def rightOp: Expr = e2 } #co(){ With these classification and access methods, writing an evaluator function is simple: } これらのクラス化とアクセスメソッドによって、評価器関数は簡単に書けます。 def eval(e: Expr): Int = { if (e.isNumber) e.numValue else if (e.isSum) eval(e.leftOp) + eval(e.rightOp) else error("unrecognized expression kind") } #co(){ However, defining all these methods in classes Sum and Number is rather tedious. Furthermore, the problem becomes worse when we want to add new forms of expressions. For instance, consider adding a new expression form Prod for products. Not only do we have to implement a new class Prod, with all previous classification and access methods; we also have to introduce a new abstract method isProduct in class Expr and implement that method in subclasses Number, Sum, and Prod. Having to modify existing code when a system grows is always problematic, since it introduces versioning and maintenance problems. } しかし、これらすべてのメソッドをクラス Sum と Number に定義するのは、かなり退屈です。さらに、式の新しい型を追加したくなった時に問題は悪化します。たとえば乗算のために新しい式の形式 Prod を追加することを考えてみましょう。既存のクラス化とアクセスメソッドに加えて、新しいクラス Prod を実装しなくてはならないだけではなく、クラス Expr に新しい抽象メソッド isPoduct を導入する必要があり、そのメソッドをサブクラス Number, Sum, Prod に実装する必要があります。システムを拡張する時に、既存コードを修正しなくてはならないのは昔からの問題です。なぜならバージョン化と保守の問題を引き起こすからです。 #co(){ The promise of object-oriented programming is that such modifications should be unnecessary, because they can be avoided by re-using existing, unmodified code through inheritance. Indeed, a more object-oriented decomposition of our problem solves the problem. The idea is to make the "high-level" operation eval a method of each expression class, instead of implementing it as a function outside the expression class hierarchy, as we have done before. Because eval is now a member of all expression nodes, all classification and access methods become superfluous, and the implementation is simplified considerably: } オブジェクト指向プログラミングの約束することは、「そのような修正は不要です。なぜなら、継承によって既存の未修整のコードを再利用できるから」というものです。実際、問題をよりオブジェクト指向的に分解すれば問題は解決します。そのアイデアは「ハイレベルな」操作である eval を、前に我々がやったように、式クラス階層の外の関数として実装するのではなく、それぞれの式クラスのメソッドにすることです。そうすれば、eval はすべての式ノードのメンバなので、クラス化とアクセスメソッドはすべて不要となり、実装はかなり簡単になります。 abstract class Expr { def eval: Int } class Number(n: Int) extends Expr { def eval: Int = n } class Sum(e1: Expr, e2: Expr) extends Expr { def eval: Int = e1.eval + e2.eval } #co(){ Furthermore, adding a new Prod class does not entail any changes to existing code: } さらに、新しい Prod クラスの追加は既存コードに何も変化を引き起こしません。 class Prod(e1: Expr, e2: Expr) extends Expr { def eval: Int = e1.eval * e2.eval } #co(){ The conclusion we can draw from this example is that object-oriented decomposition is the technique of choice for constructing systems that should be extensible with new types of data. But there is also another possible way we might want to extend the expression example. We might want to add new operations on expressions. For instance, we might want to add an operation that pretty-prints an expression tree to standard output. } この例から導かれる結論は、データ型の拡張可能なシステムを構築する際には、オブジェクト指向分解は選択すべきテクニックである、ということです。しかし他にも式の例を拡張したくなる方法があるかもしれません。式に対して新しい&bold(){操作}を追加したくなるかもしれません。たとえば、式の木を標準出力に整形して表示する操作を追加したくなるかもしれません。 #co(){ If we have defined all classification and access methods, such an operation can easily be written as an external function. Here is an example: } もしすべてのクラス化とアクセスメソッドを定義してあれば、そういった操作は簡単に外部の関数として書けます。こんな風にです。 def print(e: Expr) { if (e.isNumber) Console.print(e.numValue) else if (e.isSum) { Console.print("(") print(e.leftOp) Console.print("+") print(e.rightOp) Console.print(")") } else error("unrecognized expression kind") } #co(){ However, if we had opted for an object-oriented decomposition of expressions, we would need to add a new print procedure to each class: } しかし、オブジェクト指向分解を選んでいたなら、新しい手続き print を各クラスに追加する必要があるでしょう。 abstract class Expr { def eval: Int def print } class Number(n: Int) extends Expr { def eval: Int = n def print { Console.print(n) } } class Sum(e1: Expr, e2: Expr) extends Expr { def eval: Int = e1.eval + e2.eval def print { Console.print("(") print(e1) Console.print("+") print(e2) Console.print(")") } } #co(){ Hence, classical object-oriented decomposition requires modification of all existing classes when a system is extended with new operations. } したがって、システムに新しい操作を入れて拡張する時には、古典的なオブジェクト指向分解では、既存のすべてのクラスの修正が必要になります。 #co(){ As yet another way we might want to extend the interpreter, consider expression simplification. For instance, we might want to write a function which rewrites expressions of the form a * b + a * c to a * (b + c). This operation requires inspection of more than a single node of the expression tree at the same time. Hence, it cannot be implemented by a method in each expression kind, unless that method can also inspect other nodes. So we are forced to have classification and access methods in this case. This seems to bring us back to square one, with all the problems of verbosity and extensibility. } インタプリタの一つの拡張として、式を単純化したいとしましょう。たとえば、式の形式を a * b + a * c から a * (b + c) へ書き換える関数が欲しいとします。この操作のためには、一つ以上の式木のノードを同時に調べる必要があります。しかし、メソッドが他のノードを調べることができなければ、式の種類ごとのメソッドでは実装できません。ですからこの場合には、クラス化とアクセスメソッドを強いられます。冗長さと拡張性の問題に満ちた四角四面なやり方に逆戻りのようです。 #co(){ Taking a closer look, one observers that the only purpose of the classification and access functions is to reverse the data construction process. They let us determine, first, which sub-class of an abstract base class was used and, second, what were the constructor arguments. Since this situation is quite common, Scala has a way to automate it with case classes. } 詳しく調べてみると、クラス化とアクセス関数はデータの構成プロセスを&bold(){逆転}させるだけが目的だと分かります。それによって最初に、抽象クラスのどのサブクラスが使われたのか、その次にコンストラクタ引数が何であったのか、が決定されます。このような状況はかなり一般的なので、Scala にはそれをケースクラスによって自動化する方法があります。 - [[7.1 ケースクラスとケースオブジェクト >Example7.1]] - [[7.2 パターンマッチング >Example7.2]] #center(){[[前ページ>ExampleChap6]] [[ 7 章>ExampleChap7]] [[目次>ScalaByExample和訳]] [[次ページ>Example7.1]]} ---- - ちょっと意訳なのだと思いますが、「オブジェクト指向プログラミングの約束することは、そのような修正は不要です」を「オブジェクト指向プログラミングが約束するのは、そのような修正は不要になるということです」な感じはいかがでしょうか・・・ -- ryugate (2008-05-24 00:58:29) - そこまでやるんなら「そのような修正は不要であるとオブジェクト指向は約束してくれます」くらいまでやればいいじゃない -- 名無しさん (2008-07-07 17:26:36) #comment
#co(){ Chapter 7 Case Classes and Pattern Matching Say, we want to write an interpreter for arithmetic expressions. To keep things simple initially, we restrict ourselves to just numbers and + operations. Such expressions can be represented as a class hierarchy, with an abstract base class Expr as the root, and two subclasses Number and Sum. Then, an expression 1 + (3 + 7) would be represented as } #setmenu2(ex-r-menu) * 第 7 章 ケースクラスとパターンマッチング ところで、数式のインタプリタを書きたいとしましょう。はじめは話を単純にするために、単に数と + 演算だけに制限します。そのような式はあるクラス階層、ルートの抽象基底クラス Expr と、2つのサブクラス Number と Sum を用いて表現できます。すると、式 1 + (3 + 7) は次のように表現されます。 new Sum(new Number(1), new Sum(new Number(3), new Number(7))) #co(){ Now, an evaluator of an expression like this needs to know of what form it is (either Sum or Number) and also needs to access the components of the expression. The following implementation provides all necessary methods. } さて、このような式評価器は、それがどの形式であるか (Sum か Number か) を知る必要があり、式の要素にアクセスする必要もあります。次は必要なメソッドすべての実装です。 abstract class Expr { def isNumber: Boolean def isSum: Boolean def numValue: Int def leftOp: Expr def rightOp: Expr } class Number(n: Int) extends Expr { def isNumber: Boolean = true def isSum: Boolean = false def numValue: Int = n def leftOp: Expr = error("Number.leftOp") def rightOp: Expr = error("Number.rightOp") } class Sum(e1: Expr, e2: Expr) extends Expr { def isNumber: Boolean = false def isSum: Boolean = true def numValue: Int = error("Sum.numValue") def leftOp: Expr = e1 def rightOp: Expr = e2 } #co(){ With these classification and access methods, writing an evaluator function is simple: } これらのクラス化とアクセスメソッドによって、評価器関数は簡単に書けます。 def eval(e: Expr): Int = { if (e.isNumber) e.numValue else if (e.isSum) eval(e.leftOp) + eval(e.rightOp) else error("unrecognized expression kind") } #co(){ However, defining all these methods in classes Sum and Number is rather tedious. Furthermore, the problem becomes worse when we want to add new forms of expressions. For instance, consider adding a new expression form Prod for products. Not only do we have to implement a new class Prod, with all previous classification and access methods; we also have to introduce a new abstract method isProduct in class Expr and implement that method in subclasses Number, Sum, and Prod. Having to modify existing code when a system grows is always problematic, since it introduces versioning and maintenance problems. } しかし、これらすべてのメソッドをクラス Sum と Number に定義するのは、かなり退屈です。さらに、式の新しい型を追加したくなった時に問題は悪化します。たとえば乗算のために新しい式の形式 Prod を追加することを考えてみましょう。既存のクラス化とアクセスメソッドに加えて、新しいクラス Prod を実装しなくてはならないだけではなく、クラス Expr に新しい抽象メソッド isPoduct を導入する必要があり、そのメソッドをサブクラス Number, Sum, Prod に実装する必要があります。システムを拡張する時に、既存コードを修正しなくてはならないのは昔からの問題です。なぜならバージョン化と保守の問題を引き起こすからです。 #co(){ The promise of object-oriented programming is that such modifications should be unnecessary, because they can be avoided by re-using existing, unmodified code through inheritance. Indeed, a more object-oriented decomposition of our problem solves the problem. The idea is to make the "high-level" operation eval a method of each expression class, instead of implementing it as a function outside the expression class hierarchy, as we have done before. Because eval is now a member of all expression nodes, all classification and access methods become superfluous, and the implementation is simplified considerably: } オブジェクト指向プログラミングの約束することは、「そのような修正は不要です。なぜなら、継承によって既存の未修整のコードを再利用できるから」というものです。実際、問題をよりオブジェクト指向的に分解すれば問題は解決します。そのアイデアは「ハイレベルな」操作である eval を、前に我々がやったように、式クラス階層の外の関数として実装するのではなく、それぞれの式クラスのメソッドにすることです。そうすれば、eval はすべての式ノードのメンバなので、クラス化とアクセスメソッドはすべて不要となり、実装はかなり簡単になります。 abstract class Expr { def eval: Int } class Number(n: Int) extends Expr { def eval: Int = n } class Sum(e1: Expr, e2: Expr) extends Expr { def eval: Int = e1.eval + e2.eval } #co(){ Furthermore, adding a new Prod class does not entail any changes to existing code: } さらに、新しい Prod クラスの追加は既存コードに何も変化を引き起こしません。 class Prod(e1: Expr, e2: Expr) extends Expr { def eval: Int = e1.eval * e2.eval } #co(){ The conclusion we can draw from this example is that object-oriented decomposition is the technique of choice for constructing systems that should be extensible with new types of data. But there is also another possible way we might want to extend the expression example. We might want to add new operations on expressions. For instance, we might want to add an operation that pretty-prints an expression tree to standard output. } この例から導かれる結論は、データ型の拡張可能なシステムを構築する際には、オブジェクト指向分解は選択すべきテクニックである、ということです。しかし他にも式の例を拡張したくなる方法があるかもしれません。式に対して新しい&bold(){操作}を追加したくなるかもしれません。たとえば、式の木を標準出力に整形して表示する操作を追加したくなるかもしれません。 #co(){ If we have defined all classification and access methods, such an operation can easily be written as an external function. Here is an example: } もしすべてのクラス化とアクセスメソッドを定義してあれば、そういった操作は簡単に外部の関数として書けます。こんな風にです。 def print(e: Expr) { if (e.isNumber) Console.print(e.numValue) else if (e.isSum) { Console.print("(") print(e.leftOp) Console.print("+") print(e.rightOp) Console.print(")") } else error("unrecognized expression kind") } #co(){ However, if we had opted for an object-oriented decomposition of expressions, we would need to add a new print procedure to each class: } しかし、オブジェクト指向分解を選んでいたなら、新しい手続き print を各クラスに追加する必要があるでしょう。 abstract class Expr { def eval: Int def print } class Number(n: Int) extends Expr { def eval: Int = n def print { Console.print(n) } } class Sum(e1: Expr, e2: Expr) extends Expr { def eval: Int = e1.eval + e2.eval def print { Console.print("(") print(e1) Console.print("+") print(e2) Console.print(")") } } #co(){ Hence, classical object-oriented decomposition requires modification of all existing classes when a system is extended with new operations. } したがって、システムに新しい操作を入れて拡張する時には、古典的なオブジェクト指向分解では、既存のすべてのクラスの修正が必要になります。 #co(){ As yet another way we might want to extend the interpreter, consider expression simplification. For instance, we might want to write a function which rewrites expressions of the form a * b + a * c to a * (b + c). This operation requires inspection of more than a single node of the expression tree at the same time. Hence, it cannot be implemented by a method in each expression kind, unless that method can also inspect other nodes. So we are forced to have classification and access methods in this case. This seems to bring us back to square one, with all the problems of verbosity and extensibility. } インタプリタの一つの拡張として、式を単純化したいとしましょう。たとえば、式の形式を a * b + a * c から a * (b + c) へ書き換える関数が欲しいとします。この操作のためには、一つ以上の式木のノードを同時に調べる必要があります。しかし、メソッドが他のノードを調べることができなければ、式の種類ごとのメソッドでは実装できません。ですからこの場合には、クラス化とアクセスメソッドを強いられます。冗長さと拡張性の問題に満ちた四角四面なやり方に逆戻りのようです。 #co(){ Taking a closer look, one observers that the only purpose of the classification and access functions is to reverse the data construction process. They let us determine, first, which sub-class of an abstract base class was used and, second, what were the constructor arguments. Since this situation is quite common, Scala has a way to automate it with case classes. } 詳しく調べてみると、クラス化とアクセス関数はデータの構成プロセスを&bold(){逆転}させるだけが目的だと分かります。それによって最初に、抽象クラスのどのサブクラスが使われたのか、その次にコンストラクタ引数が何であったのか、が決定されます。このような状況はかなり一般的なので、Scala にはそれをケースクラスによって自動化する方法があります。 - [[7.1 ケースクラスとケースオブジェクト >Example7.1]] - [[7.2 パターンマッチング >Example7.2]] #center(){[[前ページ>ExampleChap6]] [[ 7 章>ExampleChap7]] [[目次>ScalaByExample和訳]] [[次ページ>Example7.1]]} ---- - ちょっと意訳なのだと思いますが、「オブジェクト指向プログラミングの約束することは、そのような修正は不要です」を「オブジェクト指向プログラミングが約束するのは、そのような修正は不要になるということです」な感じはいかがでしょうか・・・ -- ryugate (2008-05-24 00:58:29) - そこまでやるんなら「そのような修正は不要であるとオブジェクト指向は約束してくれます」くらいまでやればいいじゃない -- 名無しさん (2008-07-07 17:26:36) #comment

表示オプション

横に並べて表示:
変化行の前後のみ表示:
ツールボックス

下から選んでください:

新しいページを作成する
ヘルプ / FAQ もご覧ください。