プログラミング言語 Scala Wiki
http://w.atwiki.jp/tmiya/
プログラミング言語 Scala Wikija2012-09-19T12:41:19+09:001348026079ExampleChap3
https://w.atwiki.jp/tmiya/pages/26.html
#co(){
Chapter 3 Programming with Actors and Messages
Here's an example that shows an application area for which Scala is particularly well suited. Consider the task of implementing an electronic auction service. We use an Erlang-style actor process model to implement the participants of the auction. Actors are objects to which messages are sent. Every actor has a "mailbox" of its incoming messages which is represented as a queue. It can work sequentially through the messages in its mailbox, or search for messages matching some pattern.
}
#setmenu2(ex-r-menu)
* 第 3 章 アクターとメッセージによるプログラミング
この章では、ある応用分野の例を見てゆきます。この分野は Scala がとてもよく似合っています。電子オークションサービスを実装する仕事を考えてみて下さい。Erlang 風のアクタープロセスモデルを使ってオークション参加者の実装をしましょう。アクターとはメッセージを受信するオブジェクトです。各アクターはメッセージ受信用の「メールボックス」を持ち、それはキューとして表現されます。メールボックス中のメッセージを順番に処理することも、特定のパターンにマッチするメッセージを検索することもできます。
#co(){
For every traded item there is an auctioneer actor that publishes information about the traded item, that accepts offers from clients and that communicates with the seller and winning bidder to close the transaction. We present an overview of a simple implementation here.
}
取引される商品ごとに一つの競売人アクターがあり、そのアクターが、商品についての情報公開、クライアントからの申し込み受付け、取引終了時の売り手および落札者への通知します。ここでは簡単な実装の概要を示します。
#co(){
As a first step, we define the messages that are exchanged during an auction. There are two abstract base classes AuctionMessage for messages from clients to the auction service, and AuctionReply for replies from the service to the clients. For both base classes there exists a number of cases, which are defined in Figure 3.1.
}
最初のステップとして、オークションでやり取りされるメッセージを定義します。クライアントからオークションサービスへのメッセージである AuctionMessage と、サービスからクライアントへの返答である AuctionReply の、2つの抽象基底クラス(abstract base class)があります。両基底クラスには複数のケース(case)があり、図 3.1 で定義されています。
---
import scala.actors.Actor
abstract class AuctionMessage
case class Offer(bid: Int, client: Actor) extends AuctionMessage
case class Inquire(client: Actor) extends AuctionMessage
abstract class AuctionReply
case class Status(asked: Int, expire: Date) extends AuctionReply
case object BestOffer extends AuctionReply
case class BeatenOffer(maxBid: Int) extends AuctionReply
case class AuctionConcluded(seller: Actor, client: Actor)
extends AuctionReply
case object AuctionFailed extends AuctionReply
case object AuctionOver extends AuctionReply
--- Listing 3.1: オークションサービスのメッセージクラス ---
#co(){
--- Listing 3.1: Message Classes for an Auction Service ---
}
#co(){
For each base class, there are a number of case classes which define the format of particular messages in the class. These messages might well be ultimately mapped to small XML documents. We expect automatic tools to exist that convert between XML documents and internal data structures like the ones defined above.
}
各基底クラスごとに、クラス内の特定のメッセージ形式を定義する&bold(){ケースクラス}(case class)が複数あります。それらのメッセージが、小さな XML 文書に最終的にうまく対応できればよいのですが・・・。ここでは自動化ツールが存在すると仮定して、 XML 文書と、先のように定義された内部データ構造とを変換するとしましょう。
#co(){
Figure 3.2 presents a Scala implementation of a class Auction for auction actors that coordinate the bidding on one item. Objects of this class are created by indicating
- a seller actor which needs to be notified when the auction is over,
- a minimal bid,
- the date when the auction is to be closed.
}
図 3.2 で示す Scala 実装、Auction クラスは、ある商品のオークションを調整する競売人アクターのためのものです。このクラスのオブジェクトは、次を指定して生成されます。
- オークション終了時に通知する必要のある売り手アクター
- 最低オークション価格
- オークションの終了予定時刻
#co(){
The behavior of the actor is defined by its act method. That method repeatedly selects (using receiveWithin) a message and reacts to it, until the auction is closed, which is signaled by a TIMEOUT message. Before finally stopping, it stays active for another period determined by the timeToShutdown constant and replies to further offers that the auction is closed.
Here are some further explanations of the constructs used in this program:
}
アクターの振る舞いは act メソッドで定義されています。このメソッドでは 、TIMEOUT メッセージによってオークション終了が通知されるまで (receiveWithin を用いて) メッセージを選択し、それに対応することを繰り返します。最終的に停止する前まで、定数 timeToShutdown で定められた期間はアクティブであり続け、さらなる申し出に対してはオークションを締切った旨を返答します。
このプログラムで使われている構文について、さらにいくつか解説します。
#co(){
- The receiveWithin method of class Actor takes as parameters a time span given in milliseconds and a function that processes messages in the mailbox. The function is given by a sequence of cases that each specify a pattern and an action to perform for messages matching the pattern. The receiveWithin method selects the first message in the mailbox which matches one of these patterns and applies the corresponding action to it.
- The last case of receiveWithin is guarded by a TIMEOUT pattern. If no other messages are received in the meantime, this pattern is triggered after the time span which is passed as argument to the enclosing receiveWithin method. TIMEOUT is a special message, which is triggered by the Actor implementation itself.
- Reply messages are sent using syntax of the form destination ! SomeMessage. ! is used here as a binary operator with an actor and a message as arguments. This is equivalent in Scala to the method call destination.!(SomeMessage), i.e. the invocation of the ! method of the destination actor with the given message as parameter.
}
- クラス Actor の receiveWithin メソッドはパラメータとして、期間 (ミリ秒単位) とメールボックスのメッセージを処理する関数をとります。関数は一連のケースとして与えられ、各ケースはパターンと、そのパターンに対応したメッセージを処理するアクションを指定します。receiveWithin メソッドは、メールボックスからパターンにマッチする最初のメッセージを選び、対応するアクションを適用します。
- receiveWithin の最後のケースは TIMEOUT パターンで守られています。もし有効時間内にメッセージを受け取らなければ、receiveWithin メソッドの引数として渡された期間経過後に、このパターンが起動されます。TIMEOUT は特別なメッセージで、Actor の実装そのものによって起動されます。
- 応答メッセージを送信する構文として、destnation ! SomeMessage (宛先 ! メッセージ) を用います。ここで ! は、アクターとメッセージを引数とする二項演算子のように使われています。これは Scala では、メソッド呼び出し destination.!(SomeMessage)、すなわち、メッセージをパラメータとした destination アクターの ! メソッド呼び出しと同じです。
---
class Auction(seller: Actor, minBid: Int, closing: Date) extends Actor {
val timeToShutdown = 36000000 // msec
val bidIncrement = 10
def act() {
var maxBid = minBid - bidIncrement
var maxBidder: Actor = null
var running = true
while (running) {
receiveWithin ((closing.getTime() - new Date().getTime())) {
case Offer(bid, client) =>
if (bid >= maxBid + bidIncrement) {
if (maxBid >= minBid) maxBidder ! BeatenOffer(bid)
maxBid = bid; maxBidder = client; client ! BestOffer
} else {
client ! BeatenOffer(maxBid)
}
case Inquire(client) =>
client ! Status(maxBid, closing)
case TIMEOUT =>
if (maxBid >= minBid) {
val reply = AuctionConcluded(seller, maxBidder)
maxBidder ! reply; seller ! reply
} else {
seller ! AuctionFailed
}
receiveWithin(timeToShutdown) {
case Offer(_, client) => client ! AuctionOver
case TIMEOUT => running = false
}
}
}
}
}
--- Listing 3.2: オークションサービスの実装 ---
#co(){
--- Listing 3.2: Implementation of an Auction Service ---
}
#co(){
The preceding discussion gave a flavor of distributed programming in Scala. It might seem that Scala has a rich set of language constructs that support actor processes, message sending and receiving, programming with timeouts, etc. In fact, the opposite is true. All the constructs discussed above are offered as methods in the library class Actor. That class is itself implemented in Scala, based on the underlying thread model of the host language (e.g. Java, or .NET). The implementation of all features of class Actor used here is given in Section 17.11.
}
ここまでの話は、Scala における分散プログラミングの雰囲気を示したものです。Scala にはアクタープロセス、メッセージ送受信、タイムアウトのあるプログラミングなどをサポートする豊富な言語構文があるように見えたかもしれません。実際は全く逆です。これまでに議論した言語構文はすべてクラス Actor のライブラリ内でメソッドとして提供されています。そのクラス自身が Scala で実装されており、基礎となるホスト言語 (つまり Java や .NET) のスレッドモデルに基づいています。ここで使った Actor クラスの特徴すべての実装については、17.11 節で述べられています。
#co(){
The advantages of the library-based approach are relative simplicity of the core language and flexibility for library designers. Because the core language need not specify details of high-level process communication, it can be kept simpler and more general. Because the particular model of messages in a mailbox is a library module, it can be freely modified if a different model is needed in some applications. The approach requires however that the core language is expressive enough to provide the necessary language abstractions in a convenient way. Scala has been designed with this in mind; one of its major design goals was that it should be flexible enough to act as a convenient host language for domain specific languages implemented by library modules. For instance, the actor communication constructs presented above can be regarded as one such domain specific language, which conceptually extends the Scala core.
}
ライブラリベースのアプローチの利点は、相対的な、コア言語の簡潔さとライブラリ設計者への柔軟性にあります。コア言語では高レベルなプロセス間通信の詳細を規定する必要がないので、簡潔、一般性を保つことができます。メールボックス内メッセージのこのモデルは単なるライブラリモジュールなので、あるアプリケーションが異なるモデルを必要とする場合は、自由に変更できます。しかしながら、このアプローチはコア言語に対して、必要とされる言語の抽象化を使いやすい形で提供できるような十分な表現力を要求します。Scala はこのことを念頭においてデザインされました。Scala デザインの大きな目標の一つは、ライブラリモジュールによって実装される、ドメイン特化言語(DSL)ための使いやすいホスト言語たりうる、柔軟性にあります。たとえば先に示したアクター間通信の構文は、そのようなドメイン特化言語とみることができ、無意識に Scala コアを拡張しています。
#center(){[[前ページ>ExampleChap2]] [[ 3 章>ExampleChap3]] [[目次>ScalaByExample和訳]] [[次ページ>ExampleChap4]]}
----
「actor process model」が不明です。Erlang-styleならば「Actor model」では?
「case object」の説明は無し?
ーーーーー修正案 by ryugate
※ 修正案を取り入れました。
TIMEOUT は特別なメッセージで、Actor の実装そのものにトリガーされます。
→TIMEOUT は特別なメッセージで、Actor の実装そのものによってトリガーされます。
- ご指摘どうもありがとうございます>ryugateさん。 -- tmiya (2008-05-14 06:59:29)
- 「might well」は「~良いのですが」ではなく「~でしょう」ではないでしょうか? -- kariya_mitsuru (2012-09-19 01:00:17)
#comment
2012-09-19T12:41:19+09:001348026079Tutorial_5
https://w.atwiki.jp/tmiya/pages/16.html
[[トップ>トップページ]] > [[チュートリアル和訳>Tutorial和訳]] > [[5 Classes>Tutorial_5]]
#co(){*5 Classes
As we have seen above, Scala is an object-oriented language, and as such it has a concept of class. Classes in Scala are declared using a syntax which is close to Java’s syntax. One important difference is that classes in Scala can have parameters. This is illustrated in the following definition of complex numbers.}
*5 クラス
前に見たように、Scala はオブジェクト指向言語であり、それゆえにクラス*2の概念があります。Scala のクラスは、Java の構文と似た構文で宣言します。重要な違いは Scala のクラスはパラメータを持てるということです。以下の複素数の定義で示します。
class Complex(real: Double, imaginary: Double) {
def re() = real
def im() = imaginary
}
#co(){This complex class takes two arguments, which are the real and imaginary part of the complex. These arguments must be passed when creating an instance of class Complex, as follows: new Complex(1.5, 2.3). The class contains two methods, called re and im, which give access to these two parts.}
この複素数クラスは2つの引数、複素数の実部と虚部を取ります。これらの引数は Complex クラスのインスタンスを生成する時に、&bold(){new} Complex(1.5, 2.3) のように渡されます。このクラスは2つのメソッド、re と im を持ち、実部と虚部へのアクセスを与えます。
#co(){It should be noted that the return type of these two methods is not given explicitly. It will be inferred automatically by the compiler, which looks at the right-hand side of these methods and deduces that both return a value of type Double.}
これら2つのメソッドの戻り値型が明示的に与えられていないことに留意すべきです。コンパイラが自動的に推論し、メソッドの右辺を見て、両者の戻り値型が Double であることを演繹します。
#co(){The compiler is not always able to infer types like it does here, and there is unfortunately no simple rule to know exactly when it will be, and when not. In practice, this is usually not a problem since the compiler complains when it is not able to infer a type which was not given explicitly. As a simple rule, beginner Scala programmers should try to omit type declarations which seem to be easy to deduce from the context, and see if the compiler agrees. After some time, the programmer should get a good feeling about when to omit types, and when to specify them explicitly.}
コンパイラはこの場合のようにいつも型を推論できるとは限りませんし、不運にも、どんな場合に推論できるかできないかを知る簡単な規則はありません。実際には、通常これは問題にはなりません。なぜなら明示的に与えられていない型を推論できない時は、コンパイラが苦情を言うからです。簡単な規則としては、Scala の初心者プログラマは、型を文脈から簡単に演繹できると思った時は型宣言を省略して、コンパイラが同意するか試すべきでしょう。しばらくするとプログラマは、どんな時に型を省略しどんな時に明示的に指定すべきかについて、勘が働くようになるでしょう。
#co(){**5.1 Methods without arguments
A small problem of the methods re and im is that, in order to call them, one has to put an empty pair of parenthesis after their name, as the following example shows:}
**5.1 引数無しのメソッド
メソッド re と im のちょっとした問題は、下記の例のように、呼び出すために名前の後に空の括弧の組を付けなくてはならないことです。
object ComplexNumbers {
def main(args: Array[String]) {
val c = new Complex(1.2, 3.4)
println("imaginary part: " + c.im())
}
}
#co(){It would be nicer to be able to access the real and imaginary parts like if they were fields, without putting the empty pair of parenthesis. This is perfectly doable in Scala, simply by defining them as methods without arguments. Such methods differ from methods with zero arguments in that they don’t have parenthesis after their name, neither in their definition nor in their use. Our Complex class can be rewritten as follows:}
もし実部と虚部にあたかもフィールドのように、空の括弧の組なしでアクセスできたらよいでしょう。Scala ではこれは&italic(){引数無し}メソッドとして定義することで可能です。そのようなメソッドは、引数が0個のメソッドと異なり、宣言時でも使用時でも名前の後ろに括弧を必要としません。わたしたちの Complex クラスは次のように書き換えられます。
class Complex(real: Double, imaginary: Double) {
def re = real
def im = imaginary
}
#co(){**5.2 Inheritance and overriding
All classes in Scala inherit from a super-class. When no super-class is specified, as in the Complex example of previous section, scala.Object is implicitly used.}
**5.2 継承とオーバーライド
全ての Scala クラスはスーパークラスを継承しています。スーパークラスが指定されていない時、例えば前節の Complex クラスの例では、scala.Object が暗黙的に使用されます。
#co(){It is possible to override methods inherited from a super-class in Scala. It is however mandatory to explicitly specify that a method overrides another one using the override modifier, in order to avoid accidental overriding. As an example, our Complex class can be augmented with a redefinition of the toString method inherited from Object.}
Scala ではスーパークラスから継承したメソッドをオーバーライドできます。しかし、不用意にオーバーライドされることを避けるために、メソッドをオーバーライドする際には&bold(){override}修飾子が必須です。例では、Complex クラスは Object から継承した toString メソッドを再定義して拡張しています。
class Complex(real: Double, imaginary: Double) {
def re = real
def im = imaginary
override def toString() =
"" + re + (if (im < 0) "-" else "+") + im + "i"
}
#co(){For the sake of completeness,it should be noted that some object-oriented languages do not have the concept of class,but Scala is not one of them.}
#hr(width=80%)
*2 補足しておくと、いくつかのオブジェクト指向言語はクラスの概念を持っていないことに注意すべきですが、Scalaはそういったものの1つではありません。
#center(){[[前ページ>Tutoarial_4]] [[目次>Tutorial和訳]] [[次ページ>Tutorial_6]]}
----
- 5.2の例の「(if (im < 0) "-" else "+")」は「 (if (im < 0) "" else "+")」では? -- kariya_mitsuru (2012-09-19 00:38:21)
#comment() 2012-09-19T00:38:21+09:001347982701ExampleChap2
https://w.atwiki.jp/tmiya/pages/25.html
#co(){
Chapter 2 A First Example
As a first example, here is an implementation of Quicksort in Scala.
}
#setmenu2(ex-r-menu)
* 第 2 章 最初の例
最初の例として、Scala でのクイックソートの実装を示します。
def sort(xs: Array[Int]) {
def swap(i: Int, j: Int) {
val t = xs(i); xs(i) = xs(j); xs(j) = t
}
def sort1(l: Int, r: Int) {
val pivot = xs((l + r) / 2)
var i = l; var j = r
while (i <= j) {
while (xs(i) < pivot) i += 1
while (xs(j) > pivot) j -= 1
if (i <= j) {
swap(i, j)
i += 1
j -= 1
}
}
if (l < j) sort1(l, j)
if (j < r) sort1(i, r)
}
sort1(0, xs.length - 1)
}
#co(){
The implementation looks quite similar to what one would write in Java or C. We use the same operators and similar control structures. There are also some minor syntactical differences. In particular:
- Definitions start with a reserved word. Function definitions start with def, variable definitions start with var and definitions of values (i.e. read only variables) start with val.
- The declared type of a symbol is given after the symbol and a colon. The declared type can often be omitted, because the compiler can infer it from the context.
- Array types are written Array[T] rather than T[], and array selections are written a(i) rather than a[i].
- Functions can be nested inside other functions. Nested functions can access parameters and local variables of enclosing functions. For instance, the name of the array xs is visible in functions swap and sort1, and therefore need not be passed as a parameter to them.
}
この実装は Java や C で書くものと大変よく似ています。Scala では同じ演算子、似た制御構造を使います。ただし、少しばかり構文的な違いがあります。具体的には、
- 定義は予約語で始まります。関数定義は def で始まり、変数定義は var で始まり、値 (すなわち読み出し専用の変数) の定義は val で始まります。
- シンボルの型宣言は、シンボルとコロンの後に書きます。型宣言は省略できることもあります。コンパイラが文脈から推論するからです。
- 配列の型は T[] ではなく Array[T] と書き、配列の指定は a[i] ではなく a(i) と書きます。
- 関数は他の関数の内側に入れ子にできます。入れ子になった関数は、それを包む関数のパラメータやローカル変数にアクセスできます。次の例では、配列 xs は関数 swap と sort1 から見えるため、それらにパラメータとして渡す必要はありません。
#co(){
So far, Scala looks like a fairly conventional language with some syntactic peculiarities. In fact it is possible to write programs in a conventional imperative or object-oriented style. This is important because it is one of the things that makes it easy to combine Scala components with components written in mainstream languages such as Java, C# or Visual Basic.
However, it is also possible to write programs in a style which looks completely different. Here is Quicksort again, this time written in functional style.
}
今までのところ、Scala は構文に少し変わった点があるものの、かなり普通の言語のように見えます。実際、プログラムを書くのは、普通の命令型(手続き型)でもオブジェクト指向でも可能です。これは重要なことです。なぜならこれは、Scala のコンポーネント(部品)を、Java、C#、Visual Basic などの主流言語で書かれたコンポーネントと組み合わせることを容易にする仕掛けの一つだからです。
しかし、プログラムをまったく違うスタイルでも書けます。クイックソートを再び、今度は関数型プログラミング風に書いてみます。
def sort(xs: Array[Int]): Array[Int] = {
if (xs.length <= 1) xs
else {
val pivot = xs(xs.length / 2)
Array.concat(
sort(xs filter (pivot >)),
xs filter (pivot ==),
sort(xs filter (pivot <)))
}
}
#co(){
The functional program captures the essence of the quicksort algorithm in a concise way:
- If the array is empty or consists of a single element, it is already sorted, so
return it immediately.
- If the array is not empty, pick an an element in the middle of it as a pivot.
- Partition the array into two sub-arrays containing elements that are less than, respectively greater than the pivot element, and a third array which contains elements equal to pivot.
- Sort the first two sub-arrays by a recursive invocation of the sort function.1
- The result is obtained by appending the three sub-arrays together.
}
関数型プログラミングはクイックソート・アルゴリズムの本質を簡潔にとらえています。
- 配列が空あるいは要素が1つなら、すでにソートされているので、直ちにそれを返します。
- 配列が空でなければ、配列の真ん中の要素をピボットとして選びます。
- 配列を副配列に分割し、一つにはピボットより小さな要素を、もう一つにはピボットより大きな要素を、そして三つ目の副配列にはピボットと等しい要素を入れます。
- 初めの2つの副配列を sort 関数の再帰呼び出しでソートします(*1)。
- これら3つの副配列をひとつに結合して、結果を得ます。
#co(){
(*1) This is not quite what the imperative algorithm does; the latter partitions the array into two sub-arrays containing elements less than or greater or equal to pivot.
}
(*1) これは命令型アルゴリズムのすることと全く同じという訳ではありません。後者では元の配列を、要素がピボットに対して小さい、あるいは、大きいか等しい、に分けて2つの副配列へ分割します。
#co(){
Both the imperative and the functional implementation have the same asymptotic complexity - O(N log(N)) in the average case and O(N2) in the worst case. But where the imperative implementation operates in place by modifying the argument array, the functional implementation returns a new sorted array and leaves the argument array unchanged. The functional implementation thus requires more transient memory than the imperative one.
}
命令型と関数型の実装のどちらも同じ漸近的計算量 --- 平均的な場合は O(N log(N))、最悪の場合は O(N2) になります。しかし命令型実装が引数の配列自体を変更するのに対して、関数型実装はソートされた新しい配列を返し、引数の配列を変更しません。ですから、関数型実装は命令型実装よりも一時的なメモリを多く必要とします。
#co(){
The functional implementation makes it look like Scala is a language that's specialized for functional operations on arrays. In fact, it is not; all of the operations used in the example are simple library methods of a sequence class Seq[T] which is part of the standard Scala library, and which itself is implemented in Scala. Because arrays are instances of Seq all sequence methods are available for them.
}
この関数型実装によって Scala が配列への関数的操作に特化した言語の様に見えます。実際は違います。この例で用いた操作は、Scala の標準ライブラリの一部である&bold(){シーケンス}クラス Seq[T] のたんなるライブラリメソッドであり、そのライブラリ自身も Scala で実装されています。そして、配列は Seq のインスタンスなのでシーケンスクラスのメソッドはすべて使用できるのです。
#co(){
In particular, there is the method filter which takes as argument a predicate function. This predicate function must map array elements to boolean values. The result of filter is an array consisting of all the elements of the original array for which the given predicate function is true. The filter method of an object of type Array[T] thus has the signature
}
とりわけ、&bold(){述語関数}(predicate function)を引数にとるメソッド filter があります。この述語関数は、配列の各要素に対して真偽値を返す必要があります。filter の結果は配列であり、元の配列の要素で述語関数が真を返すもの全てからなります。Array[T] 型オブジェクトの filter メソッドは、次のような書き方をします。
def filter(p: T => Boolean): Array[T]
#co(){
Here, T => Boolean is the type of functions that take an element of type t and return a Boolean. Functions like filter that take another function as argument or return one as result are called higher-order functions.
}
ここで T => Boolean は関数の型を表わし、その関数は型 T の配列要素を引数とし Boolean 値を返します。filter のように、他の関数を引数としたり結果として返す関数は、&bold(){高階関数}と呼ばれます。
#co(){
Scala does not distinguish between identifiers and operator names. An identifier can be either a sequence of letters and digits which begins with a letter, or it can be a sequence of special characters, such as "+", "*", or ":". Any identifier can be used as an infix operator in Scala. The binary operation E op E' is always interpreted as the method call E.op(E'). This holds also for binary infix operators which start with a letter. Hence, the expression xs filter (pivot >) is equivalent to the method call xs.filter(pivot >). }
Scala は識別子と演算子の名前を区別しません。識別子は「文字で始まる、文字と数字からなる列」でも、「 '+'、'*'、':' のような特殊文字からなる列」でも構いません。Scala ではすべての識別子は中置演算子として使えます。二項演算 E op E' は常に、メソッド呼び出し E.op(E') として解釈されます。これは文字で始まる中置二項演算子にもあてはまります。したがって、式 xs filter (pivot >) は、メソッド呼び出し xs.filter(pivot >) と等価です。
#co(){
In the quicksort program, filter is applied three times to an anonymous function argument. The first argument, pivot >, represents a function that takes an argument x and returns the value pivot > x . This is an example of a partially applied function. Another, equivalent way to write this function which makes the missing argument explicit is x => pivot > x. The function is anonymous, i.e. it is not defined with a name. The type of the x parameter is omitted because a Scala compiler can infer it automatically from the context where the function is used. To summarize, xs.filter(pivot >) returns a list consisting of all elements of the list xs that are smaller than pivot.
}
先のクイックソートプログラムで、filter は無名関数の引数に3回適用されています。最初の引数 pivot > は、引数 x をとり、値 pivot > x を返す関数を表しています。これは&bold(){部分適用された関数}の例です。この関数を、見えていない引数を明示して x => pivot > x と書いても同じです。この関数は無名、つまり名前を付けて定義されていません。引数 x の型が省略されているのは、Scala コンパイラが、関数の使用されている文脈から自動的に推論できるからです。まとめると xs.filter(pivot >) は、リスト xs の要素で pivot より小さいもの全てからなるリストを返します。
#co(){
Looking again in detail at the first, imperative implementation of Quicksort, we find that many of the language constructs used in the second solution are also present, albeit in a disguised form.
For instance, "standard" binary operators such as +, -, or < are not treated in any special way. Like append, they are methods of their left operand. Consequently, the expression i + 1 is regarded as the invocation i.+(1) of the + method of the integer value x. Of course, a compiler is free (if it is moderately smart, even expected) to recognize the special case of calling the + method over integer arguments and to generate efficient inline code for it.
}
最初のクイックソートの命令型実装をよく見ると、二つ目の例で使われている多くの構文が、隠された形ではあるものの、出てきていることが分かります。
たとえば、+、-、< のような「普通の」二項演算子が特別扱かいされていないことが分かります。append などと同じく、その左側にあるオペランドのメソッドです。したがって、式 i + 1 は整数値 i のメソッド + の呼び出し i.+(i) とみなされます。もちろんコンパイラは、整数引数に対するメソッド + の呼び出しを特例とみなし、効率のよいインラインコードを生成できます (もし、ほどほどに賢いコンパイラなら、そうすることが期待されます)。
#co(){
For efficiency and better error diagnostics the while loop is a primitive construct in Scala. But in principle, it could have just as well been a predefined function. Here is a possible implementation of it:
The While function takes as first parameter a test function, which takes no parameters and yields a boolean value. As second parameter it takes a command function which also takes no parameters and yields a result of type Unit. While invokes the command function as long as the test function yields true.}
効率と、より良いエラー診断ができるように、while ループは Scala の組み込み構文となっています。しかし原理的には、たんなる事前定義された関数であってもよいのです。次は考えうる実装です。
def While (p: => Boolean) (s: => Unit) {
if (p) { s ; While(p)(s) }
}
この While 関数は最初のパラメータとしてテスト関数をとり、そのテスト関数はパラメータをとらずブール値を返します。二番目のパラメータとしてコマンド関数をとり、そのコマンド関数もパラメータをとらず、Unit 型の結果値を返します。While 関数はテスト関数が真を返す限り、コマンド関数を呼び出します。
#co(){
Scala's Unit type roughly corresponds to void in Java; it is used whenever a function does not return an interesting result. In fact, because Scala is an expression-oriented language, every function returns some result. If no explicit return expression is given, the value (), which is pronounced "unit", is assumed. This value is of type Unit. Unit-returning functions are also called procedures. Here's a more "expression-oriented" formulation of the swap function in the first implementation of quicksort, which makes this explicit:
}
Scala の Unit 型は概ね Java の void に相当し、関数が特定の結果(戻り値)を返さない場合に使います。実際のところ、Scala は式指向の言語なので、すべての関数は何かしら結果を返します。もし明示的な戻り値が与えられなければ、値 () --- "unit" と発音します --- が肩代わりします。この値は Unit 型です。Unit を返す関数もまた、手続き(procedure)と呼ばれます。クイックソートの最初の実装中の swap 関数をさらに「式指向」な形にして、これを明示します。
def swap(i: Int, j: Int) {
val t = xs(i); xs(i) = xs(j); xs(j) = t
()
}
#co(){
The result value of this function is simply its last expression - a return keyword is not necessary. Note that functions returning an explicit value always need an "=" before their body or defining expression.
}
この関数の結果(戻り値)は単に最後の式です。キーワード return は必要ありません。注意点として、明示的に値を返す関数は常に、「=」を本体あるいは定義式の前に必要とします。
#center(){[[前ページ>ExampleChap1]] [[ 2 章>ExampleChap2]] [[目次>ScalaByExample和訳]] [[次ページ>ExampleChap3]]}
----
ーーーーー 修正案 by ryugate
この述語関数は配列の要素をブール値に写像させる必要があります。
→ (filterの引数である) この述語関数は配列の要素をブール値に写像させる必要があります。
filter の結果は与えられた述語関数が真となる元の配列のすべての要素からなる配列です
→filter の結果は元の配列の要素のなかで与えられた述語関数が真となるすべての要素からなる配列です
- ご指摘どうもありがとうございます>ryugateさん。 -- tmiya (2008-05-14 06:59:09)
- おぉ。コメント欄があったのですね。直接書いちゃってスミマセン。気づいたら、またコメントさせていただきます。 -- ryugate (2008-05-19 21:53:05)
- いえ、直接本文に追記して頂いてOKです。wikiに慣れてない人の為のコメント欄です。 -- tmiya (2008-05-25 23:26:44)
- sort関数の一番外の{}は無くてOKでは? ifの単文なので。 -- Lyc (2010-01-10 06:43:43)
- repl環境だとエラーになりますね -- tksk (2010-07-25 02:20:32)
ーーーーー HELP
上の「文字で始まる文字と数字からなる列」の修正を求めます。
- 「Unit を返す関数もまた、手続き(procedure)と呼ばれます」は「Unit を返す関数は、手続き(procedure)とも呼ばれます」ではないでしょうか? -- kariya_mitsuru (2012-09-19 00:36:20)
#comment
2012-09-19T00:36:20+09:001347982580Tutorial_1
https://w.atwiki.jp/tmiya/pages/12.html
[[トップ>Top]] > [[Tutorial>Tutorial和訳]] > [[1 はじめに>Tutorial_1]]
#co(){
*1 Introduction
This document gives a quick introduction to the Scala language and compiler. It is intended for people who already have some programming experience and want an overview of what they can do with Scala. A basic knowledge of object-oriented programming, especially in java, is assumed.
}
*1 はじめに
この文書は Scala 言語とそのコンパイラについて簡単に紹介するものです。ある程度のプログラミング経験があり、Scala で何ができるか概要を知りたい人向けです。 オブジェクト指向プログラミングの基礎知識(とくに Java での)を前提としています。
#center(){[[目次>Tutorial和訳]] [[次ページ>Tutorial_2]]}
----
- この文書はScala言語とそのコンパイラへの手っとり早い入門です。ある程度プログラミングの経験があり、Scalaでなにができるのか概要を知りたい人向けです。オブジェクト指向プログラミングの基礎知識(とくにJavaでの)を想定しています。 -- いいまわしの工夫 (2008-12-09 10:22:41)
- 素早く入門>手早く紹介 -- ktr (2009-04-06 12:24:00)
- qqqqqqqqqqqqqqq -- qqqqqqqqqqqq (2012-01-19 14:30:42)
#comment()
2012-01-19T14:30:42+09:001326951042sid-annot-a
https://w.atwiki.jp/tmiya/pages/143.html
* Scala アノテーション (Internals of Scala Annotations)
#center(){Lukas Rytz}
#center(){2010 年 1 月 27 日}
#right(){[[英語PDF >http://www.scala-lang.org/sites/default/files/sids/rytz/Wed,%202010-01-27,%2015:10/annots.pdf]]}
* 目次
1 はじめに (Introduction)
2 生成定義上のアノテーション (Annotations on synthetics)
- 2.1 フィールド、ゲッターとセッター (Fields, Getters and Setters)
- 2.2 オブジェクト (Objects)
- 2.3 トレイト (Traits)
-- 2.3.1 例 (Example)
3 内部表現 (Internal Representation)
- 3.1 型チェックの前 (Before Type-Checking)
- 3.2 型チェックの後 (After Type-Checking)
4 アノテーション探訪 (Visiting Annotations)
- 4.1 ツリー:トラバーサ/トランスフォーマ (Trees : Traverser/Transformer)
- 4.2 型:TypeMap (Types : TypeMap)
5 バイトコード中のアノテーション (Annotations in bytecode)
- 5.1 Scala アノテーション (Scala annotations)
- 5.2 Java クラスファイルアノテーション (java classfile annotations)
-- 5.2.1 Java クラスファイルアノテーションの解析 (Parsing of java classfile annotations)
6 FAQ
7 事前定義アノテーション (Pre-defined annotations)
* 1 はじめに (Introduction)
このドキュメントは Scala コンパイラにおいてアノテーションがどのように表現され、処理されるか記述します。この内容は、アノテーションを使う Scala コンパイラプラグインの開発者だけでなく、コンパイラの開発者自身にとっても重要です。
Scala には 2 種類のアノテーションがあります。:シンボルアノテーションと型アノテーションです。シンボルアノテーションは、宣言または定義、例えば、クラス、メソッド、フィールド、型メンバー、ローカル値(local values)、パラメータあるいは型パラメータに付けることができます。:
@ann class C[@ann T](@ann x: T) {
@ann def f = {
@ann val x = 1
x
}
}
型アノテーションは、型に付けられます(対応する型は「アノテーション付き」と言われます)。帰属(ascription)構文を使う式上のアノテーションは、内部的には、推論された式の型に対するアノテーションとして表されることに注意してください。
def f(x: Int @ann) = { // 型アノテーション
val r = (x + 3): @ann // 式の型 : ''Int @ann''
val s = 4: Int @ann
r + s
}
次章では、2 種類のアノテーションがコンパイラの異なるフェーズにおいてどのように表されるか、アノテーションがどのようにバイトコードにセーブされるか、そして Java アノテーションとの互換性がどのようにして得られるか説明します。
* 2 生成定義上のアノテーション
(Annotations on synthetics)
多くの場合、Scala コンパイラは与えられたソース定義に対して追加のエンティティを作成します。例えば、公開フィールドに対するゲッターとセッター、あるいはオブジェクトのメソッドに対する静的フォワーダ(static forwarder)です。本章では、アノテーションがそれら生成定義(synthetic definition)へどのようにコピーされるか説明します。
** 2.1 フィールド、ゲッターとセッター
(Fields, Getters and Setters)
Scala では一つのフィールド定義が、取り囲むクラス中に 6 つのエンティティを生成することがあります。:
- 実際の(非公開)フィールド。
- ゲッターおよびセッターメソッド。
- もしフィールドが @BeanProperty あるいは @BooleanBeanProperty のアノテーション付きなら、ビーン式ゲッターおよびセッターメソッド。
- もしフィールドが、例えば class(val x: Int)のように、クラスの val-パラメータとして定義されているなら、コンストラクタパラメータ。
デフォルトでは、フィールド上のアノテーションは実際のフィールドにだけ加えられます。この振る舞いはパッケージ scala.annotation.target 中のメタアノテーション(@field、@getter、@setter、@beanGetter、@beanSetter、@param)を使って制御できます。
これらメタアノテーションを使う 1 つの場面は、それらをフィールドアノテーションのアノテーション型に付けることです。例えば次の定義では、アノテーション @Id は、最終的にはコンパイラが生成するビーン式ゲッター getX() (フィールド上ではありません)に付けられます。
class C {
@(javax.persistence.Id @beanGetter) @beanProperty val x = 1
}
構文をより軽量にしコード複製を減らすために、アノテーション型に対して型エイリアスを定義し、そこにターゲットアノテーションを加えても構いません。:
object ScalaJPA {
type Id = javax.persistence.Id @beanGetter
}
class A {
@ScalaJPA.Id @BeanProperty val x = 0
}
Scala で定義されたアノテーションについて、メタアノテーションを用いてアノテーションクラス自身をアノテートすることで、デフォルトのアノテーションターゲットを記述できます。
@getter @setter class myAccessorAnnot extends StaticAnnotation
** 2.2 オブジェクト (Objects)
The members of an object named t are compiled into a classfile named t$ (the module class). Additionally, if the object is not nested (i.e. defined inside a class or another object), a companion class named t is generated for Java compatibility which contains static forwarder methods to t$.
t という名前のオブジェクトのメンバーは、t$ という名前のクラスファイルへコンパイルされます(モジュールクラス)。さらに、もしオブジェクトがネストされていなければ(つまり、クラスあるいは他のオブジェクト内で定義されている)、Java との互換性のために、t$ への静的フォワーダメソッドを含む名前 t のコンパニオンクラスが作成されます。 これら静的フォワーダは、モジュールクラスにおけるそれら対応物と同じアノテーションを持っています。
注意 : 静的フォワーダは Scala コンパイラのバックエンドで作成され、コンパイラシンボルはありません。ですから、Java アノテーションとしてバイトコードに書き込まれるクラスファイルアノテーション以外では、それらのアノテーションを見ることはありません。
オブジェクトそれ自身のアノテーションは、コンパニオンクラス(t)の型シンボルにではなく、モジュールの項シンボル(term symbol)とモジュールクラス(バイトコードの t$)の型シンボルに加えられます。; ですから静的フォワーダを含むクラスは、オブジェクトのアノテーションを持ちません。
同様に、ケースクラス C 上のアノテーションは、コンパニオンオブジェクト C あるいはそのモジュールクラス C$ 上にではなく、クラスシンボルとクラスファイル C 中にあります。
** 2.3 トレイト (Traits)
トレイト T は通常、インターフェース T と、T の具象メンバーの実装を含む名前 T$class の実装クラスへコンパイルされます。 T の定義上のアノテーションは、実装クラス中の対応する定義にコピーされます。
トレイトを拡張しているクラスのミックスインされたメンバーは、トレイト中のそれらの対応物と同じアノテーションを持ちます。
トレイト自身に対するアノテーションは、実装クラスにコピーされません。
*** 2.3.1 例 (Example)
次は、クラスファイルアノテーション Ann を使う例です。 Scala ソースコードは次です。:
@Ann trait T {
@Ann def f: Int = 0
@Ann var x: Int = 1
}
class C extends T
コンパイルされたコード(java 構文)は、次の形になります。:
@Ann interface T {
@Ann int f();
@Ann int x();
@Ann void x_=(int y);
}
class T$class {
@Ann static int f() { return 0; }
}
class C extends T {
@Ann int _x = 1 // mixed-in field
@Ann int x() { return _x; } // mixed-in getter
@Ann void x_=(int y) { _x = y; } // mixed-in setter
@Ann int f() { return T$class.f(); } // mixed-in method f
}
* 3 内部表現 (Internal Representation)
** 3.1 型チェックの前 (Before Type-Checking)
型チェックの前に、アノテーションは AST(抽象構文木) の一部として表現されます。; アノテーションは単なるコンストラクタ呼び出しであることに注意してください。 シンボルアノテーションは、例えば次のように、定義の Modifiers に保存されます。:
@ann def f: Int = 1
// DefDef(Modifiers(flags = 0,
// annots = List(Apply(Select(New(Ident("ann")), "<init>"),
// List())))), // 引数なし
// "f", // 名前
// List(), // 型パラメータなし
// List(), // 値パラメータなし
// Int, // 戻り値型
// Literal(Constant(1)))
パーサーは、型アノテーションおよびアノテーション帰属構文(ascription)に対して、もし複数のアノテーションがあるときには、ネスト可能な Annotated ツリーを生成します。
def f: Int @ann = 1
// DefDef(NoMods,
// "f", // 名前
// List(), // 型パラメータなし
// List(), // 値パラメータなし
// Annotated(Apply(Select(New(Ident("ann")), "<init>"), List()),
// Int),
// Literal(Constant(1)))
** 3.2 型チェックの後 (After Type-Checking)
型チェックの間に、アノテーションは AST から取り除かれ、クラス AnnotationInfo のインスタンスとして、対応するシンボルあるいは型に付けられます。
case class AnnotationInfo(
atp: Type,
args: List[Tree],
assocs: List[(Name, ClassfileAnnotArg)])
An AnnotationInfo mainly consists of the annotation type tpe and the arguments args represented as compiler trees. These compiler trees are in the form as they leave the type-checking phase, i.e. they are attributed with symbols and types.
AnnotationInfo は主に、アノテーション型 tpe と、コンパイラツリーとして表わされた引数 args からなります。 これらのコンパイラツリーは、型チェックフェーズの後の形です。つまりシンボルと型で属性付けられています。 それらはコンパイラの後のフェーズで書き換えられないことに注意してください。
もしアノテーション型 atp がクラスファイルアノテーション(5.2参照)なら、3 つめの引数 assocs はアノテーション引数を表す名前/値ペアのリストです。クラスファイルアノテーションは Java アノテーションとの互換性のためだけに存在します。: Scala で定義され、かつ java 互換な方法で保持する必要のないアノテーションについては、assocs リストは常に空です。
シンボルアノテーションは、メソッド annotations:List[AnnotationInfo]を使って、対応する定義のシンボルを介してアクセスできます。:
tree match {
case dd: DefDef =>
val annots = dd.symbol.annotations
...
}
型アノテーションを伴う型は、コンパイラ中では、次の構造を持つ AnnotatedType のインスタンスとして表されます。:
case class AnnotatedType(annotations: List[AnnotationInfo],
underlying: Type,
selfsym: Symbol)
アノテーション付き型 selfsym は、実験的なフィーチャの一部であって、その使用は推奨されません。
* 4 アノテーション探訪 (Visiting Annotations)
** 4.1 ツリー : トラバーサ/トランスフォーマ
(Trees : Traverser / Transformer)
前に述べたとおり、型チェッカはアノテーションを AST から取り除きます。アノテーション引数ツリーはコンパイルされるコードの一部ではなくなるので、それは後のコンパイラフェーズでの変換を一切受けません。
勿論、アノテーション引数ツリーをシンボルの annotations あるいは AnnotatedType から抽出することで、それに対してトラバーサあるいはトランスフォーマを手作業で適用することはできます。
** 4.2 型 : TypeMap (Types:TypeMap)
A type map is a utility which maps a type transformation function (Type => Type) over a type and all its parts.
型マップ(type map) は、型とその全ての部分上に型変換関数(Type => Type)をマップするユーティリティです。型マップは Scala コンパイラで頻繁に使われます。アノテーション付き型の場合は、アノテーション引数ツリーもその型の一部であり、マッピングはそれらツリー中の型にも適用されます。 しかしこの振る舞いは、型マップ中のフィールド dropNonConstraintAnnotations を true に設定することで禁止できます。
This way all type annotations which are not type constraints (see below), are dropped when applying the type map .
このように、型制約(type constraints 下記参照)ではないすべての型アノテーションは、型マップを適用する時に外されます。
上記の振る舞いは、アノテーション付き型が型チェックフェーズの後で変わることを意味します。: アノテーション引数ツリー中の型は変わるかもしれず、完全に無くなることもあり得ます。同様に、型消去フェーズはすべての型アノテーションを削除することに注意してください。
TypeConstraint のサブ型であるアノテーションは、型マップ適用時には決して取り除かれません。これらのアノテーションはプラグイン化可能な型システム、例えば数字に単位を付加する型システム : Int @dim(Unit.kg)、の実装に使うことができます。:
* 5 バイトコード中のアノテーション
(Annotations in bytecode)
** 5.1 Scala アノテーション (Scala annotations)
バイトコード中にセーブされるアノテーションについて、アノテーションクラスは StaticAnnotation に適合しなければなりません。シンボルとアノテーション付き型は、対応するコードが現在コンパイル中かどうか、あるいはそれらがバイトコードから再構築されているかどうかにかかわらず、同じ静的なアノテーションを持ちます。
** 5.2 Java クラスファイルアノテーション
(java classfile annotations)
そのサブ型が ClassfileAnnotation であるアノテーションは、標準的な Java クラスファイルアノテーションとしてクラスファイルに書き込まれます。java で(@interface を使って)定義されたアノテーションクラスは、クラスファイルアノテーションとしてマークされるので、それらは常にこの方法で発行されます。
As required by the java platform, an argument to a classfile annotation has to be either a compile-time constant, a nested classfile annotation or an array of one of them .
java プラットフォームからの要求により、クラスファイルアノテーションへの引数は、コンパイル時定数であるネストしたクラスファイルアノテーションか、あるいはそれらの 1 つからなる配列でなければなりません。
Scala コンパイラは、アノテーションクラスの @Retention (*1) 値が何であっても、RuntimeVisibleAnnotation としてクラスファイルアノテーションを発行することに注意してください。
また、Scala コンパイラは、あらゆるアノテーションをすべてのエンティティに付けることを許し、アノテーションクラスの @Target (*2) 値をチェックしないことにも注意してください。
(*1) 参照 http://java.sun.com/j2se/1.5.0/docs/api/java/lang/annotation/Retention.html
(*2) 参照 http://java.sun.com/j2se/1.5.0/docs/api/java/lang/annotation/Target.html
*** 5.2.1 Java クラスファイルアノテーションの解析
(Parsing of java classfile annotations)
Scala コンパイラによって発行されたのではないクラスファイルを解析するとき、クラスファイルアノテーションのいくつかは読み取られ、対応するシンボルに付けられます。
- クラス、フィールド、メソッド上の RuntimeVisibleAnnotations (@Retention RUNTIMEをもつ java アノテーション) は調べられます。
- パラメータの上のクラスファイルアノテーションは調べられません。
- RuntimeInvisibleAnnotations (@Retention CLASSFILEをもつ java アノテーション) は決して調べられません。
* 6 FAQ
アノテーション中で補助コンストラクタの定義/使用は許されますか? はい。唯一の警告は、AnnotationInfo がどのコンストラクタが使ったかを明らかにしないということです。
アノテーションクラスは、他のアノテーションを拡張できますか? はい。
アノテーション中で名前付き引数を使うとどうなりますか? 引数が名前付きのスタイルで指定されなければならないクラスファイルアノテーションを除き、名前付き引数の使用はおそらく予期しない結果を招きます。名前付き引数を伴うアノテーションコンストラクタ呼び出しの型チェック時、それは次の形のブロックに変換されます。:
{
val x$1 = arg1
...
val x$n = argn
annot(x$1, ..., x$n)
}
このアノテーションに対する AnnotationInfo は、型チェック (x$1, ..., x$n) の後にアノテーション引数を使って構成されます。ですからアノテーションの実際の引数は失われます。
アノテーションコンストラクタ中でデフォルト引数を定義/使用できますか? はい。しかしそれは、名前付き引数を使う場合と似た予期しない結果を招きます。生成された AnnotationInfo の引数ツリーは、実際のアノテーション引数ではなく、名前付き適用ブロックのローカル値への参照です。
* 7 事前定義アノテーション (Pre-defined annotations)
Scala 言語仕様書[1]の 11 章で、すべての事前定義されたアノテーションをリストし、それらの振る舞いを説明しています。
* 参照 (References)
[1] Odersky, M. Scala 言語仕様書、バージョン 2.7
オンライン入手先 http://www.scala-lang.org/docu/files/ScalaReference.pdf
2011-05-06T09:24:01+09:001304641441sid-earlydef-a
https://w.atwiki.jp/tmiya/pages/142.html
* 事前初期化メンバー定義 (Early Member Definitions)
&bold(){Copyright c 2008-2009, Ingo Maier & Anders Bach Nielsen}
#right(){[[英語PDF >http://www.scala-lang.org/sites/default/files/sids/rytz/Wed,%202010-01-27,%2015:10/annots.pdf]]}
* 抄録 (Abstract)
このドキュメントは、ミックスイン合成時のオブジェクト初期化という難題に対処する、事前初期化子(early initializers)と抽象事前初期化メンバー(abstract early members)に関する完全なセマンティクススを提案します。
* 目次
1 モチベーション (Motivation)
- 1.1 目標 (Goals)
- 1.2 構文変更 (Syntax change)
-- 1.2.1 抽象事前初期化メンバー (Abstract early members)
-- 1.2.2 コンテキスト (Context)
-- 1.2.3 コンストラクタパラメータとの比較 (Comparison to constructor parameters)
-- 1.2.4 クラス中の抽象事前初期化メンバー (abstract early members in classes)
-- 1.2.5 初期化の順番 (Initialization Order)
-- 1.2.6 オーバーライド解決 (Overriding resolution)
-- 1.2.7 未解決問題 : メンバー昇格 (Open question :Member promotion)
- 1.3 この提案書の範囲を越えた将来の目標 (Future goals that extend beyond this proposal)
2 言語仕様変更 (Language specification changes)
- 2.1 SLS 5.1 節
-- 2.1.1 SLS 5.1.1 節
-- 2.1.2 SLS 5.1.3 節
-- 2.1.3 SLS 5.1.4 節
-- 2.1.4 SLS 5.1.5 節
-- 2.1.5 SLS 5.1.6 節
- 2.2 SLS 5.2 節
3.実装 (Implementation)
* 1 モチベーション (Motivation)
Scala では、クラス C のメンバーの初期化順は、 C のサブクラスと異なることがあります。それは、クラス線形化とオーバライドの順番に依存します。あるクラス中のフィールド宣言で、その初期値が同じクラス中の他の(非 lazy、非 final)フィールド値に依存するものについて考えてみます。クラスの文脈によって、参照されるフィールドは使われる前に既に初期化されているか、あるいは、別の文脈ではそうでないかもしれず、最終的に予期しない初期化値あるいは NullPointerExceptions をもたらすかもしれません。
初期化順をもっと分かり易くするために、事前初期化定義(early definition)が導入されました。それらは、他の非事前初期化フィールドより前に初期化されるものとして、ある特定のフィールドに旗を立てるのに使用できます。トレイトはコンストラクタ・パラメータを持っていませんから、事前初期化フィールド(early initialized fields)の一般的な使用ケースは、トレイト内の他のフィールドに対するパラメータとしての使用です。ある特定のフィールドを常に早く初期化することは、現在はできません。メンバー初期化子(member initializers)中でフィールドを使うクラスあるいはトレイトに関して、メンバーが使われる前に初期化されるという保証は、一般にそのサブクラスにまかされています。我々はまさにこの目的に対して抽象事前初期化フィールド(abstract early fields)の導入を提案します。それらはトレイトのクラスコンストラクタパラメータの代役を務め、そのセマンティクスはこのただ一つの用途で広く使われます。
さらに、Scala 言語仕様書では、複数の基底トレイトからの事前初期化定義がどういう順番で評価されるか曖昧です。我々は、事前初期化メンバーが現れるすべての状況をカバーする、事前初期化定義のより正確なセマンティクススを提案します。
この章の残りは、この提案書の意欲的な目標をカバーし、それら目標を際立たせる使用ケースとこの提案書を越える将来の目標に関する節を含みます。
** 1.1 目標 (Goals)
- Scala 言語における事前初期化定義ブロックのセマンティクススを提示します。
- 事前初期化定義ブロック中の抽象メンバーを含め、this を扱えるようにセマンティクススを拡張します。
- クラス階層構造中の事前初期化定義ブロック中の具象および抽象メンバーの評価順を記述します。
- SLS (Scala 言語仕様書)に必要な変更を提示し、事前初期化定義ブロック中の事前初期化定義と抽象メンバーの正確な記述を与えます。
- ここで提案された変更のサンプル実装を提示します。
** 1.2.構文変更 (Syntax change)
次は、現在の SLS からとった事前初期化定義の例です。:
trait T {
val name:String
val msg = "How are you, " + name
}
class C extends {
val name = "Bob"
} with T {
println(msg)
}
msg が初期化される前に、メンバー name が正しく初期化されることを保証するために、クラス C は事前初期化定義中で name を定義します。
まず初めに、構文の変更を提案します。現在の構文には少し驚きます。なぜなら、with の後にクラスを書ける可能性を導入しているからです。以前は、クラス定義中で with キーワードの後にはトレイトだけが許されていて、extends キーワードの後にただ一つの直接の基底クラスを書く必要がありました。さらにまた現在の構文は、無名クラスのインスタンス化中では、広範囲の先読みが潜在的に必要とされます。次の 2 つのインスタンス化を考えてみます。:
new { val x = 2 } with A
new { val x = 2 }
最初のは、事前に初期化されたメンバー x を用いて型 a のオブジェクトをインスタンス化し、他方 2 つめは、構造型のインスタンスを生成します。
改訂された構文では、上例は次のようになります。:
trait T {
val name:String
val msg = "How are you, " + name
}
class C extends T {
val name = "Bob"
super
println(msg)
}
今度は、事前初期化定義は通常のメンバー宣言と同じブロックを共有します。しかし、キーワード super によって分離されており、それはスーパーコンストラクタを呼び出す場所を示します。しかしこれは、Java におけるような実際の super 呼び出しではありません。テンプレート中に super が現れなければ、すべてのメンバーが事前ではないと仮定されます。
*** 1.2.1 抽象事前初期化メンバー (abstract early members)
上記の構文変更に加え、はじめに言及した問題を扱うために抽象事前初期化定義を含めることを提案します。抽象事前初期化メンバーは、サブクラスの事前初期化定義ブロックで実装されなくてはなりません。前節の例は、今度は、次のように書き直せます。:
trait T {
val name:String
super
val msg = "How are you, " + name
}
class C extends T {
val name = "Bob"
super
println(msg)
}
事前初期化定義は、同じテンプレート中の他のすべての非事前初期化定義の前に評価されるので、トレイト T 内でメンバー name は常に msg 定義中で使用される前に初期化される、と仮定できます。
*** 1.2.2 コンテキスト (Context)
現在は、事前初期化定義中の this への参照は、実際にはテンプレートのすぐ外側の this を参照します。抽象事前初期化定義と提案された構文変更により、このスコープ規則は、次例で示すような驚くべき結果をもたらします。
val name = "Zaphod"
trait T {
val name: String
super
}
trait S extends T { this: S =>
val msg = "What's up, " + this.name
super
println("msg");
}
new S { val name = "Doc"; super } // "What's up, Zaphod" と印字
実際、既存のスコープ規則では、トレイト S 中の this.name はトレイト T の定義の直前に定義された値を参照します。事前初期化定義 ed の右辺が this 参照を通して構築中のオブジェクトを実際に参照できるが、同じテンプレート中の ed の前に定義されたメンバーにのみアクセスできるように、規則の変更を提案します。
上記ルールの結果、:もし基底トレイトからの事前初期化メンバーが具象の事前初期化定義のために必要なら、それは繰り返されなければなりません。このことを次例で示します。:
trait T1 {
val name: String
super
}
trait T2 extends T1 {
val name: String // 省略すると、次で name が未定義となる
val msg = "What's up, " + name + "?"
super
}
*** 1.2.3 コンストラクタパラメータとの比較
(Comparison to constructor parameters)
抽象事前初期化メンバーは本質的に、トレイトのクラスコンストラクタパラメータとして役に立ちます。次の例は両方とも正しく、クラス D とトレイト S は同じ順番でそれらのメンバーを初期化します。:
class D(val name: String) extends T {
val msg = "What about " + name + "?"
println(msg)
}
new D("Bob")
trait S extends T {
val name: String
super
val msg = "What about " + name + "?"
println(msg)
}
new S { val name = "Bob"; super }
*** 1.2.4 クラス中の抽象事前初期化メンバー
(Abstract early members in classes)
抽象クラス中の場合と同様に、具象クラス中の抽象事前初期化メンバーも文法違反です。コンパイラは、すべての抽象事前初期化メンバーに対する引数を追加で受け取るために、クラス中のすべてのクラスコンストラクタを抽象事前初期化定義を使って修正する必要があるでしょう。突き詰めると、クラスは、事前初期化メンバーと同じ目的で使えるコンストラクタ引数を既に持っています。クラスコンストラクタ引数は、テンプレートの事前初期化定義部のスコープ内にありますから、上の例が示すように、ユーザーは事前初期化メンバーからコンストラクタへ容易に乗り替えできます。
*** 1.2.5 初期化の順番 (Initialization Order)
SLS は現在、クラス中の事前初期化定義はスーパークラスのコンストラクタが呼び出される前に評価される、と定義しているだけです。複数の基底トレイトからの事前初期化定義が評価される順番を定義していません。次の例を考えてみます。:
trait T1 {
val name: String
val msg = "What's up, " + name + "?"
super
}
trait T2 {
val name: String = "Doc"
super
}
class C extends T1 with T2
これを意図した通りに動作させるには、T2 の name は T1 メッセージの前に初期化されなくてはなりません。
We propose a simple solution that is in line with the order of class constructor invocations, which are called bottom-up in a class's linearization. We define the preinitialization of a trait as the evaluation of the early definitions in its template in the order they are defined. A class preinitializes its base traits bottom-up in class linearization order up to the first base class B before it calls its super class (B!) constructor. Since the linearization of C above is [C,T2,T1] bottom-up, the above now works as intended.
クラスコンストラクタ起動の順番 --- クラス線形化中でボトムアップと呼ばれる --- を用いて一直線に並べる、単純な解決を提案します。我々はトレイトの事前初期化を、テンプレート中でそれらが定義された順番で事前初期化定義を評価する、と定義します。クラスは、クラス線形化順のボトムアップでその基底トレイトを事前初期化します。その順番は、最初の基底クラスを B とすれば、そのスーパークラス(B!)コンストラクタを呼び出す前に、B の事前初期化を行います。上記 C の線形化はボトムアップ [C,T2,T1] ですから、今度は意図したとおりに動作します。しかし、次を考えてみます。:
class C1 extends T2 with T1
T1's msg would be initialized before name. We disallow the above example, by adding the following rule. For an early definition ed in a class T extended by a concrete class C, every early member refered to in ed's right hand side must be implemented in a class T1 (possibly C) that is below T in C's linearization. Note that there does not exist such a rule for non-early members: an abstract member x in a base class T can be implemented by both a class T1 that comes below or above T in the concrete class's linearization.
T1 の msg は name の前に初期化されます。我々は、次のルールを加えることで、上例を許しません。具象クラス C によって拡張されたクラス T 中の事前初期化定義 ed について、ed の右辺中で参照されるすべての事前初期化メンバーは、C の線形化中で T より下位の T1 (Cでも可) クラス中で実装されなくてはなりません。非事前初期化メンバーに対してはこのようなルールが存在しないことに注意してください。:基底クラス T 中の抽象メンバー x は、T より下位の T1、あるいは具象クラスの線形化中で T の上位にくるクラス T1 の両方で実装できます。
*** 1.2.6 オーバーライド解決 (Overriding resolution)
事前初期化メンバーをオーバライドすることは簡単です。次を考えてみます。:
trait T1 {
val name: String = "Doc"
val msg = "What's up, " + name + "?"
super
println(msg)
}
trait T2 extends T1 {
override val name: String = "Bob"
super
}
class C extends T1 with T2 // "What's up, Bob?" と印字
ここで、T1 からのメンバー name は T2 によってオーバライドされます。オーバーライド解決は通常のクラスメンバに対するのと同じです。 質問 : オーバライドされた初期化指定子を評価しますか? プログラマが初期化指定子中の副作用を当てにできるようになるので、イエスの答えが多いでしょう。この SIP の残りでは、我々はイエスと仮定します。事前初期化クラスメンバーの完成。
しかし、加えなければならない付加的なチェックが 1 つあります。次の場合を考えてみます。:
trait T1 {
val name: String
super
}
trait T2 extends T1 {
override val name: String = "Bob"
super
}
class C1 extends T1 {
val name: String = "Doc"
super
}
class C2 extends C1 with T2
これまで議論したことから、T2 が C1 からの name をオーバライドするので、C2 中の メンバー name は "Bob" に初期化されると仮定できます。残念なことに、この順番を保証する適切な実装は見当たりません。C2 のコンストラクタは最初に T2 を事前初期化し、次に C1 のコンストラクタを呼び出します。それは今度は、メンバー name を "Doc" に設定します。我々は C1 に name を再び設定しないように知らせる必要がありますが、それは現実的ではないと思われます。その代わりに、オーバーライド規則を 1 つ加えます。: 事前初期化定義は、(非トレイトの)スーパークラスからの他の事前初期化定義をオーバライドできない。このルールは基本的に、クラスのすべての事前初期化メンバーは自動的に final である、と述べています。
*** 1.2.7.未解決の問題 : メンバー昇格
(Open question : Member promotion)
原理上は、非事前初期化メンバーは、サブクラス中で事前初期化メンバーへ昇格させることができます。逆は不可です。
trait T1 {
val name: String = "Doc"
super
val msg = "What's up, " + name + "?"
}
trait T2 extends T1 {
// 正しい: 以後、安全に以下を使える
override val msg: String = "What about me?"
super
println("I have " + msg.length + " letters for you.")
}
trait T3 extends T1 {
// 正しくない: msg の前に name が初期化される保証がない
override
val name: String = "Bob"
}
後のケースは、サブクラス化原則に違反しますから、当然です。このことは、サブクラス中のメンバーに新しい制約を課します。
The first case is allowed for the opposite reason: an early member can be accessed in the same way as a non-early member but additionally in other early definitions. This is in line with class constructors that can turn class members into class parameters .
最初のケースは、逆の理由で許されます。: 事前初期化メンバーは、他の事前初期化定義中で追加されたもの以外は、非事前初期化メンバーと同じ方法でアクセスできます。それはクラスコンストラクタの直線的な並びの中にあり、クラスメンバをクラスパラメータのように使うことができます。
残念なことに、現在、JVM 上でこれを実行可能な実装は見当たりません。ここでオーバーライド解決が問題となります。現在の実装方式(下記参照)とトレイトコンストラクタ($init$ メソッド)の作成方法のため、上記 T2 中の msg フィールドは T1 の init メソッドによって事前初期化された後に上書きされます。これをうまく動作させるために、現在のテンプレート初期化方式から、次に記述したものと似たような方法に移る必要があります。トレイト実装クラスは、(ただ 1 つではなく)フィールド毎に 1 つの init メソッドを持つようにする。クラスコンストラクタは正しい順番で init メソッドを呼び出し、必要な場所で割り当てを行う責任を持つ。 たとえば、線形化された init 呼び出しからの副産物として得る代わりに、自分でオーバライド解決を実行する。
** 1.3.この提案書の範囲を越えた将来の目標
(Future goals that extend beyond this proposal)
- トレイトのコンストラクタパラメータ
- 事前初期化定義ブロック中のメソッド定義と型パラメータ
* 2 言語仕様変更 (Language specification changes)
本章では、この提案書に起因する SLS との差分をすべて記述します。変更は 5 章に制限されます。
** 2.1 SLS 5.1 節
ClassTemplate ::= ClassParents [TemplateBody]
TraitTemplate ::= TraitParents [TemplateBody]
TemplateBody ::= [nl] '{' [SelfType] [EarlyDefs] TemplateStat
{semi TemplateStat} '}'
EarlyDefs ::= EarlyDef {semi EarlyDef} 'super' semi
EarlyDef ::= {Annotation} {Modifier} PatVarDef
| AbsEarlyDef
AbsEarlyDef ::= {Annotation} {Modifier} 'val' ValDcl
| {Annotation} {Modifier} 'var' VarDcl
テンプレートは、トレイトあるいはクラス、オブジェクトあるいはシングルオブジェクトの振る舞いと初期状態、型シグニチャを定義します。テンプレートはインスタンス生成式、クラス定義とオブジェクト定義の一部を形成します。テンプレート sc with mt1 with ... with mtn { earlydefs super stats } は、テンプレートのスーパークラスを定義するコンストラクタ呼び出し sc、テンプレートのトレイトを定義するトレイト参照 mt1 ... mtn (n >= 0)、事前初期化定義シーケンス earlydefs、テンプレートの追加のメンバー定義と初期化コードを含む文並び stats 等から成ります。
Template Evaluation gets a new first point:
The preinitialization of a trait consists of the evaluation of its early definition sequence .
New first point: All base traits in the template's linearization up the template's super class denoted by sc are preinitialized. Preinitialization happens in linearization order, bottom-up, i.e., trait mtn gets preinitialized first .
Then, the superclass constructor sc is evaluated .
テンプレート評価(Template Evaluation)は新提案です。:
トレイトの事前初期化は、その事前初期化定義シーケンスの評価から成ります。
新提案: テンプレートの(sc によって表される)スーパークラスへ至る、テンプレート線形化中のすべての基底トレイトは事前初期化されます。事前初期化は線形化順、ボトムアップに起きます。すなわち、トレイト mtn が最初に事前初期化されます。
次にスーパークラスのコンストラクタ sc が評価されます。
*** 2.1.1 SLS 5.1.1
この節はまったく変わりません。
*** 2.1.2 SLS 5.1.3
クラスメンバについての節は、事前初期化メンバーに関することと、「普通の」メンバーと事前初期化メンバーの連携方法を含まなければなりません。
*** 2.1.3 SLS 5.1.4
事前初期化定義に関して、次のオーバーライドに関する制限が適用されます。
- M と M'は共に事前初期化メンバーであるか、あるいは共に非事前初期化メンバーでなければなりません。
- もし M がオーバーライドする事前初期化メンバーなら、オーバライドされたメンバー M'は非トレイトの基底クラスのメンバーであってはなりません。
*** 2.1.4 SLS 5.1.5
この節はまったく変わりません。
*** 2.1.5 SLS 5.1.6
この節は削除され、上で示したように、直接 5.1 に組み込まれます。
** 2.2 SLS 5.2 節
我々は遅延評価事前初期化メンバー(lazy early members)を拒否する必要があります。質問: 事前初期化変数(early vals)を伴う遅延評価 val (lazy vals)をオーバライドできますか? 原理的には、イエス(Ingo)でしょう。しかし実装はどうでしょうか?
lazy 修飾子は非事前初期化変数定義に適用されます。・・・
* 3 実装 (Implementation)
事前初期化子は、各トレイト T と
val x = e
の形の各具象の事前初期化定義に対して、次の形の静的な事前初期化子メソッドを T の実装クラス T$に 1 つ加えます。:
def $preinit$x(args) = e
ここで args は、すべての事前初期化メンバーと e 中で参照されるコンストラクタ・パラメータからなるパラメータリストです。
クラス C 中の各クラスコンストラクタに対して、スーパークラスコンストラクタを呼び出す前に、拡張されたトレイトの事前初期化子メソッドを呼び出すシーケンスを加えます。次の形のテンプレートをもつクラス C と、
sc with Ti ... Tn
線形化 [C,Tn,...,Ti,...T1,D,...] (ここで、D はスーパークラスコンストラクタ呼び出し sc によって表されるクラス) に対して、事前初期化子の呼び出しシーケンスは次のようになります。
val vn1 = Tn.$preinit$en1(argsn1)
...
val vnm = Tn.$preinit$enm(argsnm)
...
val v11 = T1.$preinit$e11(args11)
...
val v1p = T1.$preinit$e1p(args1p)
ここで eij はトレイト Ti 中の j 番目の事前初期化メンバーであり、argsij は事前初期化メンバーと eij の各定義が依存するコンストラクタパラメータです。eij のいくつかは同じメンバーを表しているかもしれません。それらはオーバライドされたメンバーを表します。実際には、単にそのようなメンバーの最初の事前初期化子に関する処理結果をセーブするだけです。preinit 呼び出しの後、preinit 呼び出しの結果を最終的にそれら各メンバーに割り当てるリストを加えます。:
en1 = vn1
...
e1p = v1p
オーバーライド解決の順番のせいで、preinit にそれら自身の値を割り当てさせることはできないことに注意してください。さらに、それらは初期値を返さなければなりません。なぜなら、JVM は、スーパークラスコンストラクタが呼び出される前に、クラスメンバを読むことを許さないからです。
2011-05-02T09:36:19+09:001304296579sid-lib-a
https://w.atwiki.jp/tmiya/pages/137.html
* Scala 改善ドキュメントライブラリ
Scala Improvement Documents Library
#right(){[[原ページ>http://www.scala-lang.org/sids]]}
以下は、本家の SID (Scala Improvement Documents Library) をいくつか訳したものです。誤訳もありそうなので参考程度にご覧ください。断り無く編集してください。気がついたことその他は、本ページ下方に投稿してください。 尚、特に Statusが draft(草案)のものは、内容が一部盛り込まれていることもあれば、全く盛り込まれていないこともあります。また、今後、下記の訳と最新英語PDFのバージョンが合致しないことも考えられます。
SID#1 Revision:7 、Date:2010-01-22、Status : active
&space(9)[[名前付き引数とデフォル引数 (Named and Default Arguments)>sid-named-a]]
SID#3 Revision:5 、Date:2010-07-20、Status : active
&space(9)[[新コレクションクラス (New Collection Classes)>sid-coll-1a]]
&space(9)参考:Scala 2.8 コレクション API  (1,br,16)( ttp://eed3si9n.github.com/scala-collections-doc-ja/collections_0.html )
&space(9)参考:Scala コレクションのアーキテクチャ  (1,br,16)( ttp://eed3si9n.github.com/scala-collections-impl-doc-ja )
SID#4 Revision:1 、Date:2009-06-02、Status : draft
&space(9)[[事前初期化メンバー定義(Early Member Definintions) >sid-earlydef-a]]
SID#5 Revision:2 、Date:2010-01-27、Status : active
&space(9)[[Scala アノテーション(Internals Scala Annotations)>sid-annot-a]]
SID#7 Revision:3 、Date:2010-01-22、Status : active
&space(9)[[Scala 2.8 配列 (Scala 2.8 Arrays) >sid-array-a]]
&space(9)参考:Scala 2.8 コレクション API 配列  (1,br,16) ttp://eed3si9n.github.com/scala-collections-doc-ja/collections_38.html )
SID#8 Revision:1 、Date:2009-11-02、Status : draft
&space(9)[[Scala Swing 概観 (Scala Swing Overview)>sid-swing-a]]
SID#9 Revision:2 、Date:2010-05-06、Status : draft
&space(9)[[Scala 型専用化 (Scala Specialization)>sid-special-a]]
--------
#comment()
2011-05-02T09:24:21+09:001304295861sid-swing-a
https://w.atwiki.jp/tmiya/pages/133.html
&aname(swing,option=nolink){ }
* scala.swing パッケージ
#center(){The scala.swing package}
#center(){Ingo Maier}
#center(){November 1, 2009}
#right(){[[英語PDF>http://www.scala-lang.org/sites/default/files/sids/imaier/Mon,%202009-11-02,%2008:55/scala-swing-design.pdf]]}
* 1 紹介 (Introduction)
scala.swing パッケージは Java Swing クラスの周りに、薄いラッパーのコレクションを提供します。 我々の目標は、Scala 中で自然に使えると同時に、既存の Swing プログラミング経験のある Java プログラマーも気にいる、Java Swing に十分近いライブラリを配布することです。
このドキュメントは Scala における Swing プログラミングの簡単な紹介であり、scala.swing 中の利用可能なクラスを概観し、そのデザイン原則を示します。最後に、ライブラリ拡張者が追加ラッパーを書くときに従うべきガイドラインを示します。 このドキュメント全体を通して、scala.swing ディストリビューションの一部である scala.swing.test パッケージ中の例を参照します。
読者には、少なくとも Scala についての基本的知識と、Java Swing あるいは似たような GUI ツールキットのある程度の経験が必要とされます。Scala の一般的な紹介については、http://www.scala-lang.org/node/1305 を参照してください。scala.swing の基本を含む、より包括的な紹介については、本『Programing in Scala』[1]を推奨します。Java Swing の良いチュートリアルは http://java.sun.com/docs/books/tutorial/uiswing にあります。
* 2 はじめ (Getting started)
次の図 1 で示す例は、コンパイルして簡単な "Hello World" scala.swing プログラムを生成します。
import swing._
object HelloWorld extends SimpleSwingApplication {
def top = new MainFrame {
title = "Hello, World!"
contents = new Button {
text = "Click Me!"
}
}
}
#ref(http://homepage3.nifty.com/ankomochi/figure/helloWorld.jpg,,center)
#center(){図 1 : 簡単な "Hello World!" アプリケーションのフレーム}
たいていの簡単な scala.swing アプリケーションは、上記の HelloWorld のような、クラス SimpleSwingApplication(*1) を拡張するメインオブジェクトから成ります、メインオブジェクトは、引数をとらず、Frame あるいはこの場合 MainFrame を返す、メソッド top を実装する必要があります。フレームは、閉じるボタンやリサイズハンドルのような飾りをもつ、OS の通常の表示窓です。 MainFrame はフレームであり、閉じるときに自動的にアプリケーションを停止し、上記のようにただ 1 つのフレームだけを必要とする大部分のアプリケーションで使われています。
(*1) SimpleGUIApplication は Scala 2.8 では クラス SimpleSwingApplication に置き換わりました。
起動時は、クラス SimpleSwingApplication は Swing フレームワークの初期化を行い、メソッド top によって返されるフレームを開きます。上の実装は、メンバーの初期化に scala.swing の標準的な構文を使ってメインフレームを生成します。本当は、次の式をもつ無名クラスを生成しています。
new MainFrame {
title = "Hello, World!"
contents = new Button {
text = "Click Me!"
}
}
無名クラス構文により、弓カッコ内のクラス MainFrame のすべてのメンバーにアクセスできます。フレームのタイトルプロパティを文字列 "Hello、World!" に設定し、フレームのコンテンツを、"Click Me!" と書いてある、クリックされても何もしないシンプルなクリックボタンに設定しています。
多くのクラスでは、Java Swing が提供するものに似た少数の便利なコンストラクタを提供し、コンポーネント生成をより簡潔にしています。上記のボタンインスタンス化は、次のようにも書けます。
contents = new Button("Click Me!")
しかし、より多数のプロパティ設定では、プロパティに明示的に名前をつけると scala.swing コードが分かり易くなることがよくあるので、一般的には無名クラス構文の使用が望ましいです。
** 2.1 クラス SwingApplication と SimpleSwingApplication
上記の クラス SimpleSwingApplication は、デフォルト main メソッドを実装する SwingApplication を拡張しています。それはさらに次の 3 つのメソッドを提供します。:
def startup(args: Array[String])
def quit() { ... }
def shutdown() { ... }
メソッド startup は、デフォルト main メソッド実装中で呼び出され、クライアントによって実装される必要があります。これは Swing イベントディスパッチングのスレッド上で呼び出されます。メソッド quit は、アプリケーションを潔く停止するときに呼び出されるべきです。それは、アプリケーションを最終的に終了させる前に、最後のステップとしてメソッド shutdown を呼び出します。リソースをクリーンアップし、シャットダウンで特定のコードを実行する必要があるクライアントは、メソッド shutdownを上書きすべきです。
サブクラス SimpleSwingApplication は、メソッド startup のデフォルト実装を提供していて、それは、クライアントが実装したメソッド top によって返さるフレームを表示します。
我々の手法が、http:://java.sun.com/developer/technicalArticles/javase/swingappfr/ において導入された Java Swing アプリケーションフレームワークに似てはいるがよりシンプルであることに言及しておきます。
* 3 レイアウトコンポーネント (Laying out components)
前に、フレームにボタンを加える方法を見ました。より複雑なレイアウトに対しては、コンテナ内にコンポーネントをネストし、特定のルールに従って、それら子コンポーネントを配置します。Java Swing と同じく scala.swing にも、コンテナコンポーネントに 2 つの型、ペインとパネルがあります。ペインは、可能なら各ペインに特有の装飾と一緒に、決まった数の子コンポーネントを表示します。1 つの例は分割ペインです。それは 2 つのコンポーネントを水平あるいは垂直に並べて表示し、そのあいだにノブをいれて、ユーザーが 2 つのコンポーネントの広さを調整できるようにします。もう 1 つの例はスクロールペインです。それはスクロールバーのついたキャンバスの中にただ一つのコンポーネントを置きます。
他方、パネルは、クライアントがある程度カスタマイズ可能なレイアウトルールに従って、任意数の子コンポーネントを配置するコンテナです。
** 3.1 強く型付けされた、簡潔なコンテナインターフェース
(A strongly typed, concise container interface)
Java Swing では、コンテナは特定のレイアウトマネージャからは切り離されています。Java Swing コンテナを生成するために、次のように、クライアントは適切なレイアウトマネージャと一緒に JPanel コンポーネントを使います。
val panel = new JPanel()
panel.setLayout(new BorderLayout())
java.awt.Component 基底クラスの add メソッドの 1 つを使って、コンポーネントを加えることができます。:
def add(comp: Component, constraints: Object): Component
def add(comp: Component, index: Int): Component
def add(comp: Component,constraints: Object, index: Int): Component
次は、上のパネルに、Borderlayout マネージャ ルックスのボタンを加えます。:
panel.add(new JButton("click me"), BorderLayout.CENTER)
JPanel.add メソッドは、与えられたコンポーネントとそのレイアウト制約を利用できるようにする共通のインターフェースを介して、レイアウトマネージャを呼び出します。Java Swing と異なり、scala.swing 中のコンテナとレイアウトマネージャは繋がっています。これにより、インターフェースがより簡潔になり、コンパイル時により多くの型エラーを検出できます。Java Swing では、上記 add メソッのインターフェースで示されるように、レイアウト制約は、型 Object あるいは Int でなければなりません。型引数としてレイアウトマネージャと制約型をとるジェネリックなパネルクラスは、もっと良く型付けできますが、より複雑なインターフェースとなります。
class JGenericPanel[L <: LayoutManager[C],
C <: LayoutConstraint] extends JComponent {
protected def layout: L
def add(comp: Component, constraints: C) = {
...
layout.addLayoutComponent(comp, constraints)
...
}
}
さらにまた、レイアウトマネージャとコンテナにオブジェクト合成を使っても、何も柔軟性を得られません。なぜなら、レイアウトマネージャを変えることは必然的に、すべての子コンポーネントに対して相容れない制約を再設定することになるからです。これは、新しいコンテナを生成しすべての子コンポーネントを読み出すのと同じ量の仕事になります。
上記 2 つの議論から、次の統一されたレイアウトコンテナのインターフェースが導かれます。:
trait LayoutContainer extends Container.Wrapper {
type Constraints <: AnyRef
val layout: Map[Component, Constraints] = ...
protected def constraintsFor(c: Component): Constraints
protected def areValid(c: Constraints): (Boolean, String)
protected def add(comp: Component, c: Constraints)
}
コンポーネントをレイアウトマップに加えることで、それらを、型 Constraints の制約に関連づけできます。
** 3.2 LayoutContainer の拡張
(Extending LayoutContainer)
LayoutContainer の具象サブクラスは、抽象 Contraints 型を修正し、ラッパーを基盤のピアへ接続する次の 3 つのメソッドを追加実装する必要があります。:
&bold(){constraintsFor(c: Component): Constraints}
与えられたコンポーネントの現在の制約を入手します。
&bold(){areValid(c: Constraints): (Boolean, String)}
与えられた制約が有効かどうか決定するための実行時チェックを行い、もし有効でなければ、Option型でエラーメッセージを提供します。
&bold(){add(comp: Component, c: Constraints)}
このコンテナに、与えられた制約をもつ与えられたコンポーネントを加えます。
Subclass BorderPanel, for instance, assigns the Contraints type to a custom Position enumeration and obtains the constraints from the underlying layout_manager while performing some casting operations.
たとえば、サブクラス BorderPanel は、カスタムの Position 列挙に Contraints 型を割り当て、そしていくつかのキャスト操作を実行している間に、基盤のレイアウトマネージャから制約を入手します。有効な Position 列挙値だけなので、メソッド areValid はどのような実行時チェックもする必要が無く、常に (true、" ") を返します。BorderPanel にコンポーネントを加えるのは、たいていの場合、簡単で、単に適切な引数で JPanel.add メソッドを呼び出すことです。:
class BorderPanel extends Panel with LayoutContainer {
import BorderPanel._
def layoutManager = peer.getLayout.asInstanceOf[BorderLayout]
type Constraints = Position.Value
protected def constraintsFor(comp: Component) =
wrapPosition(layoutManager.getConstraints(comp.peer).asInstanceOf[String])
protected def areValid(c: Constraints): (Boolean, String) = (true, "")
protected def add(c: Component, l: Constraints) {
peer.add(c.peer, l.toString)
}
}
* 4 イベントへの反応 (Reacting to events)
scala.swing 中のイベントの監視と反応は、発行者とリアクタ(反応者)のコンセプトをベースにしています。あらゆる scala.swing コンポーネントは、自動的に、イベントを発行する発行者およびイベントに反応するリアクタの両方です。図 2 で描かれたシンプルな Celsius から Fahrenheit への変換器をどのように実装できるか見ていきましょう。
最初に、2 つのそっくりなテキストフィールドをもつフレームを生成し、ユーザーが Celsius と Fahrenheit で温度を入力できるようにします。
#ref(http://homepage3.nifty.com/ankomochi/figure/celsius.jpg,,center)
#center(){図 2 : シンプルな Celsius から Fahrenheit への変換器}
object Converter extends SimpleSwingApplication {
def newField = new TextField {
text = "0"
columns = 5
}
val celsius = newField
val fahrenheit = newField
def top = new MainFrame {
title = "Convert Celsius / Fahrenheit"
contents = new FlowPanel(celsius, new Label(" Celsius = "),
fahrenheit, new Label(" Fahrenheit"))
}
}
テキストフィールドは、型 EditDone のイベント発行者であり、このイベントは、ユーザーがフィールドをちょうど編集し終えたことを示します。それらのイベントに反応するために、テキストフィールドを監視するリアクタにリアクションを加える必要があります。SimpleSwingApplication はアプリケーションのデフォルトのグローバルなリアクタであり、それを拡張するメインオブジェクト Converter に、次のコードを入れることができます。
listenTo(fahrenheit, celsius)
reactions += {
case EditDone(`fahrenheit`) =>
val f = Integer.parseInt(fahrenheit.text)
val c = (f - 32) * 5 / 9
celsius.text = c.toString
case EditDone(`celsius`) =>
val c = Integer.parseInt(celsius.text)
val f = c * 9 / 5 + 32
fahrenheit.text = f.toString
}
最初に、クラス Reactor のメソッド listenTo を呼び出すことで、メインオブジェクトが各テキストフィールドからのイベントを待つことを指示します。Reactor は SimpleSwingApplication の基底クラスです。次に、テキストフィールド celsius とテキストフィールド fahrenheit からの EditDone イベントに 2 つのリアクションを加えます。このリアクションはそれぞれ、編集されなかった他方のコンテンツを更新します。イベントは通常ケースクラス、あるいは抽出子(extractor)をもつクラスであり、上記の例のように、クライアントはそれらを容易にパターンマッチできます。
この例の完全なソースコードは、scala.swing テストパッケージにあります。
** 4.1 Java Swing Listeners 対 scala.swing Reactions
上述で見たように、scala.swing 中のイベントはパターンマッチング中でディスパッチされます。各イベントは普通の Scala オブジェクトなので、パターンマッチ方法を決定するただ一つの型を持ちます。これを Java Swing と対比してみてください。Java Swing では、それらの型プラス追加のリスナーメソッドに応じて 2 つのフェーズでディスパッチします。次の例は、Java Swing 中でのマウスクリックイベントの監視方法を示します。
new JComponent {
addMouseListener(new MouseAdapter {
@Override
def mouseClicked(e: MouseEvent) {
System.out.println("Mouse clicked at " + e.getPoint)
}
})
}
ここで、インターフェース MouseListener のすべてのメソッドを実装する便利なクラス MouseAdapter を使って、MouseListener の他メソッドの実装を避けていることに注意してください。
This interface is one example were we first dispatch on the event's Java type (MouseEvent) and then refine the match by implementing one of the listener's methods which all take the same parameter (using listener MouseListener and method mouseClicked).
このインターフェースは、最初にイベントの Java 型 (MouseEvent)に応じてディスパッチし、次に、すべて同じパラメータをとるリスナーのメソッドの 1 つを実装することで(リスナー MouseListener とメソッド mouseClicked を使います)、マッチイングを改善する 1 つの例です。scala.swing において、イベントの型に応じてマッチする等価なものは、次のようです。
new Component {
listenTo(mouse)
reactions += {
case e: MouseClicked =>
println("Mouse clicked at " + e.point)
}
}
効率の点から、マウスイベントはコンポーネント自身ではなく、そのメンバー mouse によって発行されます。そのため、最初にマウス発行者を listenTo しなければなりません。
** 4.2 発行者とリアクタ (Publishers and Reactors)
発行/リアクト システム全体は、クラス Publisher、Reactor、Reactions 中で実装されます。API ユーザーにとって、Publisher 中で唯一興味を引くメソッドは次です。
def publish(e: Event)
これは、登録された全てのリアクタに通知を行います。トレイト Event の唯一の目的は今のところ、あるクラスがイベントを定義することを示すことです。オブジェクトを発行者にするには、クラス Publisher を拡張して、イベントを発行させる上記のメソッドを呼び出すだけです。クラス Reactor 中の次のメソッドを使って、1 つ以上の発行者にリアクタの登録と登録解除を行えます。:
def listenTo(ps: Publisher*)
def deafTo(ps: Publisher*)
クラス Reactor の 3 つめに興味を引くメンバーは次です
val reactions: Reactions
これは、リアクションのコレクションを表します。リアクションは、型 Reactions.Reaction のオブジェクトであり、PartialFunction[Event,Unit] のエイリアスです。上記で、リアクションをメソッド += を使って加える方法を見てきました。クラス Reaction の他の興味を引くメソッド -= は、前に登録されたリアクションを撤去します。両方のメソッドとも、受け取ったリアクションオブジェクトを返します。:
def +=(r: Reactions.Reaction): this.type
def -=(r: Reactions.Reaction): this.type
このことによりクライアントは、一つのリアクションの撤去や付加を構文上連結して書けます。
** 4.3 アクション(Actions)
Java Swing は、イベントへのリアクションとして実行されるコードをカプセル化する、インターフェース javax.swing.Action を提供します。リスナーと異なりますが、しかしアクションもまた、ユーザーインターフェースコンポーネントのいくつかの型に関して役に立つ情報を保存します。しばしば使われるプロパティには、アクションの名称があります。それは通常、ボタンタイトル、あるいはメニュー項目のタイトルと、(各種のボタンとメニュー項目に対するキーボード・ショートカットに使用される)アクションのニーモニックキーとして表示されます。アクションのプロパティの完全なリストについては、http://java.sun.com/docs/books/tutorial/uiswing/misc/action.html を参照してください。
scala.swing では、コンポーネントに似た Java Swing ピア周りのラッパーである Action トレイトによって、アクションを表現します。Action コンパニオンオブジェクトは、いくつかの便利なメンバーを定義しています。次のメソッドにより、クライアントは大変手軽にアクションを生成できます。
def apply(title: String)(block: =>Unit) = new Action(title) {
def apply() { block }
}
次のように書いて、アクションを生成できます。:
Action("Run me") {
println("Someone executed this action.")
}
コンポーネントをアクションに関連づけるには、トレイト Action.Triger を拡張します。次は、クリックボタンにアクションを関連づける 1 つの方法です。
val button = new Button {
action = Action("Click me") {
println("Someone executed clicked button " + this)
}
}
ボタンがアクションを持つのは非常に一般的なので、コンパニオンオブジェクト Button 中に便利なファクトリメソッドがあります。次は、前の例と等価です。
val button = Button("Click me") {
println("Someone executed clicked button " + this)
}
アクションをもつコンポーネントは、アクションの情報を必要なだけ使います。ボタンとメニュー項目は、通常それらのアクションの本体、名前、アクセラレータ・キー、アイコンその他を使いますが、テキストフィールドはアクションの本体のみ使うことがあります。コンポーネントが関連するアクションを持っていないと言うためには、コンポーネントのアクションのデフォルト値である Action.NoAction オブジェクトを使わなくてはなりません。
button.action = Action.NoAction
NoAction をもつコンポーネントは、アクションのプロパティの代わりにそれ自身のプロパティを使います。Java Swing で、NoAction と等価なものは null です。
* 5 List Views と Tables
リストビューとテーブルは、任意数のアイテムを均一な配置で表示するコンポーネントです。リストビューは、各アイテムを単純に水平あるいは垂直に配置し、図 5 のように、各アイテムはしばしば単純な文字列として描画されます。テーブルは要素を格子中に配置し、図 6 のように、オプションで行と列ヘッダーを表示します。
** 5.1 クラス ListView
クラス ListView は、パラメータにアイテムのシーケンスをとる便利なコンストラクタを持っています。リストビューは、次のようにインスタンス化できます。:
val items = List("Lausanne", "Paris", "New York", "Berlin", "Tokio")
val view = new ListView(items)
#ref(http://homepage3.nifty.com/ankomochi/figure/listview.jpg,,center)
#center(){図 3 : ListView コンポーネント}
#ref(http://homepage3.nifty.com/ankomochi/figure/table.jpg,,center)
#center(){図 4 : Table コンポーネント}
Java Swing と対照して、ListView とそのコンストラクタは、アイテム型について多相的です。:
class ListView[A] extends Component {
def this(items: Seq[A]) = ...
...
}
ListView はメンバー選択を定義していて、それによりクライアントは、現在の選択状況を問いあわせできます。たとえば、現在選択されているアイテムのシーケンスです。 クラス ListView がジェネリックであることがどれだけ有益であるか見るために、上の例を少し修正してみましょう。:
case class City(name: String, country: String, population: Int, capital: Boolean )
val items = List(City("Lausanne", "Switzerland", 129273, false),
City("Paris", "France", 2203817, true),
City("New York", "USA", 8363710 , false),
City("Berlin", "Germany", 3416300, true),
City("Tokio", "Japan", 12787981, true))
val view = new ListView(items)
今度は、現在選択されているアイテムを問いあわせて、すぐに操作できる City オブジェクトのシーケンスを受け取ることができます。:
val cityNames = view.selection.items.map(_.name)
Java Swing では、キャストを実行するか、選択されたインデックスのリストを経なければならず、そのどちらもエラーを起こしがちで簡潔でないコードになります。
** 5.2 レンダラ (Renderers)
もし実際に 上記から city オブジェクトを表示するリストビューコードを取り除いて、それをフレームの中に置くと、結果は図 7 のようになるでしょう。
#ref(http://homepage3.nifty.com/ankomochi/figure/city.jpg,,center)
#center(){図 5 : City オブジェクトのリストを表示する ListView コンポーネント}
こうなるのは、デフォルトでは、リストビューはそのアイテム上で toString を呼び出し、文字列を含むラベルとしてそれぞれを表示するからです。City のようなケースクラスに対して、メソッド toString はクラス名とそのコンストラクタ引数からなる文字列を返すので、図 7 に描かれたような結果になります。
形の上では、クラス ListView は、クラス ListView.Renderer のインスタンスを使って各アイテムをレンダー(描画)します。コンパニオンオブジェクト ListView は、クライアントの役に立つ、多数のレンダラ関連のメンバーを持っています。オブジェクト GenericRenderer は、Java Swing によって与えられたデフォルトレンダラのラッパーであり、上記のように、各アイテムに対して toString を呼び出します。
コンパニオンオブジェクト Renderer は、次の合成メソッドを定義しています。:
def apply[A,B](f: A => B)(implicit renderer: Renderer[B]): Renderer[A]
これは、アイテム型 B に対して暗黙のうちに供給されたレンダラを使う、アイテム型 A のレンダラを返します。結果として生じるレンダラは、関数 f を各アイテムに適用することで、型 B のアイテムを入手します。この関数と暗黙の GenericRenderer オブジェクトを使って、もっと良い city ビューを生成できます。:
import ListView._
val view = new ListView(items) {
renderer = Renderer(_.name)
}
オブジェクト ListView の最後の役に立つクラスはクラス AbstractRenderer であり、より精巧なアイテムビューを生成できます。そのコンストラクタは、コンポーネント --- その paintComponent メソッドがアイテムを描画する --- を引数にとります。
abstract class AbstractRenderer[-A, C<:Component](protected val component: C)
extends Renderer[A]
クライアントは、各アイテムにそのコンポーネントを設定するために、次のメソッドを実装する必要があります。:
def configure(list: ListView[_], isSelected: Boolean, focused: Boolean,
a: A, index: Int)
クラス Item のアイテムを表示するビュー用のメソッド configure は、次のように実装できます。:
def configure(list: ListView[_], isSelected: Boolean, focused: Boolean,
icon: Icon, index: Int) {
component.icon = icon
component.xAlignment = Alignment.Center
if(isSelected) {
component.border = Swing.LineBorder(list.selectionBackground, 3)
} else {
component.border = Swing.EmptyBorder(3)
}
}
似たような例が、サンプルパッケージのコンボボックスのデモ中にあります。
** 5.3 クラス Table
クラス Table とそのレンダラの設計は、ListView とそのレンダラの設計に似ています。しかし クラス Table は、特定の用途向けに作られておらず、アイテムのリスト表示や、たとえば表計算ソフト中の 2 次元配列のようなより一般的な格子データ構造にも使えます。ですから、Table のアイテムは、1 つの列、1 つのカラム、あるいはただ 1 つのセルを表現できます。ですから、クラス Table はジェネリックではありません。
In the future, we might add a generic ListTable class tailored to lists of items similar to cass ListView but in a layout with multiple columns .
将来、我々は、クラス ListView に似てはいるが、複数カラムをもつレイアウトでアイテムのリストに仕立てるジェネリックな ListTable クラスを加えるかもしれません。
* 6 カスタム描画 (Custom Painting)
scala.swing 中のカスタムのコンポーネント描画は、Java Swing に非常によく似ています。コンポーネント中でカスタム描画を実行したいほとんどのユーザーは、Component あるいはそのサブクラスの 1 つをインスタンス化し、メソッド paintComponent をオーバライドしたいでしょう。次は、scala.swing サンプルパッケージの LinePainting デモでのやり方です。
new Component {
...
override def paintComponent(g: Graphics2D) = {
super.paintComponent(g)
g.setColor(new Color(100,100,100))
g.drawString("Press left mouse button and drag to paint.",
10, size.height-10)
g.setColor(Color.black)
g.draw(path)
}
}
これは super.paintComponent() を呼び出し、コンポーネントがその背景と飾りを適切に描画することを確実にしています。次に、カスタムの文字列と、ユーザーがマウスをドラッグしている間デモによって維持されるパスを描画します。
次は、クラス Component で提供される利用可能な描画メソッドです。
protected def paint(g: Graphics2D)
protected def paintBorder(g: Graphics2D)
protected def paintChildren(g: Graphics2D)
protected def paintComponent(g: Graphics2D)
メソッド paint は、Swing 描画メカニズムによって呼び出され、コンポーネントを描画し、その標準的な実装では他の 3 つのメソッドを起動して、各コンポーネントに特有の飾り、境界線とその子等を描画します。javax.swing.Component 中の対応するメソッドが Graphics オブジェクトをとるのに対して、これら 3 つのメソッドが Graphics2D オブジェクトをとることに注意してください。内部的には、Swing は、あらゆるイベント中の Graphics2D オブジェクトを処理します。scala.swing シグニチャのおかげで、クラス Graphics2D のより新しいインターフェースにアクセスするために、よく使われるキャスト g.asInstanceOf[Graphics2D] をする必要はありません。
カスタム描画の入門については、http:://java.sun.com/docs/books/tutorial/ uiswing/painting/index.html を訪れてください。:Graphics2D と補助クラスのような、Swing 図形処理 API の包括的な紹介は、http:://java.sun.com/docs/ books/tutorial/2d/index.html にあります。:AWT と Swing 描画メカニズムについての詳細は、http:://java.sun.com/products/jfc/tsc/articles/painting/ で論じられています:
** 6.1 メソッドオーバーライドによるカスタマイズ
(Customizing by overriding methods)
カスタム描画は、メソッドオーバーライドによるより一般的なクラスカスタマイズ方法の 1 例に過ぎません。この方法をラッパーライブラリ中で機能させるために、ピアをインスタンス化するとき、メソッド委譲とミックスイン合成を実行します。
クラス Component 中の描画メソッドの実装は、次のようになります。クラス Component は、次のトレイトを定義します。:
protected trait SuperMixin extends JComponent {
override def paint(g: Graphics) {
Component.this.paint(g.asInstanceOf[Graphics2D])
}
def __super__paint(g: Graphics) {
super.paint(g)
}
このトレイトは、ピアの paint メソッドの呼び出しをラッパーの paint メソッドへリダイレクトし、__super__paint を介してオリジナルの paint メソッドが見えるようします。そうするために、クラス Component は、そのピアへ SuperMixin トレイトをミックスします。:
override lazy val peer: javax.swing.JComponent =
new javax.swing.JComponent with SuperMixin
クラス Component 中のメソッド paint のデフォルト実装は、そのピアのオリジナルの paint メソッドを呼び出します。
def paint(g: Graphics2D) {
peer match {
case peer: SuperMixin => peer.__super__paint(g)
case _ => peer.paint(g)
}
}
循環を避けるために、ミックスインがあるときは(たとえば、ピアが scala.swing によって生成されているとき) __super__paint メソッドを呼び出します。そうでなければ、単にピアの paint メソッドを呼び出します。
paintComponent のような private および protected のメソッドに対しては、ピアのメソッドを呼び出せません。上の Component.paint 実装中の 2 番目のケースを削らなければなりません。その結果、既存の Java Swing コンポーネント用に生成されたラッパーに対しては、そのようなメソッドは何もしません。
* 7 ラッパーを書くためのガイドライン
(Guidelines for Writing Wrappers)
前章では、 scala.swing の基本的なデザイン原則を概観しました。この章では、新しいラッパーを書くときに考慮すべき、多くの点を列挙、要約します。詳細については、示された章を参照してください。
&bold(){Java Swing から離れない}
まず最も重要な原則は、あまりに巧妙にしないことです。オリジナルのデザインから離れないことは、Java Swing に精通した開発者に時間的余裕をもたらします。ピア毎のラッパークラスから始めてください。プロパティは、できる限り同じ名前を維持すべきです。Java のゲッターとセッター( get、set, あるいは booleansに対して is で始まるメソッド)は、Scala ゲッターとセッターに対応するペアに変換し、便利な代入構文と統一アクセス原則を利用できるようにすべきです。他のメソッドは、ピアとラッパーの必要なラッピングとアンラッピングをする、ただの委譲コードであるべきです。この大前提から外れるのは、そうする良い理由がある場合に限ります(例としては、以下を参照)。
&bold(){共通インターフェースの抽出}
Java インターフェースあるいは基底クラスによって表わせない、共通の API を共有する Swing クラスがいくつかあります。1 つの例として、共通のメソッドを共有するが、しかし互いに拡張せず、AWT クラスとは異なる、ウィンドウクラスの部分階層があります(詳細は??参照)。共通インターフェースだけでなく共通ラッパーコード(??)のくくり出しにも、トレイトあるいは(抽象)基底クラスを使ってください。
必要とされるインターフェース(??)にピアを適合させるために、ピアのミックスインを使ってください。もし常にピア生成を管理したければ、トレイトを使ってください。;もしそうしないなら、scala.swing.Oriented 中にあるような構造型を使ってください。
&bold(){オーバライド可能なメソッドにスーパーのミックスインパターンを使う}
If a class can be customized by overriding certain methods such_as in custom component painting (6), create a peer mixin that overrides the desired methods each delegating to the respective wrapper_method. Supply a method that lets the wrapper access the original peer method for the default wrapper implementation (6.1) .
もしクラスを、カスタムコンポーネント描画(6)中のように、ある特定のメソッドのオーバーライドによってカスタマイズできるなら、それぞれ、ラッパーメソッドへ委譲するのに必要なメソッドをオーバライドする、ピアミックスインを生成してください。
ラッパーがデフォルトのラッパー実装(6.1)に代わってオリジナルのピアメソッドにアクセスできるような、メソッドを提供してください。
&bold(){抽象と実装トレイトの対を使う}
Traits that custom widgets can implement in Scala should stay abstract. For a default implementation that delegates to its Java Swing peer, create a trait called Wrapper in the companion_object.
Scala 中でカスタムウィジェットが実装できるトレイトは、抽象のままであるべきです。その Java Swing ピアへ委譲するデフォルト実装に対して、コンパニオンオブジェクト中で Wrapper と呼ばれるトレイトを生成してください。例としては、Container と Scrollable を見てください。
&bold(){Scala コレクション API との統合}
Java Swing と Java 標準コレクションとの統合は不完全です。我々はよりうまくやることを目指します。適切な所ではどこでも、Scala コレクションを使ってください。これはプログラマに 2 つの利益をもたらします。第一に、プログラマはすでに知っている強力な API を使うことができます。第二に、Scala 標準コレクションを要求する他の API を用いたコンポーネントあるいはコンポーネントメンバーを使えます。たとえば、scala.swing 中のコンテナクラスを見てください。
あなたの要求を完全に満たす利用可能なコレクションがないなら、上で述べたと同じ理由から、独自のカスタムインターフェースを生成する代わりに、既存のコレクションをサブクラス化してください。
&bold(){ジェネリック型とメンバー型の使用}
ジェネリック型と抽象メンバー型は、より型安全で、適切に使えば、より便利なそして一貫した API を導きます。リストやコンボボックスのような同じ形のアイテムコンテナは、ジェネリック型の理想的な例なのに対して、テーブルは議論の余地がある例です。我々は、標準的な Table クラスの型安全性よりも柔軟性と簡潔性を選びました。それは、目的にふさわしいがより制限された型安全なテーブルの可能性を排除するものではありません。
Member types are sometimes preferable over type_parameters because they can lead to more concise type signatures, e.g., if the member_type needs not to be refined at use site (*2) .
メンバー型は、型パラメータよりも望ましいことがしばしばあります。なぜなら、たとえばもしメンバー型を使用場所で修正する必要がないなら、メンバー型はより簡潔な型シグニチャで書けるからです(*2)。レイアウトコンテナにはメンバー型があっています。正確な制約とその意味が、LayoutContainer(3.1)のただ 1 つの具象サブクラスに限定されるからです。言い替えると、もしただ一つの適合している基底クラス、たとえば BorderPanel だけがあるなら、一般的に「BorderPanel.Position 制約をもつレイアウトコンテナ」について話をすることは意味がありません。
(*2) もしそれが一般的なら、ジェネリック型の MyClass[Arg] の代わりに、メンバー型の MyClass { type Param <: Arg } といつも書くことになります。
型パラメータはコンボボックスとリストには望ましいものです。なぜなら、コンパイラがコンストラクト時にアイテムの型を推論して欲しいからです。コンストラクタ引数中のメンバー型は参照できないので、それはメンバー型ではできません。複雑な型、あるいは、パス依存型のような Scala のより洗練された型システムフィーチャを使う前に、注意深く理性を働かせてください。我々は、そのようなフィーチャが、scala.swing のような伝統的なスタイルの GUI ツールキットの文脈中で役に立つ場面に出会ったことがありません。
&bold(){便利なコンストラクタあるいはファクトリの提供}
少数のプロパティ設定でいつも生成するコンポーネントに対して、便利なコンストラクタを提供してください。もし引数の意味がそれらの場所と型から明らかなら、名前付き引数は避けてください。1 つの文字列と 1 つの整数をとる単純なコンストラクタは問題ありませんが、3 つの文字列をとるコンストラクタは、そうではないでしょう。
scala.swing.Action のように、クロージャを最後の引数として渡すことに意味があるなら、ファクトリメソッドを提供してください。しかしながら、コンストラクタは、無名クラス構文を用いて便利なコンストラクタをミックスできるので、一般的には望ましいです。
&bold(){フラグを列挙に変える}
Java Swing が整数フラグを使う場所では、列挙を使ってください。Orientation あるいは Alignment のような scala.swing の既存の列挙を再利用してください。
&bold(){ラッパーキャッシュの使用}
ラッパーを自分で記憶しないでください。これは簡単にメモリリークを引き起こしがちです。組み込みキャッシュを使ってください。あなたのラッパーのコンパニオンオブジェクト(??)中にキャッシュメソッドを提供してください。
&bold(){独自クラス用に指定された"null"オブジェクトの生成を検討}
公開インターフェースで null は決して使わないでください。代わりに Option を考える前に、それらの "null 値" を表現するオブジェクトについて検討してください。例としては、クラス scala.swing.Action とそれに対応する scala.swing.Action.NoAction オブジェクト(4.4)を見てください。この手法は null 参照の共通の欠点を避け、ユーザーとフレームワークコードの両方に関して Options ほど冗長ではありません。
* 参考文献 (References)
[1] Martin_Odersky, Lex Spoon, and Bill Venners. Programming in Scala :A Comprehensive Stepby-step Guide.Artima Incorporation, USA, 2008 .
#center(){[[SIDライブラリ>sid-lib-a]] [[ページ先頭>sid-swing-a]]}
2011-04-18T09:21:14+09:001303086074トップページ
https://w.atwiki.jp/tmiya/pages/1.html
&topicpath()
*プログラミング言語 Scala
**このWikiの目的は?
-Scalaのマニュアルの日本語訳を作成する。首尾よく日本語訳が完成したら、www.scala-lang.orgに置いてもらうように頼むつもり。--> 完成したのを連絡したのだけど、全然本家サイトに置いて貰えない...。
-→置いてあるようです。[[Translations of the Scala Manuals>http://www.scala-lang.org/node/274]]
-ScalaのTipsなどの情報をまとめる。
***使い方
-普通に自由にWikiとして自由に編集して下さい。spam投稿で荒れたりするまでは、自由に編集出来る様にしておきます。
-ご意見や不具合などありましたら管理人(tmiya@bu.iij4u.or.jp)までご連絡下さい。
-もう何冊も日本語でのScala解説本が出たので管理人は和訳プロジェクトを放置状態です。このサイトの翻訳文を管理者がPDF化の作業は実施しませんのでご了承下さい。
-チュートリアルと Scala By Exampleの PDF化は一応できました(BAREISHO)。
-言語仕様 2.8 の PDF化(一部英語入り)も一応できました(BAREISHO)。
**Scalaとは?
Scalaは、Ecole Polytechnique Federale de LausanneのMartin Oderskyらによって開発されたプログラミング言語です。
特徴として、
-オブジェクト指向
-関数型言語
-静的型付け
-DSLに向いた構文規則
-Java, .NETとの相互運用性
があげられます。
**目次
-[[リンク集]]
-[[チュートリアル(A Scala Tutorial for Java Programer(July 13,2010)) >Tutorial和訳]] : [[和訳PDF>http://www.scala-lang.org/node/274]] : [[原文>http://www.scala-lang.org/docu/files/ScalaTutorial.pdf]]
-[[Scala By Example (July 13,2010) 和訳 >ScalaByExample和訳]] : [[和訳PDF>http://www.scala-lang.org/node/274]] : [[原文 >http://www.scala-lang.org/docu/files/ScalaByExample.pdf]]
-[[言語仕様 2.8 (The Scala Language Specificaton 2.8) >Spec2.8和訳]] : [[和訳PDF>http://www.scala-lang.org/node/274]] : [[原文 >http://www.scala-lang.org/docu/files/ScalaReference.pdf]]
-[[Scala By Example のコード>CodeOfScalaByExample]]: Scala By Example のソースコードを実際に実行してみた。
-[[Scala ひと巡り (A Tour of Scala)>TourOfScala0]] ひとまずやりました。
-[[SIDライブラリ(Scala Improvement Document Libaray和訳)>sid-lib-a]]
-[[その他の資料>28etc-a]]
**その他
[[初心者の為のScala練習問題>Scala練習問題]] : 原文 [[Scala exercises for beginners>http://blog.tmorris.net/scala-exercises-for-beginners/]] の和訳
----
- 1から100までの間で素数を表示する方法 -- KS (2009-06-16 00:42:37)
- Scala入門翻訳、大変役に立ちました。ありがとうございます。 -- JJ (2010-06-20 17:55:28)
#comment()
2011-04-15T22:11:55+09:001302873115actor-short-tuto
https://w.atwiki.jp/tmiya/pages/141.html
* Scala アクター:簡易チュートリアル
(Scala Actors: A Short Tutorial)
By phaller
Created 2007-05-24, 08:26
** はじめに (Introduction)
マルチコアプロセッサの到来で並行プログラミングは不可欠になりました。並行性に関する Scala の主要な構文はアクターです。アクターは基本的に、メッセージ交換で通信する並行プロセスです。アクターは、メッセージ送信に関連するメソッドを起動する、アクティブなオブジェクトの形態と見ることもできます。
Scala アクターライブラリは、同期、非同期メッセージ送信の両方を提供します(前者は、複数の非同期メッセージ交換によって実装されます)。さらに、アクターは、リクエストを非同期に処理するフューチャ(*1)を使って通信しますが、その応答を待つことを可能にする表現(フューチャ)を返します。
このチュートリアルの主な目的は、すぐにコンパイルできて Scala 2.4 以降で実行できる、いくつかの完全なサンプルプログラムをウォークスルーすることです。
(*1)訳注:フューチャについては [[Scala By Example 17.3参照>Example17.3]]
** 最初の例 (First Example)
最初の例は、一連のメッセージを交換して終了する 2 つのアクターから成ります。最初のアクターは "ping" メッセージを 2 番目のアクターに送り、するとそれは "pong" メッセージを返送します。( 各受信 "ping"メッセージ に 1 つの "pong" メッセージ)。
はじめにアクターによって送受信されるメッセージを定義します。この場合、シングルトンオブジェクトが使えます(より先進的なプログラムでは、メッセージをパラメータ化します)。パターンマッチィングを使いたいので、各メッセージは case object (ケースオブジェクト)です:
case object Ping
case object Pong
case object Stop
ping アクターは、pong アクターに Ping メッセージを送ることで交換処理を開始します。Pong メッセージは pong アクターからの応答です。ping アクターは、特定の数の Ping メッセージを送り終えると、pong アクターに Stop メッセージを送ります。
Scala アクターライブラリのすべてのクラス、オブジェクトとトレイトは、scala.actors パッケージにあります。このパッケージから Actor クラスをインポートし、拡張して独自のカスタムアクターを定義するのに使います。また、それは多くの役に立つアクター操作を含むので、Actor オブジェクトのすべてのメンバーをインポートします:
import scala.actors.Actor
import scala.actors.Actor._
アクターは、Actor クラスのサブクラスをインスタンス化して生成できる、普通のオブジェクトです。Actor をサブクラス化し、その抽象 act メソッドを実装することで、ping アクターの振る舞いを定義します:
class Ping(count: int, pong: Actor) extends Actor {
def act() {
var pingsLeft = count - 1
pong ! Ping
while (true) {
receive {
case Pong =>
if (pingsLeft % 1000 == 0)
Console.println("Ping: pong")
if (pingsLeft > 0) {
pong ! Ping
pingsLeft -= 1
} else {
Console.println("Ping: stop")
pong ! Stop
exit()
}
}
}
}
}
送るべき Ping メッセージの数と pong アクターは、コンストラクタの引数として渡されます。無限ループ中の receive メソッドの呼び出しは、Pong メッセージがアクターに送られるまで、アクターをサスペンドします。この場合、メッセージはアクターのメールボックスから取り除かれ、対応する、矢印の右側のアクションが実行されます。
pingsLeft がゼロより大きい場合は、! 送信演算子を使って Ping メッセージを pong アクターへ送り、pingsLeft カウンタを減じます。pingsLeft カウンタがゼロになったら、pong アクターへ Stop メッセージを送り、exit()を呼び出して現在のアクター実行を終了させます。
pong アクター用のクラスも同じように定義できます:
class Pong extends Actor {
def act() {
var pongCount = 0
while (true) {
receive {
case Ping =>
if (pongCount % 1000 == 0)
Console.println("Pong: ping "+pongCount)
sender ! Pong
pongCount = pongCount + 1
case Stop =>
Console.println("Pong: stop")
exit()
}
}
}
}
ここで、注目すべき点が 1 つあります。Ping メッセージを受信して、Pong メッセージを sender アクターに送りますが、それは我々のクラスのどこにも定義されていません! 実は、それは Actor クラスのメソッドです。sender を使えば、現在のアクターが最後に受け取ったメッセージを送ったアクターを参照できます。これにより、メッセージの引数として明示的に送信者を渡さなくても済みます。
あとはアクタークラスを定義すれば、それらを使う Scala アプリケーションの生成準備完了です。:
object pingpong extends Application {
val pong = new Pong
val ping = new Ping(100000, pong)
ping.start
pong.start
}
Java スレッドと同じように、アクターは start メソッドの呼び出しで始めます。
*** 動かしましょう! (Let's run it!)
完全な例は、doc/scala-devel/scala/examples/actors/pingpong.scala 下の Scala ディストリビューションに含まれています。 次は、これをコンパイル、実行する方法です:
$ scalac pingpong.scala
$ scala -cp . examples.actors.pingpong
Pong: ping 0
Ping: pong
Pong: ping 1000
Ping: pong
Pong: ping 2000
...
Ping: stop
Pong: stop
** スレッド レスに! (Make it Thread-less!)
アクターはスレッドプール上で実行されます。最初は、4 つのワーカースレッドがあります。もしすべてのワーカースレッドがブロックされて、まだ処理すべきタスクがあれば、スレッドプールは増大します。理想的には、スレッドプールの大きさは計算機の処理系コアの数に一致します。
アクターが、receive のような(あるいは同等の wait)スレッドをブロックする操作を呼び出すと、現在のアクター(self)を実行しているワーカースレッドはブロックされます。これは基本的に、アクターがブロックされるスレッドであることを意味します。たいていの JVM は標準的なハードウェア上では数千スレッド以上は処理できないので、使いたいアクター数への依存は避けたいことでしょう。
スレッド-ブロッキング操作は、新しいメッセージを待つ react (イベントベースの receive の仲間)を使って避けることができます。しかし、支払うべき(通常は少しの)代償があります : react は決して戻ってきません。現実には、メッセージへの応答の終わりに、アクターの計算の残りを含む何らかの関数を呼び出す必要があるということです。while ループ内で react を使ってもうまく機能しないことに注意してください! しかし、ループは一般的ですから、このための特別な、loop 関数形のライブラリサポートがあります。次のように使えます。:
loop {
react {
case A => ...
case B => ...
}
}
react の呼び出しをネストできることに注意してください。それにより、次のようにシーケンスの複数のメッセージを受信できます。:
react {
case A => ...
case B => react { // if we get a B we also want a C
case C => ...
}
}
ping と pong アクターをスレッドレスにするには、たんに while(true)を loop で、receive を react で置き換えれば十分です。例えば、次は pong アクターの修正版 act メソッドです:
def act() {
var pongCount = 0
loop {
react {
case Ping =>
if (pongCount % 1000 == 0)
Console.println("Pong: ping "+pongCount)
sender ! Pong
pongCount = pongCount + 1
case Stop =>
Console.println("Pong: stop")
exit()
}
}
}
** 2 つめの例 (Second Example)
2 つめ例として、作り出された値のシーケンスを読み出す producers の抽象化を書くつもりです。それは標準的なイテレータインターフェースを提供します。
特定の producers は、抽象 produceValues メソッドを実装することで、定義します。個々の値は、produce メソッドを使って作り出します。クラス producer は両方のメソッドを継承します。例えば、行きがけ順(pre-order)のツリーに含まれる値を作り出す producer は次のように定義できます:
class PreOrder(n: Tree) extends Producer[int] {
def produceValues = traverse(n)
def traverse(n: Tree) {
if (n != null) {
produce(n.elem)
traverse(n.left)
traverse(n.right)
}
}
Producer は、2 つのアクター producer アクターと coordinator アクターを用いて実装されています。次は producer アクターの実装方法を示します。:
abstract class Producer[T] {
protected def produceValues: unit
protected def produce(x: T) {
coordinator ! Some(x)
receive { case Next => }
}
private val producer: Actor = actor {
receive {
case Next =>
produceValues
coordinator ! None
}
}
...
}
producer アクターの定義方法に注目してください。今回は、わざわざ Actor の特別のサブクラスを生成して、その act メソッドを実装しようとはしませんでした。その代わり、たんに actor 関数を使ってインラインでアクターの振る舞いを定義します。多分、これはずっと簡潔です! さらに、actor を使って定義したアクターは自動的に始まるので、start メソッドを起動する必要がありません!
それで、producer はどのように動作するのでしょうか? Next メッセージを受け取ると、(抽象) produceVaules メソッドを実行し、それは今度は、produce メソッドを呼び出します。この結果、値のシーケンスが Some メッセージにラップされて coordinator へ送られます。シーケンスのお終いは None メッセージです。Some と None は Scala の標準 Option クラスの 2 つのケース(訳注:ケースクラス/ケースオブジェクト)です。
coordinatorはクライアントからの要求と producer から来る値を同期させます。次のように実装できます:
private val coordinator: Actor = actor {
loop {
react {
case Next =>
producer ! Next
reply {
receive { case x: Option[_] => x }
}
case Stop => exit('stop)
}
}
}
Next メッセージ用のハンドラ中で、受信 Option 値をある要求側 actor へ返すために、reply を使っていることに注意してください。このことは、次のセクションで説明するつもりです。話を元に戻して...
*** イテレータインターフェース (The Iterator Interface)
我々は producers を標準的なイテレータとして使えることを望みます。そのために、驚くなかれ、イテレータ返す iterator メソッドを実装します。その hasNext と next メソッドは、仕事を遂行するために coordinator アクターにメッセージを送ります。見てみましょう:
def iterator = new Iterator[T] {
private var current: Any = Undefined
private def lookAhead = {
if (current == Undefined) current = coordinator !? Next
current
}
def hasNext: boolean = lookAhead match {
case Some(x) => true
case None => { coordinator ! Stop; false }
}
def next: T = lookAhead match {
case Some(x) => current = Undefined; x.asInstanceOf[T]
}
}
非公開の lookAhead メソッドを使ってイテレータロジックを実装します。next 値がまだ未探索の場合は、current 変数は単なるプレースホルダーオブジェクトである値 Undefined を持ちます。
private val Undefined = new Object
ちょっと興味を引くことが lookAhead メソッドの中にあります。current 値が Undefined のとき、これは next 値を手に入れなければならないことを意味します。そのために、同期メッセージ送信演算子 !? を使います。これは coordinatorに Next メッセージを送信しますが、しかし、通常の(非同期)メッセージ送信のように戻る代わりに、coordinator からの応答を待ちます。応答は !? の戻り値です。!? を使って送られたメッセージは、reply を使って応答されます。 sender に単純にメッセージを送っても機能しないことに注意してください! それは、!? がメールボックスの代わりに 非公開の reply チャネルからのメッセージ受信を待つからです。これは "true" 応答を、たまたまメールボックスにある古いメッセージに起因する "fake(偽物)" から区別するために必要です。
producers の例は doc/scala-devel/scala/examples/actors/producers.scala 下の Scala ディストリビューションに同じく含まれています。
2011-04-14T21:03:23+09:001302782603