TourOfScala





Scala ひと巡り (A Tour of Scala)





Published on The Scala Programming Language (http://www.scala-lang.org) .
By admin . Created 2008-07-05, 20:31

Scala は簡潔でエレガント、そして型安全な方法で共通のプログラミングパターンを表現できるよう設計された現代的なマルチパラダイム・プログラミング言語です。
オブジェクト指向の特徴と関数型言語の特質をスムーズに統合します。

Scala はオブジェクト指向言語です

すべての値がオブジェクトである [1]という意味で、Scala は純粋なオブジェクト指向言語です。オブジェクトの型と振る舞いをクラス [2]とトレイト [3]によって記述します。
クラスはサブクラス化 [4]と、多重継承の欠点のない置換えである、柔軟なミックスインベースの合成 [5]メカニズムにより拡張されます。

Scala は関数型言語です

すべての関数が値である[1]という意味で、Scala は関数型言語でもあります。
Scala は無名関数の定義に簡単な構文 [6]を提供します。それは高階関数 [7]をサポートし、関数のネストを可能とし[8]、カリー化 [9]をサポートします。
Scala のケースクラス [10]とパターンマッチィング [11]の組み込みサポートは、多くの関数型プログラミング言語で使われる代数型をモデル化します。


Furthermore, Scala's notion of pattern matching naturally extends to the processing of XML data [12] with the help of right-ignoring sequence patterns [13].
In this context, sequence comprehensions [14] are useful for formulating queries.

さらに、Scala のパターンマッチィングの概念は、右無視シーケンスパターン(right-ignoring sequence pattern) [13]の助けにより、XML データ [12]の処理へ自然に拡張されます。このコンテキストにおいて、シーケンス内包表記(sequence comprehensions) [14]はクエリの定式化に対して役立ちます。

これらの特徴により Scala は、Web サービス[15]のようなアプリケーション開発に理想的なものとなっています。

Scala は静的な型付け言語です

Scala は、抽象化が安全で理路整然とした方法でなされることを静的に強制する、表現力豊かな型システムを備えています。

特に、型システムは次をサポートします。

  • ジェネリッククラス (generic classes [16])
  • 変位指定アノテーション (variance annotations [17])
  • 上/下限 型境界 (upper[18] and lower[19] type bounds)
  • 内部クラスとオブジェクトメンバーとしての抽象型
 (inner classes [20] and abstract types [21] as object members)
  • 複合型 (compound types [22])
  • 明示的に型付けされた自己参照 (explicitly typed self references [23])
  • ビュー (view [24])
  • 多相的メソッド (polymorphic methods [25])

ローカルな型推論 [26]機構により、ユーザーはプログラムに冗長な型情報で注釈を付ける必要がありません。
これらフィーチャーの組み合わせは、プログラミング抽象の安全な再利用とソフトウェアの型安全な拡張に対して、強力な基盤を提供します。

Scala は拡張性に富んでいます

現場では、固有領域のアプリケーション開発はしばしば領域固有の言語拡張を必要とします。Scala は、新たな言語要素をライブラリの形でスムーズに容易に加えることができる言語メカニズムのユニークな組合せを提供します:

  • どのようなメソッドも中置あるいは後置演算子 [27]として使うことができます。そして
  • 要請型(expected type:期待される型)に応じて、クロージャが自動的に構成されます[28](target typing:ターゲットによる型付け)。

両フィーチャーの共同使用は、構文拡張なしの、またマクロのようなメタプログラミングファシリティの助けを借りない、新しい文定義を促進します。

Scala は Java や .NET と相互運用できます

Scala は、ポピュラーな Java 2 実行環境(JRE [29])とうまく相互運用できるように設計されています。
特に、主流のオブジェクト指向 Java プログラミング言語との相互作用は可能な限りスムーズです。
Scala は、Java と同列のコンパイルモデル(ダイナミックなクラスローディング、分割コンパイル)を採用しており、既存の何千もの高品質ライブラリにアクセスできます。
.NET フレームワーク(CLR [30])のサポートも同様に利用できます。


さらに次のページをお読みください。








Scala ひと巡り : 抽象型 (Abstract Types)





Scala では、クラスは値(コンストラクタ・パラメータ)と(もしクラスがジェネリック [16]なら)型でパラメータ化されます。
単に規則に従って、オブジェクトメンバーとして値を持てるというばかりではありません;
値と共に、型はオブジェクトのメンバーです。
さらに、メンバーの両形式とも、具象あるいは抽象で構いません。

次の例は、クラス Buffer のメンバーとして、延期された値定義と抽象型定義の両方を定義しています。

 abstract class Buffer {
   type T
   val element: T
 }


抽象型はその素性が正確には知られていない型です。
上記の例で、我々は、クラス Buffer の各オブジェクトが 型メンバー T を持つことだけを知っています。しかしクラス Buffer の定義は、メンバー型 T がどのような具象(具体的な)型に対応するのかを明らかにしません。
値定義と同じように、サブクラス中で型定義をオーバライドできます。
これにより、(抽象型の、可能な具象インスタンス化を記述する)型境界を厳しくすることで、抽象型についてより多くの情報を明らかにできます

次のプログラムでは、型 T が新しい抽象型 U のサブ型でなければならないと述べることで、バッファ中にシーケンスのみを記憶できるクラス SeqBuffer を得ます:


 abstract class SeqBuffer extends Buffer {
   type U
   type T <: Seq[U]
   def length = element.length
 }


抽象型メンバーをもつトレイトあるいはクラス [2]は、無名クラスのインスタンス化と組合せてしばしば使われます。
このことを示す例として、整数リストを参照するシーケンスバッファを扱う、次のプログラムを見てみます。:


 abstract class IntSeqBuffer extends SeqBuffer {
   type U = Int
 }
 object AbstractTypeTest1 extends Application {
   def newIntSeqBuf(elem1: Int, elem2: Int): IntSeqBuffer =
     new IntSeqBuffer {
          type T = List[U]
          val element = List(elem1, elem2)
        }
   val buf = newIntSeqBuf(7, 8)
   println("length = " + buf.length)
   println("content = " + buf.element)
 }


メソッド newIntSeqBuf の戻り値型は、型 U が toInt に等価になった、トレイト Buffer の特化を参照します。
メソッド newIntSeqBuf 本体内における無名クラスのインスタンス化で、似たような型エイリアスを使っています。
ここで、型 T が List[Int]を参照する IntSeqBuffer の新しいインスタンスを生成します。

抽象型メンバーをクラスの型パラメータに変えることや、その逆も可能であることに注意してください。
次は、上記コードの型パラメータだけを使うバージョンです:


 abstract class Buffer[+T] {
   val element: T
 }
 abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T] {
   def length = element.length
 }
 object AbstractTypeTest2 extends Application {
   def newIntSeqBuf(e1: Int, e2: Int): SeqBuffer[Int, Seq[Int]] =
     new SeqBuffer[Int, List[Int]] {
       val element = List(e1, e2)
     }
   val buf = newIntSeqBuf(7, 8)
   println("length = " + buf.length)
   println("content = " + buf.element)
 }


ここでは変位指定アノテーション [17]を使う必要があることに注意してください;
そうでなければ、メソッド newIntSeqBuf が返すオブジェクトの具象シーケンス実装型を隠せなくなります。
さらにまた、型パラメータを抽象型で置き換えできない場合があります。







Scala ひと巡り : アノテーション (Annotations)





アノテーションは、定義にメタ情報を関連づけます。
単純なアノテーション節は、@C あるいは @C(a1,...,an) の形です。
ここで、C はクラス C のコンストラクタで、scala.Annotation [31]に適合しなくてはなりません。

All given constructor_arguments a1,...,an must be constant_expressions (i.e., expressions on numeral literals, strings, class_literals, Java enumerations and one-dimensional arrays of them) .

与えられたコンストラクタ引数 a1,...,an はすべて、定数式(すなわち、数値リテラル、文字列、クラスリテラル、Java enumとそれらの 1 次元配列上の式)でなければなりません。

アノテーション節は、その後に続く、最初の定義または宣言に適用されます。
1 つ以上のアノテーション節が定義や宣言に先行するかもしれません。
それら節の与えられた順番は重要ではありません。

アノテーション節の意味は処理系依存です。
Java プラットフォームでは、次の Scala アノテーションは標準的な意味を持っています。


   Scala                               Java     
   scala.SerialVersionUID [32]         serialVersionUID [33] (フィールド)    
   scala.cloneable [34]                java.lang.Cloneable [35]     
   scala.deprecated [36]               java.lang.Deprecated [37]    
   scala.inline [38] (2.6.0から)       等価なもの無し
   scala.native [39] (2.6.0から)       native [40] (キーワード)
   scala.remote [41]                   java.rmi.Remote [42]     
   scala.serializable [43]             java.io.Serializable [44]    
   scala.throws [45]                   throws [40] (キーワード)    
   scala.transient [46]                transient [40] (キーワード)     
   scala.unchecked [47] (2.4.0から)    等価なもの無し
   scala.volatile [48]                 volatile [40] (キーワード)      
   scala.reflect.BeanProperty [49]     Design pattern [50]      


次の例では、Java main プログラム中で例外送出をキャッチするために、メソッド read の定義に throws アノテーションを加えています。

Java コンパイラは、どのチェック例外がメソッドあるいはコンストラクタの実行によって引き起こされるか分析し、プログラムがチェック例外 [51]用のハンドラを含むことを確認します。
起きる可能性のある各チェック例外に対して、メソッドあるいはコンストラクタの throw 節では、その例外クラスあるいはその例外クラスのスーパークラスの 1 つに言及しなくてはなりません。

Scala にはチェック例外がないので、Java コードが Scala メソッドの送出する例外を捕えることができるように、Scala メソッドに 1 つ以上の throws アノテーションをつける必要があります。


 package examples
 import java.io._
 class Reader(fname: String) {
   private val in = new BufferedReader(new FileReader(fname))
   @throws [45](classOf [52][IOException])
   def read() = in.read()
 }


次の Java プログラムはファイルの内容を印字します。ファイル名は main メソッドに最初の引数として渡されます。

 package test;
 import examples.Reader;  // Scala クラス !!
 public class AnnotaTest {
     public static void main(String[] args) {
         try {
             Reader in = new Reader(args[0]);
             int c;
             while ((c = in.read()) != -1) {
                 System.out.print((char) c);
             }
         } catch (java.io.Exception e) {
             System.out.println(e.getMessage());
         }
     }
 }



クラス Reader 中の throws アノテーションをコメントアウトすると、Java main プログラムのコンパイル時に、次のエラーメッセージが出力されます:

 Main.java:11: Exception java.io.IOException is never thrown in body of corresponding try statement
 
 corresponding try statement
         } catch (java.io.IOException e) {
           ^
 1 error


Java アノテーション

注意 : Java アノテーションの -target:jvm-1.5 オプションの使用を確認してください。

Java 1.5 は、アノテーション [53]の形でユーザー定義メタデータを導入しました。
アノテーションの重要な特徴は、指定された名前と値の対に依存して、それら要素を初期化することです。
例えば、もしあるクラスのソースを追跡するアノテーションが必要なら、次のように定義するかもしれません。

   Java annotations  @interface Source {
       public String URL();
       public String mail();
   }

それを次のように適用してください

   @Source(URL = "http://coders.com/",
           mail = "support@coders.com")
   public class MyClass extends HisClass ...


Scala のアノテーション適用は、Java アノテーションのインスタンス化のために名前付き引数を使う必要があるので、コンストラクタ呼び出しのように見えます。

 @Source(URL = "http://coders.com/",
         mail = "support@coders.com")
 class MyScalaClass ...


もしアノテーションが(デフォルト値をもたない) ただ 1 つの要素を含むだけなら、この構文はたいへんうんざりします。そこで、規約により(by convention)、もし名前を値として指定するなら、それをコンストラクタに似た構文を使って Java 中で適用できます。:

 @interface SourceURL {
     public String value();
     public String mail() default "";
 }


それを次のように適用してください

 @SourceURL("http://coders.com/")
 public class MyClass extends HisClass ...

In this case, Scala provides the same possiblity.
この場合、Scala は同じことを提供します。


 @SourceURL("http://coders.com/")
 class MyScalaClass ...


mail 要素はデフォルト値を指定されたので、それに明示的に値を与える必要はありません。
しかし、もしそうする必要があっても、Java 中で 2 つのスタイルを混ぜて適応させることはできません。:


 @SourceURL(value = "http://coders.com/",
            mail = "support@coders.com")
 public class MyClass extends HisClass ...


Scala はこの点に関してより柔軟です。

 @SourceURL("http://coders.com/",
            mail = "support@coders.com")
 class MyScalaClass ...

この拡張された構文は、.NET のアノテーションでも同じあり、それらアノテーションのフルの能力を引き出します。







Scala ひと巡り : クラス (Classes)





Scala のクラスは静的なテンプレートであり、実行時にたくさんのオブジェクトへインスタンス化されます。

次は、クラス Point を定義するクラス定義です:


 class Point(xc: Int, yc: Int) {
   var x: Int = xc
   var y: Int = yc
   def move(dx: Int, dy: Int) {
     x = x + dx
     y = y + dy
   }
   override def toString(): String = "(" + x + ", " + y + ")";
 }


クラスは 2 つの変数 x と y、2 つのメソッド move と toString を定義します。
move は 2 つの整数を引数にとりますが、値を返しません (暗黙の戻り値型 Unit は、Java ライクな言語の void に相当します)。他方、toString は引数をとらず、String 値を返します。
toString は事前定義された toString メソッドをオーバライドするので、override フラグでタグ付けしなければなりません。

Scala のクラスは、コンストラクタ引数でパラメータ化されます。
上記のコードは 2 つのコンストラクタ引数 xc と yc を定義します;それらは共にクラス本体全体で可視です。
この例では、それらは変数 x と y の初期化に使われます。

クラスは、次の例が示すように、new プリミティブでインスタンス化されます:

 object Classes {
   def main(args: Array[String]) {
     val pt = new Point(1, 2)
     println(pt)
     pt.move(10, 10)
     println(pt)
   }
 }


このプログラムは実行可能なアプリケーション Classes を、main メソッドをもつトップレベルのシングルトンオブジェクトの形で定義します。
main メソッドは新しい Point を生成し、それを値 pt に記憶します。
val 構文で定義された値は、それらの更新が許されない点が、var 構文(上記 クラス Point 参照)で定義された変数とは異なることに注意してください;
すなわち、値(value)は不変です。

次はプログラムの出力です:

   (1, 2)
   (11, 12)









Scala ひと巡り : ケースクラス (Case Classes)





Scala はケースクラスの概念をサポートします。ケースクラスは通常のクラスであり、そのコンストラクタ・パラメータをエクスポートし、パターンマッチィング [11]を通して再帰的な分解メカニズムを提供します。

次は、1 つの抽象スーパークラス Term と 3 つの具象ケースクラス Var、Fun と App からなるクラス階層の例です。

 abstract class Term
 case class Var(name: String) extends Term
 case class Fun(arg: String, body: Term) extends Term
 case class App(f: Term, v: Term) extends Term


このクラス階層は、型付けされていない(untyped) λ計算 [54]の項を表現するのに使えます。
ケースクラスのインスタンス構築にあたり、Scala では new プリミティブを使う必要がありません。
単純に、クラス名を関数として使用できます。


次は 1 つの例です:

 Fun("x", Fun("y", App(Var("x"), Var("y"))))


ケースクラスのコンストラクタ・パラメータは公開の値として扱われ、直接アクセスできます。

 val x = Var("x")
 Console.println(x.name)


すべてのケースクラスに対して、Scala コンパイラは、構造的等価性を実装する equals メソッドと toString メソッドを生成します。
たとえば:

 val x1 = Var("x")
 val x2 = Var("x")
 val y1 = Var("y")
 println("" + x1 + " == " + x2 + " => " + (x1 == x2))
 println("" + x1 + " == " + y1 + " => " + (x1 == y1))


は、次のように印字するでしょう。

 Var(x) == Var(x) => true
 Var(x) == Var(y) => false


もしパターンマッチィングをデータ構造の分解に使うなら、ケースクラスを定義することは道理にかなっています。
次のオブジェクトは、λ計算を表現するプリティプリンタ関数を定義します:


 object TermTest extends Application {
   def printTerm(term: Term) {
     term match {
       case Var(n) =>
         print(n)
       case Fun(x, b) =>
         print("^" + x + ".")
         printTerm(b)
       case App(f, v) =>
         Console.print("(")
         printTerm(f)
         print(" ")
         printTerm(v)
         print(")")
     }
   }
   def isIdentityFun(term: Term): Boolean = term match {
     case Fun(x, Var(y)) if x == y => true
     case _ => false
   }
   val id = Fun("x", Var("x"))
   val t = Fun("x", Fun("y", App(Var("x"), Var("y"))))
   printTerm(t)
   println
   println(isIdentityFun(id))
   println(isIdentityFun(t))
 }

この例で関数 print は、パターンマッチィング文として表現されており、それは match キーワードで始まる case Pattern => Body 節の並びから成っています。

上記のプログラムは、与えられた項が単純な識別関数に対応するかどうかチェックする関数 isIdentityFun も定義します。
この例は、深いパターンとガードを使います。
与えられた値をもつパターンとマッチした後、その(キーワード if の後に定義された)ガードが評価されます。
もしそれが true を返すなら、マッチは成功です。;そうでなければ失敗であり、次のパターンが試みられます。







Scala ひと巡り : 事前定義された classOf 関数 (Predefined function classOf)





事前定義された関数 classOf[T]は、 Scala のクラス型 T の実行時表現を返します。
次の Scala コード例は、 args パラメータの実行時表現を印字します:


 object ClassReprTest {
   abstract class Bar {
     type T <: AnyRef
     def bar(x: T) {
       println("5: " + x.getClass())
     }
   }
   def main(args: Array[String]) {
     println("1: " + args.getClass())
     println("2: " + classOf[Array[String]])
     new Bar {
       type T = Array[String]
       val x: T = args
       println("3: " + x.getClass())
       println("4: " + classOf[T])
     }.bar(args)
   }
 }


次は Scala プログラムの出力です:

 1: class [Ljava.lang.String;
 2: class [Ljava.lang.String;
 3: class [Ljava.lang.String;
 4: class [Ljava.lang.String;
 5: class [Ljava.lang.String;








Scala ひと巡り : 複合型 (Compound Types)





ときには、オブジェクトの型が、他の複数の型のサブ型であると表現することが必要になります。
Scala では、それをオブジェクト型の論理積である複合型(compound types)の助けをかりて表現できます。

2 つのトレイト Cloneable と Resetable があるとします。


 trait Cloneable extends java.lang.Cloneable {
   override def clone(): Cloneable = { super.clone(); this }
 }
 trait Resetable {
   def reset: Unit
 }


いま、オブジェクトを引数にとってクローンし、オリジナルのオブジェクトをリセットする関数 cloneAndReset を書きたいとします。

 def cloneAndReset(obj: ?): Cloneable = {
   val cloned = obj.clone()
   obj.reset
   cloned
 }


パラメータ obj の型は何か、という問題が生じます。
もしそれが Cloneable なら、オブジェクトはクローンできますがリセットできません。;
しかしもし Resetable なら、リセットできますがクローン操作がありません。
そのような状況で型キャストを避けるために、obj の型が Cloneable と Resetable の両方であると指定できます。
Scala では、複合型を使って Cloneable with Resetable のように書きます。

次は、アップデートした関数です:

 def cloneAndReset(obj: Cloneable with Resetable): Cloneable = {
   //...
 }


複合型は複数のオブジェクト型からなり、既存のオブジェクトメンバーのシグニチャを狭めるのに使える、ただ一つの細別(refinement)を持てます。

一般的な書き方は A with B with C ... { refinement } です。

細別の使用例は、抽象型 [21]に関するページにあります。







Scala ひと巡り : シーケンス内包表記 (Sequence Comprehensions)





Scala は、シーケンス内包表記式に対して簡単な表記法を提供します。
内包表記は for enums yield e の形をしています。ここで enums は、セミコロンで分離された列挙子のリストを参照します。
列挙子は、新しい変数を導入する生成子、あるいは、フィルタです。
内包表記は、列挙子 enum によって生成された各束縛にごとに、本体 e を評価し、それら値のシーケンスを返します。

次は 1 つの例です:


 object ComprehensionTest1 extends Application {
   def even(from: Int, to: Int): List[Int] =
     for (i <- List.range(from, to) if i % 2 == 0) yield i
   Console.println(even(0, 20))
 }


関数 even 中の for 式は Int 型の新しい変数 i を導入し、それをリスト List(from,from + 1,...,to - 1) の全ての値へ次々に束縛します。
ガード if i % 2 == 0 は、(式 i だけからなる)本体が偶数の場合のみ評価されるよう、全ての奇数をフィルターします。
最終的に、for 式全体は偶数のリストを返します。

プログラムは次を出力します:

 List(0, 2, 4, 6, 8, 10, 12, 14, 16, 18)

次はより複雑な例で、その合計が与えられた値 v と等しい、0 から n - 1 までの数の対をすべて計算します。:


 object ComprehensionTest2 extends Application {
   def foo(n: Int, v: Int) =
     for (i <- 0 until n;
          j <- i + 1 until n if i + j == v) yield
       Pair(i, j);
   foo(20, 32) foreach {
     case (i, j) =>
       println("(" + i + ", " + j + ")")
   }
 }


この例は、内包表記がリストに制限されないことを示しています。
前のプログラムは、代わりにイテレータを使っています。
(適切な型をもつ) 操作 filter、map そして flagMapをサポートするすべてのデータ型は、シーケンス内包表記中で使えます。

次はプログラムの出力です:

 (13, 19)
 (14, 18)
 (15, 17)

シーケンス内包表記で Unitを返す、特別の形式もあります。
そこでは、生成子のリストとフィルターから生成される束縛は、副作用を起こさせるために使われます。
そのようなシーケンス内包表記を利用するには、プログラマはキーワード yield を取り除かなければなりません。

次は、前のものと等価ではあるが Unit を返す、特別な for 内包表記を使うプログラムです。:


 object ComprehensionTest3 extends Application {
   for (i <- Iterator.range(0, 20);
        j <- Iterator.range(i + 1, 20) if i + j == 32)
     println("(" + i + ", " + j + ")")
 }








Scala ひと巡り : 抽出子オブジェクト (Extractor Objects)





Scala では、パターンをケースクラスとは独立に定義できます。
この目的のために、unapply という名前のメソッドが定義されて、いわゆる抽出子をもたらします。

例えば、次のコードは抽出子オブジェクト Twice を定義します。


 object Twice {                              
   def apply(x: Int): Int = x * 2
   def unapply(z: Int): Option[Int] = if (z%2 == 0) Some(z/2) else None
 }
 
 object TwiceTest extends Application {
   val x = Twice(21)
   x match { case Twice(n) => Console.println(n) } // prints 21
 }


次は、ここで関係する 2 つの文法上の規約(convention)です:

  • パターン case Twice(n)は、Twice.unapply の呼び出しを引き起こし、偶数のマッチに使われます。; unapply の戻り値は、引数がマッチしたかどうか、そしてさらなるマッチングのために使えるサブ値を伝えます。
ここで、サブ値は z/2 です。

  • The apply_method is not necessary for pattern matching. It is only used to mimic a constructor val x = Twice(21) expands to val x = Twice.apply(21) .

  • apply メソッドは、パターンマッチィングについては必須ではありません。
 これはただ、コンストラクタをまねて val x = Twice(21) を 
 val x = Twice.apply(21) へ展開する時に使われるだけです。

unapply の戻り値型は、次のように選ぶべきです:

  • もしそれが単なるテストなら、Boolean を返す。たとえば case even() 。
  • もしそれがただ一つの、型 T のサブ値を返すなら、Option[T]を返す。
  • もし複数のサブ値 T1,...,Tn を返したいなら、
 それらをまとめてタプルのオプション Option[(T1,...,Tn)]として返す。

しばしば、サブ値の数が固定で、シーケンスを返したいことがあります。
その場合は、unapplySeq を介して同様にパターンを定義できます。
最後のサブ値の型 Tn は、Seq[S]でなければなりません。
このメカニズムは、たとえば、パターン case List(x1,...,xn) 中で使われます。

抽出子を使えば、コードはさらに保守しやすくなります。
詳細は、Emir [56]、Odersky[57] と Williams (2007 年 1 月) らによる論文「
パターンを用いたオブジェクトマッチング」[55](4 章参照) を読んでください。


           目次 

タグ:

+ タグ編集
  • タグ:

このサイトはreCAPTCHAによって保護されており、Googleの プライバシーポリシー利用規約 が適用されます。

最終更新:2011年03月08日 05:48
ツールボックス

下から選んでください:

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