ExampleChap6

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

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

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

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

#co(){ Chapter 6 Classes and Objects Scala does not have a built-in type of rational numbers, but it is easy to define one, using a class. Here's a possible implementation. } * 第 6 章 クラスとオブジェクト Scala には組み込みの有理数型はありませんが、クラスを使って簡単に定義できます。次が実装例です。 class Rational(n: Int, d: Int) { private def gcd(x: Int, y: Int): Int = { if (x == 0) y else if (x < 0) gcd(-x, y) else if (y < 0) -gcd(x, -y) else gcd(y % x, x) } private val g = gcd(n, d) val numer: Int = n/g val denom: Int = d/g def +(that: Rational) = new Rational(numer * that.denom + that.numer * denom, denom * that.denom) def -(that: Rational) = new Rational(numer * that.denom - that.numer * denom, denom * that.denom) def *(that: Rational) = new Rational(numer * that.numer, denom * that.denom) def /(that: Rational) = new Rational(numer * that.denom, denom * that.numer) } #co(){ This defines Rational as a class which takes two constructor arguments n and d, containing the number's numerator and denominator parts. The class provides fields which return these parts as well as methods for arithmetic over rational numbers. Each arithmetic method takes as parameter the right operand of the operation. The left operand of the operation is always the rational number of which the method is a member. } 有理数を、分子 n と分母 d の2つのコンストラクタ引数をとるクラスとして定義しています。このクラスは有理数上の演算メソッドだけではなく、分子と分母を返すフィールドも提供します。各演算メソッドは操作の右オペランドを引数にとります。操作の左オペランドは、常にそのメソッドがメンバであるような有理数です。 #co(){ Private members. The implementation of rational numbers defines a private method gcd which computes the greatest common denominator of two integers, as well as a private field g which contains the gcd of the constructor arguments. These members are inaccessible outside class Rational. They are used in the implementation of the class to eliminate common factors in the constructor arguments in order to ensure that numerator and denominator are always in normalized form. } &b(){プライベート・メンバ } 有理数の実装では、2つの整数の最大公約数を計算するプライベートなメソッド gcd を定義し、また、コンストラクタ引数の gcd を格納するプライベートなフィールド g も定義します。これらのメンバはクラス Rational の外からはアクセスできません。それらはクラスの実装において、コンストラクタ引数を公約数で割って、分子と分母を常に正規形(約分された形)にするために使います。 #co(){ Creating and Accessing Objects. As an example of how rational numbers can be used, here's a program that prints the sum of all numbers 1/i where i ranges from 1 to 10. } &b(){オブジェクト生成とアクセス } 有理数の使い方の例として、i の範囲が 1 から 10 まで時に、1/i の和を印刷するプログラムを示します。 var i = 1 var x = new Rational(0, 1) while (i <= 10) { x += new Rational(1, i) i += 1 } println("" + x.numer + "/" + x.denom) #co(){ The + takes as left operand a string and as right operand a value of arbitrary type. It returns the result of converting its right operand to a string and appending it to its left operand. } ~+ は左オペランドに文字列を、右オペランドに任意の型の値をとります。右オペランドを文字列に変換し、それを左オペランドに追加した結果を返します。 #co(){ Inheritance and Overriding. Every class in Scala has a superclass which it extends. If a class does not mention a superclass in its definition, the root type scala.AnyRef is implicitly assumed (for Java implementations, this type is an alias for java.lang.Object. For instance, class Rational could equivalently be defined as } &b(){継承とオーバーライド } Scala のすべてのクラスは親クラスを持ち、それを継承しています。クラス定義の中で親クラスが指定されていない場合、ルート型 scala.AnyRef が暗黙のうちに仮定されます (Java の実装でいうと、この型は java.lang.Object のエイリアスです)。 たとえばクラス Rational を次のように定義しても同じことです。 class Rational(n: Int, d: Int) extends AnyRef { ... // as before } #co(){ A class inherits all members from its superclass. It may also redefine (or: override) some inherited members. For instance, class java.lang.Object defines a method toString which returns a representation of the object as a string: } クラスは親クラスのメンバをすべて継承します。継承したメンバを再定義 (つまり、&bold(){オーバーライド}) できます。たとえばクラス java.lang.Object は、オブジェクトの文字列表現を返すメソッド toString を定義しています。 class Object { ... def toString: String = ... } #co(){ The implementation of toString in Object forms a string consisting of the object's class name and a number. It makes sense to redefine this method for objects that are rational numbers: } Object での toString は、オブジェクトのクラス名と数からなる文字列として実装されています。このメソッドを有理数のオブジェクト用に再定義すると役に立ちます。 class Rational(n: Int, d: Int) extends AnyRef { ... // as before override def toString = "" + numer + "/" + denom } #co(){ Note that, unlike in Java, redefining definitions need to be preceded by an override modifier. If class A extends class B , then objects of type A may be used wherever objects of type B are expected. We say in this case that type A conforms to type B . For instance, Rational conforms to AnyRef, so it is legal to assign a Rational value to a variable of type AnyRef: } 注意すべき点は、Java とは異なり、定義の再定義では手前に override 修飾子が必要なことです。 もしクラス A がクラス B を継承しているなら、型 A のオブジェクトは、型 B のオブジェクトが期待される所ではどこでも使用できます。このような場合、型 A は型 B に&bold(){適合する}(conform)、といいます。たとえば Rational は AnyRef に適合します。したがって Rational 値を AnyRef 型の変数に代入できます。 var x: AnyRef = new Rational(1, 2) #co(){ Parameterless Methods. Unlike in Java, methods in Scala do not necessarily take a parameter list. An example is the square method below. This method is invoked by simply mentioning its name. } &b(){引数なしのメソッド } Java とは異なり、Scala のメソッドは引数リストを必ずしも必要としません。次の square メソッドがその例です。このメソッドは単に名前を書くだけで呼び出されます。 class Rational(n: Int, d: Int) extends AnyRef { ... // as before def square = new Rational(numer*numer, denom*denom) } val r = new Rational(3, 4) println(r.square) // prints''9/16''* #co(){ That is, parameterless methods are accessed just as value fields such as numer are. The difference between values and parameterless methods lies in their definition. The right-hand side of a value is evaluated when the object is created, and the value does not change afterwards. A right-hand side of a parameterless method, on the other hand, is evaluated each time the method is called. The uniform access of fields and parameterless methods gives increased flexibility for the implementer of a class. Often, a field in one version of a class becomes a computed value in the next version. Uniform access ensures that clients do not have to be rewritten because of that change. } これは、引数なしメソッドは numer のような値フィールドと同じようにアクセスされるということです。値と引数なしメソッドの違いはそれらの定義にあります。値の右辺はオブジェクトが作成された時に評価され、以後は値が変化しません。その一方で、引数なしメソッドの右辺はメソッドが呼ばれる度に評価されます。フィールドと引数なしメソッドのアクセス方法が統一されているのでクラス実装者の自由度が増します。しばしば、あるバージョンではフィールドだったものが次のバージョンでは計算された値だったりします。統一的なアクセスのおかげで、クライアントは変更による書き直しが不要になります。 #co(){ Abstract Classes. Consider the task of writing a class for sets of integer numbers with two operations, incl and contains. (s incl x) should return a new set which contains the element x together with all the elements of set s. (s contains x) should return true if the set s contains the element x, and should return false otherwise. The interface of such sets is given by: } &b(){抽象クラス } 整数の集合に対して、2つの操作 incl と contains を持つクラスを書く課題について考えてみましょう。(s incl x) は集合 s の要素すべてと、要素 x を持つ新しい集合を返すべきです。(s contains x) は、集合 s が要素 x を含む時は true を、さもなければ false を返すべきです。そのような集合のインターフェイスは次のようになります。 abstract class IntSet { def incl(x: Int): IntSet def contains(x: Int): Boolean } #co(){ IntSet is labeled as an abstract class. This has two consequences. First, abstract classes may have deferred members which are declared but which do not have an implementation. In our case, both incl and contains are such members. Second, because an abstract class might have unimplemented members, no objects of that class may be created using new. By contrast, an abstract class may be used as a base class of some other class, which implements the deferred members. } IntSet は&bold(){抽象クラス}と分類されます。これは2つの結果をもたらします。一つは、抽象クラスは&bold(){延期された}メンバを持ち、それらは宣言はあっても実装はありません。この場合、incl と contains の両方がそのようなメンバです。二つ目は、抽象クラスには未実装のメンバがあるかもしれないので、そのクラスのオブジェクトを new で生成できません。その一方で、抽象クラスは他のクラスの基底クラスとして使うことができ、継承したクラスでは延期されたメンバを実装します。 #co(){ Traits. Instead of abstract class one also often uses the keyword trait in Scala. Traits are abstract classes that are meant to be added to some other class. This might be because a trait adds some methods or fields to an unknown parent class. For instance, a trait Bordered might be used to add a border to a various graphical components. Another usage scenario is where the trait collects signatures of some functionality provided by different classes, much in the way a Java interface would work. Since IntSet falls in this category, one can alternatively define it as a trait: } &b(){トレイト } Scala では、abstract class の代わりにキーワード trait をよく使います。トレイトは、他のクラスに付け加えるための抽象クラスです。これはトレイトが未知の親クラスにメソッドやフィールドをいくつか追加するからです。たとえば、トレイト Bordered は様々なグラフィカルコンポーネントに縁を追加するのに使われるでしょう。別の使い方としては、様々なクラスによって提供される機能のシグネチャをトレイトが集約する、という Java のインターフェイスがするようなやり方です。 IntSet はこの部類に入るので、トレイトとしても定義できます。 trait IntSet { def incl(x: Int): IntSet def contains(x: Int): Boolean } #co(){ Implementing Abstract Classes. Let's say, we plan to implement sets as binary trees. There are two possible forms of trees. A tree for the empty set, and a tree consisting of an integer and two subtrees. Here are their implementations. } &b(){抽象クラスの実装 } たとえば集合を二分木で実装することにしましょう。木の形には2つの可能性があります。空集合を表す木と、整数1つと2つの部分木からなる木です。次がその実装です。 class EmptySet extends IntSet { def contains(x: Int): Boolean = false def incl(x: Int): IntSet = new NonEmptySet(x, new EmptySet, new EmptySet) } class NonEmptySet(elem: Int, left: IntSet, right: IntSet) extends IntSet { def contains(x: Int): Boolean = if (x < elem) left contains x else if (x > elem) right contains x else true def incl(x: Int): IntSet = if (x < elem) new NonEmptySet(elem, left incl x, right) else if (x > elem) new NonEmptySet(elem, left, right incl x) else this } #co(){ Both EmptySet and NonEmptySet extend class IntSet. This implies that types EmptySet and NonEmptySet conform to type IntSet - a value of type EmptySet or NonEmptySet may be used wherever a value of type IntSet is required. } EmptySet と NonEmptySet の両方とも IntSet を継承します。これは、型 EmptySet と NonEmptySet が型 IntSet に適合すること --- 型 EmptySet や NonEmptySet の値は、型 IntSet の値が必要なところではどこでも使えることを意味します。 #co(){ Exercise 6.0.1 Write methods union and intersection to form the union and intersection between two sets. Exercise 6.0.2 Add a method def excl(x: Int) to return the given set without the element x. To accomplish this, it is useful to also implement a test method def isEmpty: Boolean for sets. } &b(){演習 6.0.1 } メソッド union と intersection を2つの集合の結びと交わりとなるように書きなさい。 &b(){演習 6.0.2 } 要素 x を取り除いた集合を返すメソッド def excl(x: Int) を追加しなさい。そうするために便利なテスト用メソッド def isEmpty: Boolean も、集合に対して実装しなさい。 #co(){ Dynamic Binding. Object-oriented languages (Scala included) use dynamic dispatch for method invocations. That is, the code invoked for a method call depends on the run-time type of the object which contains the method. For example, consider the expression s contains 7 where s is a value of declared type s: IntSet. Which code for contains is executed depends on the type of value of s at run-time. If it is an EmptySet value, it is the implementation of contains in class EmptySet that is executed, and analogously for NonEmptySet values. This behavior is a direct consequence of our substitution model of evaluation. For instance, (new EmptySet).contains(7) -> (by replacing contains by its body in class EmptySet) false Or, new NonEmptySet(7, new EmptySet, new EmptySet).contains(1) -> (by replacing contains by its body in class NonEmptySet) if (1 < 7) new EmptySet contains 1 else if (1 > 7) new EmptySet contains 1 else true -> (by rewriting the conditional) new EmptySet contains 1 -> (by replacing contains by its body in class EmptySet) false . } &b(){動的束縛 } (Scala を含む) オブジェクト指向言語はメソッド呼び出しのときに&bold(){動的ディスパッチ}を用います。これは、メソッド呼び出しで起動されるコードは、メソッドを含むオブジェクトの実行時型に依存するということです。たとえば式 s contains 7、ただし s は宣言された型 s: IntSet の値だとします。もしそれが EmptySet の値なら、実行されるのはクラス EmptySet の contains の実装であり、NonEmptySet の値の場合も同様です。この振る舞いは評価の置き換えモデルの直接的な帰結です。たとえば (new EmptySet).contains(7) -> (クラス EmptySet の contains 本体で置き換えることで) false あるいは new NonEmptySet(7, new EmptySet, new EmptySet).contains(1) -> (クラス NonEmptySet の contains 本体で置き換えることで) if (1 < 7) new EmptySet contains 1 else if (1 > 7) new EmptySet contains 1 else true -> (条件式を書き換えて) new EmptySet contains 1 -> (クラス EmptySet の contains 本体で置き換えることで) false . #co(){ Dynamic method dispatch is analogous to higher-order function calls. In both cases, the identity of code to be executed is known only at run-time. This similarity is not just superficial. Indeed, Scala represents every function value as an object (see Section 8.6). } 動的メソッドディスパッチは高階関数呼び出しと似ています。どちらの場合にも、実行されるコードの正体は実行時にしか分かりません。この類似性は表面上のものにとどまりません。実際、Scala はすべての関数値をオブジェクトとして表現しています (8.6 節参照)。 #co(){ Objects. In the previous implementation of integer sets, empty sets were expressed with new EmptySet; so a new object was created every time an empty set value was required. We could have avoided unnecessary object creations by defining a value empty once and then using this value instead of every occurrence of new EmptySet. For example: } &b(){オブジェクト } 整数集合の以前の実装では、空集合は new EmptySet で表現されていました。つまり空集合値が必要になる度に、毎回新しいオブジェクトが生成されました。値 empty を一度だけ定義して、その値をすべての new EmptySet の出現の代わりに使えば、不必要なオブジェクト生成を避けることができます。たとえば val EmptySetVal = new EmptySet #co(){ One problem with this approach is that a value definition such as the one above is not a legal top-level definition in Scala; it has to be part of another class or object. Also, the definition of class EmptySet now seems a bit of an overkill - why define a class of objects, if we are only interested in a single object of this class? A more direct approach is to use an object definition. Here is a more streamlined alternative definition of the empty set: } この方法の問題の一つは、このような値定義は Scala の正しいトップレベルでの定義ではない、ということです。これは別のクラスかオブジェクトの一部でなくてはなりません。またクラス EmptySet の定義は少しばかりやり過ぎています。もしたった一つのオブジェクトにだけ関心があるなら、なぜオブジェクト用のクラスを定義するのでしょう? より直接的な方法は&bold(){オブジェクト定義}を使うことです。代わりの、より洗練された空集合の定義は次のようになります。 object EmptySet extends IntSet { def contains(x: Int): Boolean = false def incl(x: Int): IntSet = new NonEmptySet(x, EmptySet, EmptySet) } #co(){ The syntax of an object definition follows the syntax of a class definition; it has an optional extends clause as well as an optional body. As is the case for classes, the extends clause defines inherited members of the object whereas the body defines overriding or new members. However, an object definition defines a single object only it is not possible to create other objects with the same structure using new. Therefore, object definitions also lack constructor parameters, which might be present in class definitions. } オブジェクト定義の構文はクラス定義の構文に従います。オプションの extends 節とオプションの本体があります。クラスの場合のように、extends 節はオブジェクトが継承するメンバを定義し、一方で本体はオーバーライドするあるいは新規のメンバを定義します。しかしオブジェクト定義はただ1つのオブジェクトだけを定義するため、同じ構造を持った他のオブジェクトを new で生成できません。したがってオブジェクト定義には、クラス定義にはあるかもしれないコンストラクタ引数はありません。 #co(){ Object definitions can appear anywhere in a Scala program; including at top-level. Since there is no fixed execution order of top-level entities in Scala, one might ask exactly when the object defined by an object definition is created and initialized. The answer is that the object is created the first time one of its members is accessed. This strategy is called lazy evaluation. } オブジェクト定義は Scala プログラムのどこにでも、トップレベルにも、書けます。Scala ではトップレベルのエンティティの実行順序は固定されていないため、オブジェクト定義によって定義されたオブジェクトがいつ生成され初期化されるのか、正確に知りたい人もいるでしょう。その答えは、オブジェクトはそのメンバが最初にアクセスされる時に生成される、というものです。この戦略は&bold(){遅延評価}と呼ばれています。 #co(){ Standard Classes. Scala is a pure object-oriented language. This means that every value in Scala can be regarded as an object. In fact, even primitive types such as int or boolean are not treated specially. They are defined as type aliases of Scala classes in module Predef: } &b(){標準クラス } Scala は純粋なオブジェクト指向言語です。これは Scala のすべての値がオブジェクトである、ということです。実際 int や boolean のようなプリミティブ型でさえ特別扱いされません。それらはモジュール Predef において、Scala クラスの型エイリアスとして定義されています。 type boolean = scala.Boolean type int = scala.Int type long = scala.Long ... #co(){ For efficiency, the compiler usually represents values of type scala.Int by 32 bit integers, values of type scala.Boolean by Java's booleans, etc. But it converts these specialized representations to objects when required, for instance when a primitive Int value is passed to a function with a parameter of type AnyRef. Hence, the special representation of primitive values is just an optimization, it does not change the meaning of a program. Here is a specification of class Boolean. } コンパイラは効率化のために通常、scala.Int 型の値を 32 bit 整数で、scala.Boolean 型の値を Java の boolean で、等と表現します。しかし必要に応じてこれらの特別な表現をオブジェクトに変換します。たとえばプリミティブの Int 値が AnyRef 型の引数をとる関数に渡される時です。したがってプリミティブ値の特別な表現は単に最適化のためであり、プログラムの意味を変えません。 次がクラス Boolean の仕様です。 package scala abstract class Boolean { def && (x: => Boolean): Boolean def || (x: => Boolean): Boolean def ! : Boolean def == (x: Boolean) : Boolean def != (x: Boolean) : Boolean def < (x: Boolean) : Boolean def > (x: Boolean) : Boolean def <= (x: Boolean) : Boolean def >= (x: Boolean) : Boolean } #co(){ Booleans can be defined using only classes and objects, without reference to a built-in type of booleans or numbers. A possible implementation of class Boolean is given below. This is not the actual implementation in the standard Scala library. For efficiency reasons the standard implementation uses built-in booleans. } Boolean はクラスとオブジェクトだけを使って定義でき、組み込みのブール値や数値の型を参照する必要はありません。Boolean 型の一つの実装を次に示します。これは標準 Scala ライブラリの実際の実装ではありません。効率化のために標準の実装では、組み込みのブール値を使います。 package scala abstract class Boolean { def ifThenElse(thenpart: => Boolean, elsepart: => Boolean) def && (x: => Boolean): Boolean = ifThenElse(x, false) def || (x: => Boolean): Boolean = ifThenElse(true, x) def ! : Boolean = ifThenElse(false, true) def == (x: Boolean) : Boolean = ifThenElse(x, x.!) def != (x: Boolean) : Boolean = ifThenElse(x.!, x) def < (x: Boolean) : Boolean = ifThenElse(false, x) def > (x: Boolean) : Boolean = ifThenElse(x.!, false) def <= (x: Boolean) : Boolean = ifThenElse(x, true) def >= (x: Boolean) : Boolean = ifThenElse(true, x.!) } case object True extends Boolean { def ifThenElse(t: => Boolean, e: => Boolean) = t } case object False extends Boolean { def ifThenElse(t: => Boolean, e: => Boolean) = e } #co(){ Here is a partial specification of class Int. } 次はクラス Int の仕様の一部です。 package scala abstract class Int extends AnyVal { def toLong: Long def toFloat: Float def toDouble: Double def + (that: Double): Double def + (that: Float): Float def + (that: Long): Long def + (that: Int): Int // analogous for -, *, /, % def << (cnt: Int): Int // analogous for >>, >>> def & (that: Long): Long def & (that: Int): Int // analogous for |, ^ def == (that: Double): Boolean def == (that: Float): Boolean def == (that: Long): Boolean // analogous for !=, <, >, <=, >= } #co(){ Class Int can in principle also be implemented using just objects and classes, without reference to a built in type of integers. To see how, we consider a slightly simpler problem, namely how to implement a type Nat of natural (i.e. non-negative) numbers. Here is the definition of an abstract class Nat:} クラス Int も原理的にはオブジェクトとクラスだけで、組み込みの整数型を参照することなく実装できます。その方法を知るために、少しだけ簡単な問題、自然数を表す型 Nat の実装方法を考えみましょう。次は抽象クラス Nat の定義です。 abstract class Nat { def isZero: Boolean def predecessor: Nat def successor: Nat def + (that: Nat): Nat def - (that: Nat): Nat } #co(){ To implement the operations of class Nat, we define a sub-object Zero and a subclass Succ (for successor). Each number N is represented as N applications of the Succ constructor to Zero: } クラス Nat の操作を実装するために、子オブジェクト Zero と子クラス Succ (次の数) を定義します。それぞれの数 N は、Zero に Succ コンストラクタを N 回適用したものです。 new Succ( ... new Succ(Zero) ... ) <------- N 回 -------> #co(){ The implementation of the Zero object is straightforward: } オブジェクト Zero の定義はそのままです。 object Zero extends Nat { def isZero: Boolean = true def predecessor: Nat = error("negative number") def successor: Nat = new Succ(Zero) def + (that: Nat): Nat = that def - (that: Nat): Nat = if (that.isZero) Zero else error("negative number") } #co(){ The implementation of the predecessor and subtraction functions on Zero throws an Error exception, which aborts the program with the given error message. Here is the implementation of the successor class: } Zero における、前の数(predecessor)および減算(-)関数の実装は、Error 例外を送出し、与えられたエラーメッセージと共にプログラムをアボートします。 次は「次の数」クラスの実装です。 class Succ(x: Nat) extends Nat { def isZero: Boolean = false def predecessor: Nat = x def successor: Nat = new Succ(this) def + (that: Nat): Nat = x + that.successor def - (that: Nat): Nat = if (that.isZero) this else x - that.predecessor } #co(){ Note the implementation of method successor. To create the successor of a number, we need to pass the object itself as an argument to the Succ constructor. The object itself is referenced by the reserved name this. } メソッド successor の実装に注意して下さい。ある数の次の数を作るために、Succ コンストラクタにオブジェクト自身を引数として渡す必要があります。オブジェクト自身は予約語 this で参照できます。 #co(){ The implementations of + and - each contain a recursive call with the constructor argument as receiver. The recursion will terminate once the receiver is the Zero object (which is guaranteed to happen eventually because of the way numbers are formed). } ~+ と - の実装はそれぞれ、コンストラクタ引数を受け手とする再帰呼び出しを含みます。再帰は、受け手が Zero オブジェクトの時 (そのように数を構成したので、それが起きることは保証されています) に終わります。 #co(){ Exercise 6.0.3 Write an implementation Integer of integer numbers The implementation should support all operations of class Nat while adding two methods} &b(){演習 6.0.3 } 整数の実装 Integer を書きなさい。実装はクラス Nat の操作すべてをサポートし、加えて、次の2つのメソッドもサポートしなさい。 def isPositive: Boolean def negate: Integer #co(){ The first method should return true if the number is positive. The second method should negate the number. Do not use any of Scala's standard numeric classes in your implementation. (Hint: There are two possible ways to implement Integer. One can either make use the existing implementation of Nat, representing an integer as a natural number and a sign. Or one can generalize the given implementation of Nat to Integer, using the three subclasses Zero for 0, Succ for positive numbers and Pred for negative numbers.) } 最初のメソッドは数が正であれば true を返すものとします。二つ目のメソッドは数の符号を変えます。実装の中で Scala の標準の数値クラスを使ってはいけません (ヒント : Integer の実装方法は二つ可能です。一つは既存の Nat 実装を利用し、整数を自然数と符号で表すものです。あるいは3つの子クラスとして Zero を 0 に、Succ を正の数のため、Pred を負の数のために使い、既知の Nat 実装を Integer へ一般化できます)。 #co(){ Language Elements Introduced In This Chapter Types: } *** この章で紹介した構文 &b(){型 (Types) } Type = ... | ident #co(){ Types can now be arbitrary identifiers which represent classes. Expressions: } 型は、クラスを表す任意の識別子です。 &b(){式 (Expressions) } Expr = ... | Expr '.' ident | 'new' Expr | 'this' #co(){ An expression can now be an object creation, or a selection E.m of a member m from an object-valued expression E, or it can be the reserved name this. Definitions and Declarations: } 式は、オブジェクト生成、オブジェクト値を持つ式 E のメンバ m を選択する E.m、あるいは予約語の this です。 &b(){定義と宣言 (Definitions and Declarations) } Def = FunDef | ValDef | ClassDef | TraitDef | ObjectDef ClassDef = ['abstract'] 'class' ident ['(' [Parameters] ')'] ['extends' Expr] ['{' {TemplateDef} '}'] TraitDef = 'trait' ident ['extends' Expr] ['{' {TemplateDef} '}'] ObjectDef = 'object' ident ['extends' Expr] ['{' {ObjectDef} '}'] TemplateDef = [Modifier] (Def | Dcl) ObjectDef = [Modifier] Def Modifier = 'private' | 'override' Dcl = FunDcl | ValDcl FunDcl = 'def' ident {'(' [Parameters] ')'} ':' Type ValDcl = 'val' ident ':' Type #co(){ A definition can now be a class, trait or object definition such as } 定義は次のようなクラス、トレイト、あるいはオブジェクト定義です。 class C(params) extends B { defs } trait T extends B { defs } object O extends B { defs } #co(){ The definitions defs in a class, trait or object may be preceded by modifiers private or override. Abstract classes and traits may also contain declarations. These introduce deferred functions or values with their types, but do not give an implementation. Deferred members have to be implemented in subclasses before objects of an abstract class or trait can be created. } クラス、トレイト、オブジェクト定義内の定義 def の手前に、修飾子 private あるいは override を置けます。 抽象クラスとトレイトに宣言を含めることもできます。それらは&bold(){延期された}関数や値を型とともに導入しますが、実装は与えません。延期されたメンバは、抽象クラスやトレイトのオブジェクトを生成する前に、サブクラスで実装しておく必要があります。 #center(){[[前ページ>Example5.5]] [[ 6 章>ExampleChap6]] [[目次>ScalaByExample和訳]] [[次ページ>ExampleChap7]]} ---- - お疲れさまです。「新規のmンバ」は「新規のメンバ」のtypoかと・・・ -- ryugate (2008-05-23 00:18:04) - ↑を反映。訳で抜けている文章を追加。行頭の+は~+にしないといけないらしい・・・ -- asaq (2008-10-05 13:46:14) #comment
#co(){ Chapter 6 Classes and Objects Scala does not have a built-in type of rational numbers, but it is easy to define one, using a class. Here's a possible implementation. } #setmenu2(ex-r-menu) * 第 6 章 クラスとオブジェクト Scala には組み込みの有理数型はありませんが、クラスを使って簡単に定義できます。次が実装例です。 class Rational(n: Int, d: Int) { private def gcd(x: Int, y: Int): Int = { if (x == 0) y else if (x < 0) gcd(-x, y) else if (y < 0) -gcd(x, -y) else gcd(y % x, x) } private val g = gcd(n, d) val numer: Int = n/g val denom: Int = d/g def +(that: Rational) = new Rational(numer * that.denom + that.numer * denom, denom * that.denom) def -(that: Rational) = new Rational(numer * that.denom - that.numer * denom, denom * that.denom) def *(that: Rational) = new Rational(numer * that.numer, denom * that.denom) def /(that: Rational) = new Rational(numer * that.denom, denom * that.numer) } #co(){ This defines Rational as a class which takes two constructor arguments n and d, containing the number's numerator and denominator parts. The class provides fields which return these parts as well as methods for arithmetic over rational numbers. Each arithmetic method takes as parameter the right operand of the operation. The left operand of the operation is always the rational number of which the method is a member. } 有理数を、分子 n と分母 d の2つのコンストラクタ引数をとるクラスとして定義しています。このクラスは有理数上の演算メソッドだけではなく、分子と分母を返すフィールドも提供します。各演算メソッドは操作の右オペランドをパラメータにとります。操作の左オペランドは、常にそのメソッドがメンバであるような有理数です。 #co(){ Private members. The implementation of rational numbers defines a private method gcd which computes the greatest common denominator of two integers, as well as a private field g which contains the gcd of the constructor arguments. These members are inaccessible outside class Rational. They are used in the implementation of the class to eliminate common factors in the constructor arguments in order to ensure that numerator and denominator are always in normalized form. } &b(){プライベート・メンバ } 有理数の実装では、2つの整数の最大公約数を計算するプライベートなメソッド gcd を定義し、また、コンストラクタ引数の gcd を格納するプライベートなフィールド g も定義します。これらのメンバはクラス Rational の外からはアクセスできません。それらはクラスの実装において、コンストラクタ引数を公約数で割って、分子と分母を常に正規形(約分された形)にするために使います。 #co(){ Creating and Accessing Objects. As an example of how rational numbers can be used, here's a program that prints the sum of all numbers 1/i where i ranges from 1 to 10. } &b(){オブジェクト生成とアクセス } 有理数の使い方の例として、i の範囲が 1 から 10 まで時に、1/i の和を印刷するプログラムを示します。 var i = 1 var x = new Rational(0, 1) while (i <= 10) { x += new Rational(1, i) i += 1 } println("" + x.numer + "/" + x.denom) #co(){ The + takes as left operand a string and as right operand a value of arbitrary type. It returns the result of converting its right operand to a string and appending it to its left operand. } ~+ は左オペランドに文字列を、右オペランドに任意の型の値をとります。右オペランドを文字列に変換し、それを左オペランドに追加した結果を返します。 #co(){ Inheritance and Overriding. Every class in Scala has a superclass which it extends. If a class does not mention a superclass in its definition, the root type scala.AnyRef is implicitly assumed (for Java implementations, this type is an alias for java.lang.Object. For instance, class Rational could equivalently be defined as } &b(){継承とオーバーライド } Scala のすべてのクラスは親クラスを持ち、それを継承しています。クラス定義の中で親クラスが指定されていない場合、ルート型 scala.AnyRef が暗黙のうちに仮定されます (Java の実装でいうと、この型は java.lang.Object のエイリアスです)。 たとえばクラス Rational を次のように定義しても同じことです。 class Rational(n: Int, d: Int) extends AnyRef { ... // as before } #co(){ A class inherits all members from its superclass. It may also redefine (or: override) some inherited members. For instance, class java.lang.Object defines a method toString which returns a representation of the object as a string: } クラスは親クラスのメンバをすべて継承します。継承したメンバを再定義 (つまり、&bold(){オーバーライド}) できます。たとえばクラス java.lang.Object は、オブジェクトの文字列表現を返すメソッド toString を定義しています。 class Object { ... def toString: String = ... } #co(){ The implementation of toString in Object forms a string consisting of the object's class name and a number. It makes sense to redefine this method for objects that are rational numbers: } Object での toString は、オブジェクトのクラス名と数からなる文字列として実装されています。このメソッドを有理数のオブジェクト用に再定義すると役に立ちます。 class Rational(n: Int, d: Int) extends AnyRef { ... // as before override def toString = "" + numer + "/" + denom } #co(){ Note that, unlike in Java, redefining definitions need to be preceded by an override modifier. If class A extends class B , then objects of type A may be used wherever objects of type B are expected. We say in this case that type A conforms to type B . For instance, Rational conforms to AnyRef, so it is legal to assign a Rational value to a variable of type AnyRef: } 注意すべき点は、Java とは異なり、定義の再定義では手前に override 修飾子が必要なことです。 もしクラス A がクラス B を継承しているなら、型 A のオブジェクトは、型 B のオブジェクトが期待される所ではどこでも使用できます。このような場合、型 A は型 B に&bold(){適合する}(conform)、といいます。たとえば Rational は AnyRef に適合します。したがって Rational 値を AnyRef 型の変数に代入できます。 var x: AnyRef = new Rational(1, 2) #co(){ Parameterless Methods. Unlike in Java, methods in Scala do not necessarily take a parameter list. An example is the square method below. This method is invoked by simply mentioning its name. } &b(){パラメータなしのメソッド } Java とは異なり、Scala のメソッドはパラメータリストを必ずしも必要としません。次の square メソッドがその例です。このメソッドは単に名前を書くだけで呼び出されます。 class Rational(n: Int, d: Int) extends AnyRef { ... // as before def square = new Rational(numer*numer, denom*denom) } val r = new Rational(3, 4) println(r.square) // prints''9/16''* #co(){ That is, parameterless methods are accessed just as value fields such as numer are. The difference between values and parameterless methods lies in their definition. The right-hand side of a value is evaluated when the object is created, and the value does not change afterwards. A right-hand side of a parameterless method, on the other hand, is evaluated each time the method is called. The uniform access of fields and parameterless methods gives increased flexibility for the implementer of a class. Often, a field in one version of a class becomes a computed value in the next version. Uniform access ensures that clients do not have to be rewritten because of that change. } これは、パラメータなしメソッドは numer のような値フィールドと同じようにアクセスされるということです。値とパラメータなしメソッドの違いはそれらの定義にあります。値の右辺はオブジェクトが作成された時に評価され、以後は値が変化しません。その一方で、パラメータなしメソッドの右辺はメソッドが呼ばれる度に評価されます。フィールドとパラメータなしメソッドのアクセス方法が統一されているのでクラス実装者の自由度が増します。しばしば、あるバージョンではフィールドだったものが次のバージョンでは計算された値だったりします。統一的なアクセスのおかげで、クライアントは変更による書き直しが不要になります。 #co(){ Abstract Classes. Consider the task of writing a class for sets of integer numbers with two operations, incl and contains. (s incl x) should return a new set which contains the element x together with all the elements of set s. (s contains x) should return true if the set s contains the element x, and should return false otherwise. The interface of such sets is given by: } &b(){抽象クラス } 整数の集合に対して、2つの操作 incl と contains を持つクラスを書く課題について考えてみましょう。(s incl x) は集合 s の要素すべてと、要素 x を持つ新しい集合を返すべきです。(s contains x) は、集合 s が要素 x を含む時は true を、さもなければ false を返すべきです。そのような集合のインターフェイスは次のようになります。 abstract class IntSet { def incl(x: Int): IntSet def contains(x: Int): Boolean } #co(){ IntSet is labeled as an abstract class. This has two consequences. First, abstract classes may have deferred members which are declared but which do not have an implementation. In our case, both incl and contains are such members. Second, because an abstract class might have unimplemented members, no objects of that class may be created using new. By contrast, an abstract class may be used as a base class of some other class, which implements the deferred members. } IntSet は&bold(){抽象クラス}と分類されます。これは2つの結果をもたらします。一つは、抽象クラスは&bold(){延期された}メンバを持ち、それらは宣言はあっても実装はありません。この場合、incl と contains の両方がそのようなメンバです。二つ目は、抽象クラスには未実装のメンバがあるかもしれないので、そのクラスのオブジェクトを new で生成できません。その一方で、抽象クラスは他のクラスの基底クラスとして使うことができ、継承したクラスでは延期されたメンバを実装します。 #co(){ Traits. Instead of abstract class one also often uses the keyword trait in Scala. Traits are abstract classes that are meant to be added to some other class. This might be because a trait adds some methods or fields to an unknown parent class. For instance, a trait Bordered might be used to add a border to a various graphical components. Another usage scenario is where the trait collects signatures of some functionality provided by different classes, much in the way a Java interface would work. Since IntSet falls in this category, one can alternatively define it as a trait: } &b(){トレイト } Scala では、abstract class の代わりにキーワード trait をよく使います。トレイトは、他のクラスに付け加えるための抽象クラスです。これはトレイトが未知の親クラスにメソッドやフィールドをいくつか追加するからです。たとえば、トレイト Bordered は様々なグラフィカルコンポーネントに縁を追加するのに使われるでしょう。別の使い方としては、様々なクラスによって提供される機能のシグネチャをトレイトが集約する、という Java のインターフェイスがするようなやり方です。 IntSet はこの分類に入るので、トレイトとしても定義できます。 trait IntSet { def incl(x: Int): IntSet def contains(x: Int): Boolean } #co(){ Implementing Abstract Classes. Let's say, we plan to implement sets as binary trees. There are two possible forms of trees. A tree for the empty set, and a tree consisting of an integer and two subtrees. Here are their implementations. } &b(){抽象クラスの実装 } たとえば集合を二分木で実装することにしましょう。木の形には2つの可能性があります。空集合を表す木と、整数1つと2つの部分木からなる木です。次がその実装です。 class EmptySet extends IntSet { def contains(x: Int): Boolean = false def incl(x: Int): IntSet = new NonEmptySet(x, new EmptySet, new EmptySet) } class NonEmptySet(elem: Int, left: IntSet, right: IntSet) extends IntSet { def contains(x: Int): Boolean = if (x < elem) left contains x else if (x > elem) right contains x else true def incl(x: Int): IntSet = if (x < elem) new NonEmptySet(elem, left incl x, right) else if (x > elem) new NonEmptySet(elem, left, right incl x) else this } #co(){ Both EmptySet and NonEmptySet extend class IntSet. This implies that types EmptySet and NonEmptySet conform to type IntSet - a value of type EmptySet or NonEmptySet may be used wherever a value of type IntSet is required. } EmptySet と NonEmptySet の両方とも IntSet を継承します。これは、型 EmptySet と NonEmptySet が型 IntSet に適合すること --- 型 EmptySet や NonEmptySet の値は、型 IntSet の値が必要なところではどこでも使えることを意味します。 #co(){ Exercise 6.0.1 Write methods union and intersection to form the union and intersection between two sets. Exercise 6.0.2 Add a method def excl(x: Int) to return the given set without the element x. To accomplish this, it is useful to also implement a test method def isEmpty: Boolean for sets. } &b(){演習 6.0.1 } メソッド union と intersection を2つの集合の結びと交わりとなるように書きなさい。 &b(){演習 6.0.2 } 要素 x を取り除いた集合を返すメソッド def excl(x: Int) を追加しなさい。そうするために便利なテスト用メソッド def isEmpty: Boolean も、集合に対して実装しなさい。 #co(){ Dynamic Binding. Object-oriented languages (Scala included) use dynamic dispatch for method invocations. That is, the code invoked for a method call depends on the run-time type of the object which contains the method. For example, consider the expression s contains 7 where s is a value of declared type s: IntSet. Which code for contains is executed depends on the type of value of s at run-time. If it is an EmptySet value, it is the implementation of contains in class EmptySet that is executed, and analogously for NonEmptySet values. This behavior is a direct consequence of our substitution model of evaluation. For instance, (new EmptySet).contains(7) -> (by replacing contains by its body in class EmptySet) false Or, new NonEmptySet(7, new EmptySet, new EmptySet).contains(1) -> (by replacing contains by its body in class NonEmptySet) if (1 < 7) new EmptySet contains 1 else if (1 > 7) new EmptySet contains 1 else true -> (by rewriting the conditional) new EmptySet contains 1 -> (by replacing contains by its body in class EmptySet) false . } &b(){動的束縛 } (Scala を含む) オブジェクト指向言語はメソッド呼び出しのときに&bold(){動的ディスパッチ}を用います。これは、メソッド呼び出しで起動されるコードは、メソッドを含むオブジェクトの実行時型に依存するということです。たとえば式 s contains 7、ただし s は宣言された型 s: IntSet の値だとします。もしそれが EmptySet の値なら、実行されるのはクラス EmptySet の contains の実装であり、NonEmptySet の値の場合も同様です。この振る舞いは評価の置き換えモデルの直接的な帰結です。たとえば (new EmptySet).contains(7) -> (クラス EmptySet の contains 本体で置き換えることで) false あるいは new NonEmptySet(7, new EmptySet, new EmptySet).contains(1) -> (クラス NonEmptySet の contains 本体で置き換えることで) if (1 < 7) new EmptySet contains 1 else if (1 > 7) new EmptySet contains 1 else true -> (条件式を書き換えて) new EmptySet contains 1 -> (クラス EmptySet の contains 本体で置き換えることで) false . #co(){ Dynamic method dispatch is analogous to higher-order function calls. In both cases, the identity of code to be executed is known only at run-time. This similarity is not just superficial. Indeed, Scala represents every function value as an object (see Section 8.6). } 動的メソッドディスパッチは高階関数呼び出しと似ています。どちらの場合にも、実行されるコードの正体は実行時にしか分かりません。この類似性は表面上のものにとどまりません。実際、Scala はすべての関数値をオブジェクトとして表現しています (8.6 節参照)。 #co(){ Objects. In the previous implementation of integer sets, empty sets were expressed with new EmptySet; so a new object was created every time an empty set value was required. We could have avoided unnecessary object creations by defining a value empty once and then using this value instead of every occurrence of new EmptySet. For example: } &b(){オブジェクト } 整数集合の以前の実装では、空集合は new EmptySet で表現されていました。つまり空集合値が必要になる度に、毎回新しいオブジェクトが生成されました。値 empty を一度だけ定義して、その値をすべての new EmptySet の出現の代わりに使えば、不必要なオブジェクト生成を避けることができます。たとえば val EmptySetVal = new EmptySet #co(){ One problem with this approach is that a value definition such as the one above is not a legal top-level definition in Scala; it has to be part of another class or object. Also, the definition of class EmptySet now seems a bit of an overkill - why define a class of objects, if we are only interested in a single object of this class? A more direct approach is to use an object definition. Here is a more streamlined alternative definition of the empty set: } この方法の問題の一つは、このような値定義は Scala の正しいトップレベルでの定義ではない、ということです。これは別のクラスかオブジェクトの一部でなくてはなりません。またクラス EmptySet の定義は少しばかりやり過ぎています。もしたった一つのオブジェクトにだけ関心があるなら、なぜオブジェクト用のクラスを定義するのでしょう? より直接的な方法は&bold(){オブジェクト定義}を使うことです。代わりの、より洗練された空集合の定義は次のようになります。 object EmptySet extends IntSet { def contains(x: Int): Boolean = false def incl(x: Int): IntSet = new NonEmptySet(x, EmptySet, EmptySet) } #co(){ The syntax of an object definition follows the syntax of a class definition; it has an optional extends clause as well as an optional body. As is the case for classes, the extends clause defines inherited members of the object whereas the body defines overriding or new members. However, an object definition defines a single object only it is not possible to create other objects with the same structure using new. Therefore, object definitions also lack constructor parameters, which might be present in class definitions. } オブジェクト定義の構文はクラス定義の構文に従います。オプションの extends 節とオプションの本体があります。クラスの場合のように、extends 節はオブジェクトが継承するメンバを定義し、一方で本体はオーバーライドするあるいは新規のメンバを定義します。しかしオブジェクト定義はただ1つのオブジェクトだけを定義するため、同じ構造を持った他のオブジェクトを new で生成できません。したがってオブジェクト定義には、クラス定義にはあるかもしれないコンストラクタパラメータはありません。 #co(){ Object definitions can appear anywhere in a Scala program; including at top-level. Since there is no fixed execution order of top-level entities in Scala, one might ask exactly when the object defined by an object definition is created and initialized. The answer is that the object is created the first time one of its members is accessed. This strategy is called lazy evaluation. } オブジェクト定義は Scala プログラムのどこにでも、トップレベルにも、書けます。Scala ではトップレベルのエンティティの実行順序は固定されていないため、オブジェクト定義によって定義されたオブジェクトがいつ生成され初期化されるのか、正確に知りたい人もいるでしょう。その答えは、オブジェクトはそのメンバが最初にアクセスされる時に生成される、というものです。この戦略は&bold(){遅延評価}と呼ばれています。 #co(){ Standard Classes. Scala is a pure object-oriented language. This means that every value in Scala can be regarded as an object. In fact, even primitive types such as int or boolean are not treated specially. They are defined as type aliases of Scala classes in module Predef: } &b(){標準クラス } Scala は純粋なオブジェクト指向言語です。これは Scala のすべての値がオブジェクトである、ということです。実際 int や boolean のようなプリミティブ型でさえ特別扱いされません。それらはモジュール Predef において、Scala クラスの型エイリアスとして定義されています。 type boolean = scala.Boolean type int = scala.Int type long = scala.Long ... #co(){ For efficiency, the compiler usually represents values of type scala.Int by 32 bit integers, values of type scala.Boolean by Java's booleans, etc. But it converts these specialized representations to objects when required, for instance when a primitive Int value is passed to a function with a parameter of type AnyRef. Hence, the special representation of primitive values is just an optimization, it does not change the meaning of a program. Here is a specification of class Boolean. } コンパイラは効率化のために通常、scala.Int 型の値を 32 bit 整数で、scala.Boolean 型の値を Java の boolean で、等と表現します。しかし必要に応じてこれらの特別な表現をオブジェクトに変換します。たとえばプリミティブの Int 値が AnyRef 型のパラメータをとる関数に渡される時です。したがってプリミティブ値の特別な表現は単に最適化のためであり、プログラムの意味を変えません。 次がクラス Boolean の仕様です。 package scala abstract class Boolean { def && (x: => Boolean): Boolean def || (x: => Boolean): Boolean def ! : Boolean def == (x: Boolean) : Boolean def != (x: Boolean) : Boolean def < (x: Boolean) : Boolean def > (x: Boolean) : Boolean def <= (x: Boolean) : Boolean def >= (x: Boolean) : Boolean } #co(){ Booleans can be defined using only classes and objects, without reference to a built-in type of booleans or numbers. A possible implementation of class Boolean is given below. This is not the actual implementation in the standard Scala library. For efficiency reasons the standard implementation uses built-in booleans. } Boolean はクラスとオブジェクトだけを使って定義でき、組み込みのブール値や数値の型を参照する必要はありません。Boolean 型の一つの実装を次に示します。これは標準 Scala ライブラリの実際の実装ではありません。効率化のために標準の実装では、組み込みのブール値を使います。 package scala abstract class Boolean { def ifThenElse(thenpart: => Boolean, elsepart: => Boolean) def && (x: => Boolean): Boolean = ifThenElse(x, false) def || (x: => Boolean): Boolean = ifThenElse(true, x) def ! : Boolean = ifThenElse(false, true) def == (x: Boolean) : Boolean = ifThenElse(x, x.!) def != (x: Boolean) : Boolean = ifThenElse(x.!, x) def < (x: Boolean) : Boolean = ifThenElse(false, x) def > (x: Boolean) : Boolean = ifThenElse(x.!, false) def <= (x: Boolean) : Boolean = ifThenElse(x, true) def >= (x: Boolean) : Boolean = ifThenElse(true, x.!) } case object True extends Boolean { def ifThenElse(t: => Boolean, e: => Boolean) = t } case object False extends Boolean { def ifThenElse(t: => Boolean, e: => Boolean) = e } #co(){ Here is a partial specification of class Int. } 次はクラス Int の仕様の一部です。 package scala abstract class Int extends AnyVal { def toLong: Long def toFloat: Float def toDouble: Double def + (that: Double): Double def + (that: Float): Float def + (that: Long): Long def + (that: Int): Int // analogous for -, *, /, % def << (cnt: Int): Int // analogous for >>, >>> def & (that: Long): Long def & (that: Int): Int // analogous for |, ^ def == (that: Double): Boolean def == (that: Float): Boolean def == (that: Long): Boolean // analogous for !=, <, >, <=, >= } #co(){ Class Int can in principle also be implemented using just objects and classes, without reference to a built in type of integers. To see how, we consider a slightly simpler problem, namely how to implement a type Nat of natural (i.e. non-negative) numbers. Here is the definition of an abstract class Nat:} クラス Int も原理的にはオブジェクトとクラスだけで、組み込みの整数型を参照することなく実装できます。その方法を知るために、少しだけ簡単な問題、自然数を表す型 Nat の実装方法を考えみましょう。次は抽象クラス Nat の定義です。 abstract class Nat { def isZero: Boolean def predecessor: Nat def successor: Nat def + (that: Nat): Nat def - (that: Nat): Nat } #co(){ To implement the operations of class Nat, we define a sub-object Zero and a subclass Succ (for successor). Each number N is represented as N applications of the Succ constructor to Zero: } クラス Nat の操作を実装するために、子オブジェクト Zero と子クラス Succ (次の数) を定義します。それぞれの数 N は、Zero に Succ コンストラクタを N 回適用したものです。 new Succ( ... new Succ(Zero) ... ) <------- N 回 -------> #co(){ The implementation of the Zero object is straightforward: } オブジェクト Zero の定義はそのままです。 object Zero extends Nat { def isZero: Boolean = true def predecessor: Nat = error("negative number") def successor: Nat = new Succ(Zero) def + (that: Nat): Nat = that def - (that: Nat): Nat = if (that.isZero) Zero else error("negative number") } #co(){ The implementation of the predecessor and subtraction functions on Zero throws an Error exception, which aborts the program with the given error message. Here is the implementation of the successor class: } Zero における、前の数(predecessor)および減算(-)関数の実装は、Error 例外を送出し、与えられたエラーメッセージと共にプログラムをアボートします。 次は「次の数」クラスの実装です。 class Succ(x: Nat) extends Nat { def isZero: Boolean = false def predecessor: Nat = x def successor: Nat = new Succ(this) def + (that: Nat): Nat = x + that.successor def - (that: Nat): Nat = x - that.predecessor } #co(){ Note the implementation of method successor. To create the successor of a number, we need to pass the object itself as an argument to the Succ constructor. The object itself is referenced by the reserved name this. } メソッド successor の実装に注意して下さい。ある数の次の数を作るために、Succ コンストラクタにオブジェクト自身を引数として渡す必要があります。オブジェクト自身は予約語 this で参照できます。 #co(){ The implementations of + and - each contain a recursive call with the constructor argument as receiver. The recursion will terminate once the receiver is the Zero object (which is guaranteed to happen eventually because of the way numbers are formed). } ~+ と - の実装はそれぞれ、コンストラクタ引数を受け手とする再帰呼び出しを含みます。再帰は、受け手が Zero オブジェクトの時 (そのように数を構成したので、それが起きることは保証されています) に終わります。 #co(){ Exercise 6.0.3 Write an implementation Integer of integer numbers The implementation should support all operations of class Nat while adding two methods} &b(){演習 6.0.3 } 整数の実装 Integer を書きなさい。実装はクラス Nat の操作すべてをサポートし、加えて、次の2つのメソッドもサポートしなさい。 def isPositive: Boolean def negate: Integer #co(){ The first method should return true if the number is positive. The second method should negate the number. Do not use any of Scala's standard numeric classes in your implementation. (Hint: There are two possible ways to implement Integer. One can either make use the existing implementation of Nat, representing an integer as a natural number and a sign. Or one can generalize the given implementation of Nat to Integer, using the three subclasses Zero for 0, Succ for positive numbers and Pred for negative numbers.) } 最初のメソッドは数が正であれば true を返すものとします。二つ目のメソッドは数の符号を変えます。実装の中で Scala の標準の数値クラスを使ってはいけません (ヒント : Integer の実装方法は二つ可能です。一つは既存の Nat 実装を利用し、整数を自然数と符号で表すものです。あるいは3つの子クラスとして Zero を 0 に、Succ を正の数のため、Pred を負の数のために使い、既知の Nat 実装を Integer へ一般化できます)。 #co(){ Language Elements Introduced In This Chapter Types: } *** この章で紹介した構文 &b(){型 (Types) } Type = ... | ident #co(){ Types can now be arbitrary identifiers which represent classes. Expressions: } 型は、クラスを表す任意の識別子です。 &b(){式 (Expressions) } Expr = ... | Expr '.' ident | 'new' Expr | 'this' #co(){ An expression can now be an object creation, or a selection E.m of a member m from an object-valued expression E, or it can be the reserved name this. Definitions and Declarations: } 式は、オブジェクト生成、オブジェクト値を持つ式 E のメンバ m を選択する E.m、あるいは予約語の this です。 &b(){定義と宣言 (Definitions and Declarations) } Def = FunDef | ValDef | ClassDef | TraitDef | ObjectDef ClassDef = ['abstract'] 'class' ident ['(' [Parameters] ')'] ['extends' Expr] ['{' {TemplateDef} '}'] TraitDef = 'trait' ident ['extends' Expr] ['{' {TemplateDef} '}'] ObjectDef = 'object' ident ['extends' Expr] ['{' {ObjectDef} '}'] TemplateDef = [Modifier] (Def | Dcl) ObjectDef = [Modifier] Def Modifier = 'private' | 'override' Dcl = FunDcl | ValDcl FunDcl = 'def' ident {'(' [Parameters] ')'} ':' Type ValDcl = 'val' ident ':' Type #co(){ A definition can now be a class, trait or object definition such as } 定義は次のようなクラス、トレイト、あるいはオブジェクト定義です。 class C(params) extends B { defs } trait T extends B { defs } object O extends B { defs } #co(){ The definitions defs in a class, trait or object may be preceded by modifiers private or override. Abstract classes and traits may also contain declarations. These introduce deferred functions or values with their types, but do not give an implementation. Deferred members have to be implemented in subclasses before objects of an abstract class or trait can be created. } クラス、トレイト、オブジェクト定義内の定義 def の手前に、修飾子 private あるいは override を置けます。 抽象クラスとトレイトに宣言を含めることもできます。それらは&bold(){延期された}関数や値を型とともに導入しますが、実装は与えません。延期されたメンバは、抽象クラスやトレイトのオブジェクトを生成する前に、サブクラスで実装しておく必要があります。 #center(){[[前ページ>Example5.5]] [[ 6 章>ExampleChap6]] [[目次>ScalaByExample和訳]] [[次ページ>ExampleChap7]]} ---- - お疲れさまです。「新規のmンバ」は「新規のメンバ」のtypoかと・・・ -- ryugate (2008-05-23 00:18:04) - ↑を反映。訳で抜けている文章を追加。行頭の+は~+にしないといけないらしい・・・ -- asaq (2008-10-05 13:46:14) #comment

表示オプション

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

下から選んでください:

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