ログイン
編集不可のページディスカッション情報添付ファイル
"alstamber/Scala"の差分

MMA
8と9のリビジョン間の差分
2013-03-08 17:31:21時点のリビジョン8
サイズ: 44903
編集者: alstamber
コメント:
2013-03-08 18:15:08時点のリビジョン9
サイズ: 50987
編集者: alstamber
コメント:
削除された箇所はこのように表示されます。 追加された箇所はこのように表示されます。
行 1109: 行 1109:
{{{#!highlight scala
trait Philosophical {
  def philosophize(){
    println("I consume memory, therefore I am!")
  }
}
}}}

=== トレイトを使う ===
 * 既存のクラスにミックスインして使う
  * Java風に言えば実装する
 * すでにextendsを使っているときはwith, 使ってないときはextends
{{{#!highlight scala
class Kyoko extends Philosophical

val kyoko = new Kyoko
kyoko.philosophize()
I consume memory, therefore I am!
}}}

=== スーパークラスを持っているクラスにミックスイン ===
{{{#!highlight scala
class Human

class Kyoko extends Human with Philosophical

val kyoko = new Kyoko
kyoko.philosophize()
I consume memory, therefore I am!
}}}

=== 複数トレイトのミックスイン ===
 * クラスと違いトレイトは複数ミックスインできる

{{{#!highlight scala
class Human

trait hasLeg

class Kyoko extends Human with Philosophical with hasLeg

val kyoko = new Kyoko
kyoko.philosophize()
I consume memory, therefore I am!
}}}

=== トレイトのメソッドのオーバーライド ===
 * トレイトで定義されたメソッドはオーバーライドできる

=== トレイトとは結局何か ===
 * コンストラクタ引数をとれないクラスぐらいに考えてしまっていい
 * トレイトは道具箱のようなもの
  * クラスという作業場を用意しておいて、必要なメソッド(道具)をトレイトから引っ張り出してくるイメージ
  * トレイトを使うことで貧弱なインタフェースに処理を付け足してリッチなインターフェースを作ることができる

=== トレイトを実際に使ってみる ===
 * Scalaに標準で用意されているOrderedトレイトというのを使ってみる
 * 有理数を提供するクラスRationalというものがあるとする
  * こいつに>, >=, <, <=というメソッドを用意したい
   * 2つのRationalを引数にとってBooleanを返すメソッド
   * 普通に頑張ってそれぞれのメソッドを実装してもいいけど、Orderedトレイトを使うと簡単に実装できる
{{{#!highlight scala
class Rational(n:Int, d:Int) extends Ordered[Rational]{
  def compare(that:Rational) = //compareの値が正、負、0のどれかになるかという結果をもとに比較演算子の結果を求めるような仕組みになっている
    (this.numer * that.denom) - (that.numer * this.denom) //numerは分子, denomは分母に対応するフィールド
}
}}}

=== 積み重ね可能な変更 ===
 * 整数のキューを考える
{{{#!highlight scala
abstract class IntQueue {
  def get():Int
  def put(x:Int)
}

import scala.collection.mutable.ArrayBuffer
class BasicIntQueue extends IntQueue {
  private val buf = new ArrayBuffer[Int] //mutableになっている

  def get() = buf.remove(0)
  def put(x:Int){ buf += x }
}
}}}
 * 整数を追加するときに整数を2倍にして追加できるようにするトレイトをつくってみる
{{{#!highlight scala
trait Doubling extends IntQueue {
  abstract override def put(x:Int){super.put(2 * x)}
}
}}}
 * トレイト宣言でextendsキーワードを用いるとIntQueueクラスを拡張したクラスにしかミックスインできないようになる
 * extendsで明示的に親クラスを指定することでsuperキーワードを使って親クラスのメソッドを参照できる
  * 実際にはIntQueueクラスではputメソッドは抽象メソッドになっているので、具体的な実装を行なっているBasicIntQueueクラスのputメソッドが呼び出される
   * という認識でいいのかな
 * abstract overrideと書くことによってコンパイラに対して、既存のputメソッドに対して更に変更を加えているんだよということを示している

{{{#!highlight scala
class MyQueue extends BasicIntQueue with Doubling

val queue = new MyQueue
queue.put(10)
queue.get() //20が出てくる
}}}

 * いちいち使い捨てのクラスを作るのもアレだよね
  * newするときに一緒にミックスインの指定ができる
{{{#!highlight scala
val queue = new BasicIntQueue with Doubling
queue.put(10)
queue.get() //20が出てくる
}}}

 * 複数のトレイトで積み重ねをやってみる
{{{#!highlight scala
// 待ち行列の値に1加えるトレイト
trait Incrementing extends IntQueue {
  abstract override def put(x:int){super.put(x +1)}
}

// 待ち行列に負の数を加えないようにするトレイト
trait Filtering extends IntQueue {
  abstract override def put(x:int){ if(x >= 0) super.put(x)}
}
}}}

{{{#!highlight scala
val queue = new BasicIntQueue with Incrementing with Filtering

queue.put(-2);queue.put(-1);queue.put(0);

queue.get() //1が出てくる

queue.get() //エラー
}}}
 * 上の挙動から複数のミックスインは右から行われることがわかる
 * つまりトレイトを適用する順番によって挙動が変わる!!
  * これは強力だけど、同時に危険性もある

=== 多重継承ではなくトレイトによるミックスインを採用した理由 ===
 * 菱型継承をした時の問題を解決するため

=== ミックスインの線形化 ===
 * 複数のトレイトをミックスインするときどの順番にミックスインされるのか
{{{#!highlight scala
class Animal

trait Furry extends Animal
trait HasLegs extends Animal
trait FourLegged extends HasLegs

class Cat extends Animal with Furry with FourLegged
}}}
{{{#!highlight scala
Animal→AnyRef→Any //Animalはこのような階層構造になっている
Furry → Animal → AnyRef → Any //Furryトレイトは次のような構造
Cat→FourLegged→HasLegs→Furry→Animal→AnyRef→Any //Catクラスはこのような構造
}}}
 * 自分から始めて右から順番に適用されると思っておけばよいかと

目次

  1. なにこれ
  2. とりあえず触ってみる
  3. 変数の定義
  4. varとval
  5. 関数定義
  6. 単独で動くプログラムをつくってみる
  7. Scalaらしいループ
  8. 配列
    1. 配列の生成
    2. 配列への代入と参照
    3. 便利な配列の生成方法
  9. リスト
    1. リストの生成
    2. リストの結合
    3. リストの先頭に要素を追加
  10. タプル
    1. タプルを作る
    2. タプルを使う
  11. クラス
  12. メソッド
  13. シングルトンオブジェクト
    1. シングルトンオブジェクトの定義
    2. シングルトンオブジェクトの性質
  14. コンパニオンクラスとコンパニオンオブジェクト
    1. コンパニオンオブジェクトの例
  15. 基本型
  16. 基本リテラル
    1. 整数リテラル
    2. 浮動小数点リテラル
    3. 文字リテラル
    4. 文字列リテラル
    5. シンボルリテラル
    6. Booleanリテラル
  17. 演算子
    1. 比較演算子について
    2. 演算子の優先順位について
    3. リッチラッパー
  18. 制御構造
    1. if式
    2. while式, do-while式
    3. for式
      1. for式の基本的な使い方
      2. Range
      3. フィルタリング
      4. 入れ子のfor式
      5. yield
    4. try-catch-finally
    5. match式
  19. ローカル関数
  20. 関数リテラル
  21. 第一級オブジェクトとしての関数リテラル
  22. プレースホルダー
  23. 部分適用
    1. 部分適用の内部的動作
    2. アンダースコアの省略
  24. クロージャ
    1. 束縛変数と自由変数
    2. 開いた項と閉じた項
    3. クロージャの定義
    4. ローカル関数としてクロージャを定義する
  25. 連続パラメータ
  26. 末尾再帰
  27. 高階関数
    1. 高階関数の例
    2. 高階関数もう一例
  28. カリー化
    1. カリー化された関数にプレースホルダーを使ってみる
    2. 3引数以上で試してみる
    3. カリー化するメリット
  29. 制御構造の生成
  30. 名前渡しパラメータ
    1. こんなことしなくても関数を渡すようにすればいいんじゃね
  31. Scala的オブジェクト指向
    1. ライブラリの仕様
    2. 抽象クラス
    3. 引数なしメソッドの定義
    4. クラスの継承
    5. メソッドとフィールドのオーバーライド
    6. コンストラクタ
    7. 補助コンストラクタ
    8. パラメータでフィールドを初期化してしまう
    9. スーパークラスのコンストラクタ呼び出し
    10. override修飾子
    11. ポリモーフィズム
    12. final
    13. 合成と継承
    14. 引数をとるメソッドを定義
    15. ファクトリーメソッド
  32. Scalaのクラス構造
    1. Anyクラス
    2. AnyValクラス
    3. AnyRefクラス
    4. Javaのプリミティブ型との関連
    5. Nullクラス
    6. Nothingクラス
  33. トレイト
    1. トレイトを定義する
    2. トレイトを使う
    3. スーパークラスを持っているクラスにミックスイン
    4. 複数トレイトのミックスイン
    5. トレイトのメソッドのオーバーライド
    6. トレイトとは結局何か
    7. トレイトを実際に使ってみる
    8. 積み重ね可能な変更
    9. 多重継承ではなくトレイトによるミックスインを採用した理由
    10. ミックスインの線形化

なにこれ

  • コップ本にだいたい則ってScalaについてまとめたもの。
  • Scala勉強会とかで使いたい。
  • Scala処理系が入っている前提。
  • コップ本19章ぐらいまで読んだのでその辺りまでとりあえずまとめたい(無理か)

とりあえず触ってみる

  • scalaコマンドを実行するとインタプリタが立ち上がる。

   1 scala> 1+1
   2 res0: Int = 2

   1 scala> res0 * 5
   2 res1: Int = 10
  • 出力にはprintlnメソッドを使う。
    • printlnメソッドはUnit型の唯一のメンバである()を返す→Javaのvoidに相当

   1 scala> println("Hello, world!")
   2 Hello, world!

変数の定義

   1 scala> val msg = "Hello, world!"
   2 msg: String = Hello, world!
  • 上の定義の時にわざわざ型名(String)って書かなかった
  • 型が明らかなときは型名を書かなくてもいい→型推論

  • もちろん書いてもいい
    • 変数の後ろにコロンを書いて型名

   1 scala> val msg2:String = "Hello, world!"
   2 msg2: String = Hello, world!

varとval

  • 変数を定義するときにはvarとvalというキーワードが使える
    • varは何度でも変更できる
    • valは一度しか代入できない

   1 scala> val msg = "Hello, world!!"
   2 <console>:8: error: reassignment to val
   3        msg = "Hello, World!!"
  • varはなるべく使わないほうがいい
    • 変数の値をいつでも変えられるというのは強力だけど、同時にどこで変数の値が変わってしまったのかわかりにくくなる
    • 極力valを使い、どうしてもパフォーマンスなどでvarのほうが良い時だけvarを使う。

関数定義

   1 def 関数名(引数名:引数型, 引数名:引数型, ...):戻り値型 = 関数の式
  • 具体的な例

   1 def hello(name:String):String = {
   2   return "Hello! " + name
   3 }
  • returnは省略できる
    • 最後の式の結果が返される

   1 def hello(name:String):String = {
   2   "Hello! " + name
   3 }
  • returnを省略して、かつ戻り値の型が明らかなら戻り値の方も省略できる

   1 def hello(name:String) = {
   2   "Hello! " + name
   3 }
  • 一行ならそもそも中括弧もいらない

   1 def hello(name:String) = "Hello! " + name

単独で動くプログラムをつくってみる

  • テンプレ
    • 今はおまじないだと思いましょう
    • argsはStringの配列になっていてmainメソッドの引数です。

   1 object hoge
   2   def main(args:Array[String]) = {
   3     //メインの処理
   4   }
   5 }
  • かけたらコンパイルします

scalac hoge.scala
  • 実行します

scala hoge
  • scalacじゃなくてfscを使うと便利です
    • fscを使うと2回目以降コンパイルが速くなる

Scalaらしいループ

  • argsの中身を順番に表示するプログラムを書いてみる
  • こんなプログラムを書いてみよう
    • scalaでは配列の添字は丸括弧
    • インクリメントの演算子がないのでi += 1で我慢する
    • 行末のセミコロンはいらない

   1 var i = 0
   2 while(i < args.length) {
   3   println(args(i))
   4   i += 1
   5 }
  • このプログラムは良くない
    • varがあるから
  • 配列にはforeachメソッドというのが用意されている
    • 関数を引数にとってその関数を配列の要素に対して実行する

   1 args.foreach((arg:String) => println(arg))
  • argの型はStringなのはわかりきってるのでargの型名は省略できる

   1 args.foreach(arg => println(arg))

配列

  • 同じ型しか要素にとれない
  • valで宣言しても要素の変更は自由

配列の生成

   1 val hoge = Array[String](3)

配列への代入と参照

   1 hoge(2) = "kyoko"
   2 println(hoge(2))

便利な配列の生成方法

   1 val hoge = Array("Akari", "Kyoko", "Yui", "Chinatsu")

リスト

  • 同じ型しか要素にとれない
  • 要素の変更は(通常)不可能
    • mutableなリストというのが特別に用意してあって、それを使えば良い。もちろん推奨はされない。

リストの生成

   1 val hoge = List(2, 3, 5, 7)

リストの結合

   1 val hoge = List(2, 3)
   2 val fuga = List(5, 7)
   3 val piyo = hoge ::: fuga

リストの先頭に要素を追加

   1 val hoge = List(3, 5, 7)
   2 val fuga = 2 :: hoge
  • これは実は下の糖衣構文になっている。つまりListの::というメソッドを呼び出してる

   1 hoge.::(2)
  • ::や:::のことをconsという

タプル

  • 違う型の要素も持てる
  • 要素の変更は不可能
  • 言語の制約上22個しか要素が持てない

タプルを作る

   1 val hoge = (1, "Kyoko", 2, "Akari")

タプルを使う

   1 println(hoge._2)  // Kyoko と表示される
   2 
  • タプルの要素番号はHaskellなどの慣習から1番スタートになっている

クラス

  • Scalaはオブジェクト指向言語なのでクラスを持っている

   1 class Hoge {
   2   var hoge = 1 //フィールド
   3   var fuga = 3 //フィールド
   4 
   5   def myMultiple() = hoge * fuga //メソッド
   6 }
  • Javaとかと同じようにnewでクラスからオブジェクトを生成できる

   1 val a = new Hoge()
  • オブジェクトをval宣言の変数に代入しても、そのオブジェクトのフィールドがvar宣言されていれば再代入できる

   1 a.hoge = 2  // OK
   2 

メソッド

  • メソッドの引数は勝手にvalで定義される
    • 勝手に変えられないようにするため
  • 戻り値がUnitのメソッドは=を省略できる。ただし=を省略すると中括弧を省略できない。
  • メソッドをprivateにするには次のように記述する

   1 private def hello() = "Hello, World!"

シングルトンオブジェクト

  • Scalaは徹底したオブジェクト指向
    • オブジェクトに属していないものの存在なんて許せない!!!!
    • なのでstaticがありません
  • そのかわりにシングルトンオブジェクトというのがある

シングルトンオブジェクトの定義

   1 object Hoge {
   2   // あーだこーだ
   3 

シングルトンオブジェクトの性質

  • シングルトンオブジェクトはコードが実行されるときに自動的に生成される
  • またあるスコープの中でひとつしかないことが保証される
    • 多分後述
  • 先ほど「単独で動くプログラムをつくってみる」の所でobjectうんたらと書いたのは、プログラム実行開始時にシングルトンオブジェクトを生成させてその中のmainメソッドを実行するというふうにScalaが仕組まれているから。

コンパニオンクラスとコンパニオンオブジェクト

  • シングルトンオブジェクトと同じ名前のクラスを定義するとそれぞれコンパニオンオブジェクトとコンパニオンクラスと呼ばれるようになって、対で扱われるようになる。
  • コンパニオンオブジェクトからはコンパニオンクラスにアクセスすることができる。
  • コンパニオンオブジェクトはシングルトンオブジェクトなので、プログラム実行開始時に勝手に生成される。
  • なのでstatic代わりに使える。

コンパニオンオブジェクトの例

基本型

  • Scalaには次のような型がある
    • 数値型
      • Byte
      • Int
      • Long
      • Short
      • Double
      • Float
      • Char
    • String
    • Boolean

基本リテラル

整数リテラル

  • Int, Long, Short, Byte型のリテラル。
  • 10進、8進、16進表記可能。

浮動小数点リテラル

  • 10進表記のみ。
  • 指数表記もできる。

文字リテラル

  • シングルクォートでUnicode文字を囲んだもの。

文字列リテラル

  • 文字列をダブルクォートで囲ったもの。
  • 改行するとそれもそのまま文字列として反映される

シンボルリテラル

  • シンボルもやはり文字列を表すためのリテラル
  • 次のように表記する

   1 `hoge //hogeのシンボルリテラル
   2 
  • 文字列リテラルとの違いは文字列へのシンボルリテラルであれば必ず同じオブジェクトへの参照になっているということ

   1 val hoge = "hoge"
   2 val hoge2 = "hoge" //hogeとhoge2は違うオブジェクトになっている
   3 
   4 val fuga = `fuga
   5 val fuga2 = `fuga //fugaとfuga2は同じオブジェクト
   6 

Booleanリテラル

  • TrueとFalseがある

演算子

  • 演算子はメソッドです

   1 1 + 2
   2 (1).+(2)  // この2つは同じ意味
   3 
  • 演算子の種類はJavaに準ずるので適当にググってね

比較演算子について

  • 1つだけ注意を要するのが比較演算子の==
  • Javaではオブジェクトとしての等価性を判定するものだった

   1 "hoge" == "hoge" //左と右の文字列は同じオブジェクトかも知れないし違うオブジェクトかもしれないから、TrueかもしれないしFalseかもしれない
   2 
  • Scalaでは内部的にJavaのequalsメソッドを呼び出しているので、==はオブジェクトの持っている値そのものの比較になる

   1 "hoge" == "hoge" //かならずTrue
   2 

演算子の優先順位について

  • Scalaでは演算子の優先順位は演算子の名前で決まっている
    • 演算子は特別な存在ではなく、ただのメソッドだから
    • 例えば*メソッドは+メソッドよりも優先順位が高いとかいう決め方をしている

リッチラッパー

  • ScalaではJavaの演算子に加えてもっと賢い演算子が使えるようになっている
  • 例えば

   1 0 min 5 // min演算子で計算されて結果が0になる
   2 
  • これはリッチラッパーというクラスによって用意されている。ふだん使うぶんにはリッチラッパーという存在を意識せずにそのまま使うことが出来る。

制御構造

if式

  • if文じゃなくてif「式」です
    • 値を返すから

   1 if (条件式) {
   2   //真の時の式
   3 }else{
   4   //偽の時の式
   5 }
  • 条件式が真のときは「真の時の式」の結果が返ってくる
  • 条件式が偽のときは「偽の時の式」の結果が返ってくる
  • 関数定義の時と同じように式が複数あるときは最後の式の結果が返ってくる
  • なので次のように書ける

   1 val hoge = "hoge"
   2 println(if (!hoge.isempty) hoge else "default")

while式, do-while式

  • CとかJavaと同じ。
  • こいつらはUnit型を返す

   1 var a = x
   2 var b = y
   3 while(a != 0){
   4   val temp = a
   5   a = b % a
   6   b = temp
   7 }
  • 一応言っておくとwhileを使うような構文はたいていvarを伴うのでうんこ
  • 使わないようにしましょう
  • 再帰を使うのが常道
    • というわけで練習がてら再帰で上のプログラムを書き換えてみましょう

for式

  • for式を使いこなすのがScalaマスターへの第一歩です
  • for「式」なのでやっぱり値を返す
    • forが値を返すとはどういうことなのかは下のyieldに関する所を読んでね

for式の基本的な使い方

  • コレクションの反復操作
    • コレクションというのは配列とかListとかそのあたり

   1 val yuruyuri = List("Kyoko", "Akari", "Yui", "Chinatsu")
   2 for(member <- yuruyuri){
   3   println(member)
   4 }
  • 「member <- yuruyuri」の部分をジェネレータと呼ぶ

    • memberはvalになっていて、ループが1巡するごとにyuruyuriから1つ要素を取り出してmemberという変数を新たに初期化しているイメージ
    • memberという変数を生成しているのでジェネレータ

Range

  • 順番に並んでいる数値に対して処理をしたいとき

   1 for(i <- 1 to 10) println(i) //(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)という数列が生成される
   2 for(i <- 1 until 10) println(i) //(1, 2, 3, 4, 5, 6, 7, 8, 9)という数列が生成される
   3 

フィルタリング

  • for式では取り出す要素に対してif式で条件を追加できる
  • ifはセミコロンで区切って何個でも並べられる

   1 val yuruyuri = List("Kyoko", "Akari", "Yui", "Chinatsu")
   2 for(member <- yuruyuri if member.endsWith("i")){
   3   println(member)
   4 }

入れ子のfor式

  • <-を複数書くことで入れ子のfor式を1つのfor式でまとめて書くことができる。

   1 for(i <- 1 to 9; j <- 1 to 9) println(i*j)

yield

  • yieldはfor式の中に記述して使う。
  • 下の例の場合for文が実行されるごとにyield i+1というのが実行される。これが実行されるとi+1の結果がfor文の返り値として返ってくるようになる。for文は繰り返し実行されるのでyieldによって返ってきた戻り値は蓄積されていって新しいコレクションになる。つまりこの結果hogeには(1, 2, 3, 4, 5, 6)という配列が格納されることになる。

   1 val hoge = for (i <- 0 to 5) yield i+1
  • yieldの後ろで関数を呼び出すこともできるし、ブロックを書くこともできる。

try-catch-finally

  • ScalaではJavaと違って例外をかならずcatchしなくても良い
  • 次のように書きます

   1 try{
   2   //例外が発生しそうな処理を書く
   3 }catch{
   4     case e: 例外名  => 例外処理
   5 }
  • catch節での例外名の記述を端折るとすべての例外をcatchする式になります
  • caseは複数並べて複数の例外に対応出来ます
  • tryもcatchもブロック式なので、最後の式の結果が全体の結果として返る
    • 例外が発生しなかったときはtry節の最後の式の結果
    • 例外が発生したときは対応する例外の処理の最後の式の結果
  • finally節には例外の有無にかかわらず行いたい処理を書く
    • finally節に値を返すような処理を書くべきではない
      • tryやcatchで返された値を上書きしてしまう可能性があるから
      • 例外の有無にかかわらず行いたい副作用を伴う処理に限って書くべき

match式

  • こいつも強力な式なのだが、とりあえず最も基本的な使い方であるswitch文の代わりとしての使用法を見てみる

   1 val yuruyuri = "kyoko"
   2 yuruyuri match {
   3   case "kyoko" => println("so cute")
   4   case "akari" => println("who is she?")
   5   case _ => println("so so") // _ はdefaultに相当
   6 }
  • match式は式なので次のように書くこともできる
    • 副作用を伴う処理をmatch式の外に出してしまえるのでこっちのほうがいいかも

   1 val yuruyuri = "kyoko"
   2 val result = yuruyuri match {
   3   case "kyoko" => "so cute"
   4   case "akari" => "who is she?"
   5   case _ => "so so"
   6 }
   7 println(result)

ローカル関数

  • Scalaでは関数の中に関数を定義することができる→ローカル関数

   1 def calc(a:Int, b:Int) = {
   2   def add() = a + b
   3   def sub() = a - b
   4   (add, sub)
   5 }
  • 上のaddとsubという関数はcalc関数の中でしか使用できない
  • ローカル関数からローカル関数の外の変数にアクセスすることは可能

関数リテラル

  • 「Scalaらしいループ」というところでforeachメソッドを用いた例を示した
    • そこで以下の様な表現があった

   1 arg => println(arg)
  • これはargという引数をとって、println(arg)という処理を実行する関数になっている
  • より関数型っぽく言えばargという引数に対してprintln(arg)というものを返す関数である
  • Scalaではこのように名前の無い関数をその場で生成して使うことができる。これを関数リテラルとか無名関数などと呼ぶ

第一級オブジェクトとしての関数リテラル

  • 第一級オブジェクトなどというと仰々しいが、要するに関数リテラルは文字列や数値と同じようにScalaでは扱われていますよということ
  • 同じように扱われているので変数に代入できる

   1 val hoge = (x:Int) => x+1
   2 println(hoge(10))

プレースホルダー

  • ある引数が関数リテラルの中で一度しか使われないときは名前を書かずアンダーバーで代用することができる。

   1 val yuruyuri = List("Kyoko", "Akari", "Yui", "Chinatsu")
   2 yuruyuri.foreach(println(_))
   3 // yuruyuri.foreach(x => println(x))と同じ
   4 
  • 型を推論できないような書き方をすると怒られる

   1 val myadd = _ + _ //エラーが出る
   2 
  • 型を明示すれば良い

   1 val myadd = (_:Int) + (_:Int)
   2 println(myadd(2, 3))
  • アンダーバーで置き換えられるのは1回だけなので、アンダーバーの個数だけ引数があるとみなされる
    • だからアンダーバーが例えば2つあれば2引数の関数なのだ、という認識をコンパイラはするわけ

部分適用

  • アンダーバーによる置き換えは関数の引数リストに対しても適用出来る。

   1 def sum(a:Int, b:Int, c:Int) = a + b + c
   2 val sum2 = sum(_:Int, 3, _:Int)
  • 上の操作によってsum関数の第2引数に3を与えた引数を2つとる関数を新しく作ることが出来た
    • このように関数に引数の一部分だけを適用して新しい関数を得ることを部分適用という
  • 上の操作をした上で下のような操作をすることができる

   1 println(sum2(2, 4)) //9が出力される
   2 
  • 部分適用とは言うものの1つも引数を適用しないこともできる

   1 def sum(a:Int, b:Int, c:Int) = a + b + c
   2 val sum2 = sum _
  • 上の操作の結果sum2にはsumと同じ挙動をする3引数の関数が格納されている状態になる

部分適用の内部的動作

  • 次のコードを考える

   1 def sum(a:Int, b:Int) = a + b
   2 val sum2 = sum _
  • sum2にはsumに引数を0個部分適用した結果の2引数関数のオブジェクトが格納されている
  • sum2に格納されているものは実は厳密にはsumで定義されている関数そのものではない

   1 sum2(2, 3)
  • 上の式を評価するとsum2オブジェクトが持っているapplyというメソッドが呼び出され、sum2に対応する関数、すなわちsum関数が取り出され適用されるという形になっている

アンダースコアの省略

  • 「その関数式を記述する箇所が関数呼び出し必須である」ようなときアンダースコアを省略することも可能である

   1 val yuruyuri = List("Kyoko", "Akari", "Yui", "Chinatsu")
   2 yuruyuri.foreach(println)
   3 // foreachメソッドの引数には呼び出す関数を必ず記述しなければならないため、println(_)のアンダースコアは省略できる
   4 

クロージャ

束縛変数と自由変数

   1 (x:Int) => x + more
  • 上の関数リテラルでxを束縛変数、moreを自由変数と呼ぶ
  • xは関数の引数でInt型であるという事がわかっていて、関数の中で文脈的意味を与えられている
  • moreは突然出てきた変数で、文脈的意味が与えられていない
  • 自由変数も関数リテラルの外でちゃんと意味を与えてやれば(束縛するという)機能する

   1 var more = 1
   2 (x:Int) => x + more

開いた項と閉じた項

  • 自由変数がある関数リテラルを開いた項という。
  • 自由変数がない関数リテラルを閉じた項という。

クロージャの定義

  • 開いた項の関数リテラルが束縛された自由変数を掴んで閉じた項になった結果生まれた関数

   1 var more = 1
   2 val f = (x:Int) => x + more //これがクロージャ
   3 
  • クロージャはmoreの値を持っているわけではなく、more自体への参照を持っているのでmoreをいじればクロージャの中でのmoreの値も変わる。

   1 var more = 1
   2 var f = (x:Int) => x + more
   3 
   4 println(f(3)) //4が出力される
   5 
   6 more = 10
   7 
   8 println(f(3)) //13が出力される
   9 
  • 逆にクロージャから自由変数を変更することもできる

   1 val list = List(1,2,3)
   2 var sum = 0
   3 list.foreach(sum += _)
   4 println(sum) //6が出力される
   5 

ローカル関数としてクロージャを定義する

  • ローカル関数としてクロージャを定義し、そのクロージャの自由変数をクロージャの外側の関数の引数として与えてみる
  • 自由変数moreはmyadd関数の中でのみ通用する変数になっていて、クロージャが生成された時にmyaddに与えられた値がmoreの値になっている

   1 def myadd(more:Int) = (x:Int) => x + more
   2 
   3 val add10 = myadd(10)
   4 val add100 = myadd(100)
   5 
   6 println(add10(10)) //20が出力される
   7 println(add100(10)) //110が出力される
   8 

連続パラメータ

  • Scalaでは関数の「最後の」引数として可変長引数が渡せる
  • 型指定の後ろにアスタリスクをつける

   1 def echo(args:String*) = args.foreach(println)
  • 連続パラメータは内部的にはArrayとして扱われているが、Arrayを引数として渡そうとすると怒られる
    • Arrayごと渡したいときは後ろに:_*とかく

末尾再帰

  • Scalaは関数型言語なのでループを書く際にはwhileなどを使うのではなくなるべく再帰するのがよい
    • ただし末尾再帰になるように書けば再帰で書いても内部的にはただのループに置き換えられるので関数呼び出しのオーバヘッドが発生しないのでおすすめ
    • もちろんforeachメソッドなどが使えるのであればそちらを使うほうが良い

高階関数

  • 関数を引数にとったり関数を返したりする関数
    • foreachメソッドは紛れもない高階関数!

高階関数の例

  • 膨大な量の文字列のリストlistがあると仮定する
  • これに対して次のようなものを探したい
    • 先頭が指定文字列と合致する文字列
    • どこかしらが指定文字列と合致する文字列
    • 指定した正規表現とマッチする文字列
  • 普通に書くとこのようになる

   1 def listEnds(query:String) = {
   2   for(str <- list; if str.endsWith(query)) yield str
   3 }
   4 def listContains(query:String) = {
   5   for(str <- list; if str.contains(query)) yield str
   6 }
   7 def fileRegex(query:String) = {
   8   for(str <- list; if str.matches(query)) yield str
   9 }
  • ぶっちゃけ一部を除いてほとんど同じ内容の関数なので、何とかして共通な部分をくくりだしたい
  • 次のような関数を作る
    • matcherはStringを2つ受け取ってBooleanを返す関数式

   1 def listMatching(query:String, matcher:(String, String) => Boolean) = {
   2   for(str <- list; if matcher(str, query)) yield str
   3 }
  • 上のlistMatchingを使えばlistEnds関数は次のように書ける

   1 def listEnding(query:String) = {
   2     listMatching(query, (str:String, query:String) => str.endsWith(query)
   3 }
  • プレースホルダーを使ってfilesMatching関数の2引数は省略することが出来て

   1 def listEnding(query:String) = listMatching(query, _.endsWith(_))
  • クロージャを使えば更に短縮することができる

   1 def listMatching(matcher:String => Boolean) = {
   2   for(str <- list; if matcher(str)) yield str
   3 }
   4 def listEnding(query:String) = listMatching(_.endsWith(query))
  • listMatching関数で使われるqueryをlistMatching関数で束縛するのではなくて外側のlistEnding関数で束縛している。

高階関数もう一例

  • existsメソッドというのがある。existsメソッドはコレクションに適用されるメソッドで、コレクションの要素の中に一つでも与えられた関数式を満足するものがあればTrueを返し、そうでなければFalseを返すというものである。
  • 数値のリストが与えられた時負の数が含まれているかどうかを判定する

   1 list.exists(_ < 0)
  • existsの引数が関数式になっているのでこれも高階関数
  • 嘘だと思う人はこのように書き換えてみよう

   1 list.exists(x => x < 0)

カリー化

  • 一つの引数リストではなく複数の引数リストに対応できるような関数をカリー化された関数という

   1 def plainSum(x:Int, y:Int) = x + y //カリー化されてない
   2 plainSum(2, 3)
   3 
   4 def curriedSum(x:Int)(y:Int) = x + y //カリー化されてる
   5 curriedSum(2)(3)
  • カリー化されている関数はどうなっているのか
    • curriedSum関数は引数xをとってyという1引数の関数にまず化けている
    • そのあと引数yをとってInt型の数値を返している
    • 2段階になってるわけね
  • だから以下の関数とcurriedSumは同じ意味
    • curriedSum2はxを引数にとって「yを引数にとる関数リテラル」を返している

   1 def curriedSum2(x:Int) = (y:Int) => x + y

カリー化された関数にプレースホルダーを使ってみる

  • カリー化された関数はプレースホルダーを使って引数を固定できる

   1 val onePlus = curriedSum(1)_ //一つ目の引数に1を与えて固定してる
   2 onePlus(2) // curriedSum(1)(2)と同じ意味になる
   3 

3引数以上で試してみる

  • プレースホルダーは引数全体を一つのアンダースコアで置き換えるので、複数の引数を置き換えるときも1つだけ書けば良い

   1 def hoge(x:Int)(y:Int)(z:Int) = x + y + z
   2 val fuga = hoge(1)_
   3 fuga(2, 3) // hoge(1, 2, 3)と同じ意味
   4 

カリー化するメリット

  • カリー化された関数は部分適用がしやすくなる
  • 例えば足し算をする関数をカリー化しておけば、簡単に適当な数値を部分適用して、その数値の分インクリメントする関数を生成できる

制御構造の生成

  • ファイルを開いて書き込んで閉じる操作を行う関数を定義してみる

   1 def withPrintWriter(file:File, op:PrintWriter => Unit){
   2   val writer = new PrintWriter(file)
   3   try{
   4     op(writer)
   5   } finally {
   6     writer.close()
   7   }
   8 }
   9 
  10 withPrintWriter(new File("date.txt"), writer => writer.println(new java.util.Date))
  • opはファイル書き込みのためのPrintWriterオブジェクトを引数にとってファイルに実際に書き込み操作を行う操作を定義している関数

  • 操作を定義している関数を外から引数として与えられるようにしたので、ファイルを開いて何か操作をしてファイルを閉じるという操作だけを切り出した関数を作ることができる
  • このような書き方をopに対してファイルを開いて貸してあげているようにみえるため、「ローンパターン」という
  • 引数が1つだけのメソッドは丸括弧の代わりに中括弧を使ってもいい
    • 上のwithPrintWriterをカリー化して中括弧を使えるようにしてみよう

   1 def withPrintWriter(file:File)(op:PrintWriter => Unit){
   2   val writer = new PrintWriter(file)
   3   try{
   4     op(writer)
   5   } finally {
   6     writer.close()
   7   }
   8 }
   9 
  10 withPrintWriter(new File("date2.txt")){
  11   writer => writer.println(new java.util.Date)
  12 }
  • この関数に与えている処理を表す関数が中括弧で囲まれたことで、この部分が処理に対応していることが一目瞭然になった

名前渡しパラメータ

  • 次のようなプログラムを考える

   1 object MyLog {
   2   val isDebug = true
   3 
   4   def main(args: Array[String]) {
   5     log(heavyFunc)
   6   }
   7 
   8   def log(s: String) = { //heavyFuncのデバッグログを取るための関数
   9     if(isDebug) {
  10       println("call log")
  11       println(s)
  12     }
  13   }
  14 
  15   def heavyFunc() = {
  16     println("call heavyFunc") //実際には重い処理が用意されていると考える
  17     "hoge"
  18   }
  19 }
  • このプログラムを実行すると次のような結果になる

   1 call heavyFunc
   2 call log
   3 hoge
  • main関数でlog(heavyFunc)を実行するときにheavyFuncが評価されてしまうためこうなってしまう
    • isDebugをfalseにしてもheavyFuncはやっぱり評価されるので、"call heavyFunc"が出力されて、"hoge"が返される
      • "hoge"はいらないのに!!!
  • heavyFuncの返り値が必要になるまでheavyFuncの評価をしないでほしい
    • 名前渡しする

   1 object MyLog {
   2   val isDebug = true
   3 
   4   def main(args: Array[String]) {
   5     log(heavyFunc)
   6   }
   7 
   8   def log(s: => String) = { //ここが書き換わっている
   9     if(isDebug) {
  10       println("call log")
  11       println(s)
  12     }
  13   }
  14 
  15   def heavyFunc() = {
  16     println("call heavyFunc")
  17     "hoge"
  18   }
  19 }
  • 引数に=>をつけて関数を定義するとそこは名前渡しとなって、実際に戻り値が必要になるまで評価されなくなる

こんなことしなくても関数を渡すようにすればいいんじゃね

   1 def log(s: () => String) = { //ここが書き換わっている
   2   if(isDebug) {
   3     println("call log")
   4     println(s)
   5   }
   6 }
  • この書き方だと関数じゃなくて文字列を直接渡したいときに苦労する

   1 log("hoge") //エラー
   2 log(() => "hoge")) //関数リテラルとして書かないといけない
   3 

Scala的オブジェクト指向

  • コップ本10章に則って、Scalaにおけるオブジェクト指向の実現手法について見てみる
  • 簡単な2D描画をするライブラリを作る

ライブラリの仕様

  • 2Dオブジェクトはelemというメソッドで生成する
  • 生成したオブジェクトはaboveやbesideというメソッドを使って2つを結合することができる
  • 2Dオブジェクトは文字列表現の2次元矩形として定義する

抽象クラス

  • 2Dオブジェクトに対応するElementというクラスを用意する

   1 abstract class Element {
   2   def contents:Array[String]
   3 }
  • contentsは矩形を表現するための文字列の配列
  • Elementは抽象クラスになっている

引数なしメソッドの定義

  • 要素の高さを調べるheightメソッドと幅を調べるwidthメソッドを用意する

   1 abstract class Element {
   2   def contents:Array[String]
   3   def height:Int = contents.length
   4   def width:Int = if(height == 0) 0 else contents(0).length
   5 }
  • 別にdefで定義する必要はなくて、valで定義してしまってもいい
    • 関数がフィールドになりますね
    • このようにScalaではフィールドとメソッドのアクセス方法に区別がない

   1 abstract class Element {
   2   def contents:Array[String]
   3   val height:Int = contents.length
   4   val width:Int = if(height == 0) 0 else contents(0).length
   5 }
  • フィールドにすると初回にその結果を保持するので高速になるがメモリを食う
  • メソッドだと毎回計算数するが、メモリは食わない

クラスの継承

   1 class ArrayElement(conts:Array[String]) extends Element {
   2   def contents:Array[String] = conts
   3 }
  • Elementは抽象クラスなのでcontentsメソッドを実装しないといけない

メソッドとフィールドのオーバーライド

  • Scalaではメソッドとフィールドのアクセス方法が同じなので、メソッドとフィールドを相互にオーバーライドできる

コンストラクタ

  • Scalaではコンストラクタのための専用の構文はない
    • クラス定義の直下にベタ書きした式がそのままコンストラクタになる

   1 class Hoge {
   2   println("hoge") //この部分はHogeを生成した時に実行される
   3 }
  • 引数付きコンストラクタを定義したいときは次のように書く

   1 class Hoge(msg : String) {
   2   println(msg)
   3 }

補助コンストラクタ

  • 複数の引数リストのためのコンストラクタを定義したいときは補助コンストラクタを使う

   1 class Hoge(msg : String) {
   2   def this() = this("hoge") 引数を与えなかった時のコンストラクタを定義
   3   println(msg)
   4 }
  • 補助コンストラクタは複数定義できて、基本コンストラクタか別の補助コンストラクタを必ず呼び出さないといけない

パラメータでフィールドを初期化してしまう

  • コンストラクタのパラメータがフィールド初期化のためのパラメータなら次のように略して書くこともできる

   1 class ArrayElement(val contents:Array[String]) extends Element //valをつけることでフィールドと同じ名前のパラメータを定義できて、直接フィールド初期化ができる
   2 
  • valじゃなくてvarにすれば再代入できるフィールドにできる
  • privateとかをつけることもできる

   1 class ArrayElement(private val contents:Array[String]) extends Element

スーパークラスのコンストラクタ呼び出し

  • 高さが1の(即ち線になっている)ようなオブジェクトのためのクラスを作ると便利かも

   1 class LineElement(s:String) extends ArrayElement(Array(s)){ //extends ArrayElement(Array(s))と書くことで親クラスのコンストラクタにArray(s)を与えることができる
   2   override def width = s.length //親クラスをオーバーライドするのでoverrideをつけます
   3   override def height = 1
   4 }

override修飾子

  • 親クラスで実装済みのメソッドをオーバーライドする時は必ずメソッドの前にoverrideを書かないといけない
  • 親クラスのメソッドが抽象メソッドなら別に要らない
    • さっきも付けなかったよね

ポリモーフィズム

  • 指定された幅と高さの範囲を、指定された文字で埋め尽くすElement形式であるUniformElementを定義してみる

   1 class UniformElement(
   2   // 範囲を埋め尽くす文字を指定
   3   ch:Char
   4   override val width:Int,
   5   override val height:Int
   6 ) extends Element {
   7   private val line = ch.toString * width
   8   def contents = Array.make(height, line)
   9 }
  • 今まで定義したクラスはElementを拡張して作ってきたのでElementの一族として扱える

   1 val e1:Element = new ArrayElement(Array("hoge", "huga"))
   2 val e2:Element = new LineElement("hello")
   3 val e3:Element = new UniformElement('x', 3, 4) 
  • Scalaでは呼び出されるメソッドは変数の型ではなく、オブジェクトがどのクラスから生成されたかに依存する

   1 abstract class Element {
   2   def demo() {println("Element's implementation invoked")}
   3 }
   4 
   5 class ArrayElement extends Element {
   6   override def demo() {println("Array Element's implementation invoked")}
   7 }
   8 
   9 class LineElement extends ArrayElement {
  10   override def demo() {println("Line Element's implementation invoked")}
  11 }
  12 
  13 class UniformElement extends Element

   1 val e = new ArrayElement
   2 e.demo() //Array Element's implementation invokedが出力される
   3 
   4 val e = new LineElement
   5 e.demo() //Line Element's implementation invokedが出力される
   6 
   7 val e = new UniformElement
   8 e.demo() //Element's implementation invokedが出力される
   9 

final

  • サブクラスにオーバーライドさせたくないものにはfinalをつける
  • クラスそのものをそもそも拡張させたくないときはクラス自体にfinalをつければよい

合成と継承

  • 既存のクラスを利用する方法には合成と継承がある
  • 合成はクラスの持つフィールドのデータ型として別のクラスを指定することで、あるクラスから別のクラスを利用しようとする方法
    • オーバーライドを伴わないので、スーパークラスを書き換えるとサブクラスが使えなくなる、という問題を回避できる
    • オーバーライドを多用するより抽象メソッドの実装という形で実装を心がけるようにしたい
  • 上の例だとLineElementArrayElementに対して合成の関係ではない

    • ArrayElementにcontentsフィールドの定義を任せてそれを継承して使用しているため

    • 合成の関係にするためにはLineElementをElementの直下に来るようにする

      • contentsはArrayElementで定義されたものを継承したのではなく、自前でArrayクラスをデータ型として指定したものとなっている

   1 class lineElement(s:String) extends Element {
   2   val contents = Array(s)
   3   override def width = s.length
   4   override def height = 1
   5 }
  • この辺の感覚はトレイトを学ぶとつかめるようになる気がする

引数をとるメソッドを定義

  • オブジェクトを連結するメソッドabove, besideを定義します

   1 def above(that: Element): Element =  {
   2   new ArrayElement(this.contents ++ that.contents) // ++ は配列を結合するための演算子
   3 }
   4 
   5 def beside(that: Element):Element = {
   6   new ArrayElement(
   7     for ((line1, line2) <- this.contents zip that.contents) yield line1 + line2
   8   )
   9 }
  • zip演算子は2つの配列から1つずつ要素を取り出してそれを組み合わせた要素を生成し、新しい配列を作るという演算子
  • 出力用のtoStringメソッドも定義しょう
    • toStringはScalaのオブジェクト全てに定義されているので、overrideしないといけない

   1 override def toString = contents mkString "\n" //mkStringは引数にとった文字列を糊にしてcontentsの要素を結合するメソッド
   2 

ファクトリーメソッド

  • コンパニオンオブジェクトを使って実装するのが一番いい

   1 object Element {
   2   def elem(contents:Array[String]):Element = {
   3     new ArrayElement(contents)
   4   }
   5 
   6   def elem(char:Char, width:Int, height:Int):Element = {
   7     new UniformElement(char, width, height)
   8   }
   9 
  10   def elem(line:String):Element = {
  11     new LineElement(line)
  12   }
  13 }
  • 引数リストの違いに応じて呼び出すコンストラクタを変えることによって、コンストラクタの違いを意識すること無くオブジェクトを生成できる
  • ArrayElement, UniformElement, LineElementはコンパニオンオブジェクトからコンストラクタを呼び出せればいいのでコンパニオンオブジェクトの中で定義してしまうことにする

   1 object Element {
   2   private class ArrayElement(val contents:Array[String]) extends Element
   3 
   4   private class LineElement(s:String) extends Element{
   5     val contents = Array(s)
   6     override def width = s.length
   7     override def height = 1
   8   }
   9 
  10   private class UniformElement(
  11     ch:Char,
  12     override val width:Int,
  13     override val height:Int
  14    ) extends Element {
  15     private val line = ch.toString * width
  16     def contents = Array.make(height, line)
  17   }
  18 
  19   def elem(contents:Array[String]):Element = {
  20     new ArrayElement(contents)
  21   }
  22 
  23   def elem(char:Char, width:Int, height:Int):Element = {
  24     new UniformElement(char, width, height)
  25   }
  26 
  27   def elem(line:String):Element = {
  28     new LineElement(line)
  29   }
  30 }
  • 最後にElementクラスの定義でコンストラクタを呼び出しているところをファクトリーメソッドに置き換えれば完成
    • 省略するのでじぶんでやってみよう

Scalaのクラス構造

Anyクラス

  • Scalaのすべてのクラスの親クラス、頂点
    • AnyクラスにはtoStringやequalsメソッドなどが実装されている
  • Anyクラスの直下にはAnyValクラスとAnyRefクラスがある

AnyValクラス

  • Scalaにおける値をあらわすクラスの親クラス
    • ByteとかIntとかいうクラスの親

AnyRefクラス

  • Scalaにおける参照クラスの親クラス
    • Scalaでクラスを定義するとこいつの子になる

Javaのプリミティブ型との関連

  • Scalaでの数値演算は内部的にはJavaのプリミティブ型に変換して行われている模様
  • 数値をオブジェクトとして扱う必要性が出てきたときは、自動的にオブジェクトとして扱ってくれる
  • Scalaでは==はオブジェクトの値の等価性を判定するためのメソッド
    • 内部的にJavaのequalsメソッドを呼び出している
  • Javaの==と同じように同じオブジェクトであるかどうかを判定したいときはeqメソッドを使う

Nullクラス

  • すべての参照クラスのサブクラスになる
  • 参照になにもないことを表す
  • 参照クラスのサブクラスだから値型とは互換性がない
    • 例えばInt型の変数にNullを突っ込もうとすると怒られる

Nothingクラス

  • すべてのクラスのサブクラスになっている
    • どんな型にも化けられる!
  • エラーが発生した時にこいつを返すようにするとエラー判定に便利

   1 def error(message:String):Nothing = throw new RuntimeException(message)

トレイト

トレイトを定義する

  • 見た感じクラスと同じ感じ

   1 trait Philosophical {
   2   def philosophize(){
   3     println("I consume memory, therefore I am!")
   4   }
   5 }

トレイトを使う

  • 既存のクラスにミックスインして使う
    • Java風に言えば実装する
  • すでにextendsを使っているときはwith, 使ってないときはextends

   1 class Kyoko extends Philosophical
   2 
   3 val kyoko = new Kyoko
   4 kyoko.philosophize()
   5 I consume memory, therefore I am!

スーパークラスを持っているクラスにミックスイン

   1 class Human
   2 
   3 class Kyoko extends Human with Philosophical
   4 
   5 val kyoko = new Kyoko
   6 kyoko.philosophize()
   7 I consume memory, therefore I am!

複数トレイトのミックスイン

  • クラスと違いトレイトは複数ミックスインできる

   1 class Human
   2 
   3 trait hasLeg
   4 
   5 class Kyoko extends Human with Philosophical with hasLeg
   6 
   7 val kyoko = new Kyoko
   8 kyoko.philosophize()
   9 I consume memory, therefore I am!

トレイトのメソッドのオーバーライド

  • トレイトで定義されたメソッドはオーバーライドできる

トレイトとは結局何か

  • コンストラクタ引数をとれないクラスぐらいに考えてしまっていい
  • トレイトは道具箱のようなもの
    • クラスという作業場を用意しておいて、必要なメソッド(道具)をトレイトから引っ張り出してくるイメージ
    • トレイトを使うことで貧弱なインタフェースに処理を付け足してリッチなインターフェースを作ることができる

トレイトを実際に使ってみる

  • Scalaに標準で用意されているOrderedトレイトというのを使ってみる
  • 有理数を提供するクラスRationalというものがあるとする
    • こいつに>, >=, <, <=というメソッドを用意したい

      • 2つのRationalを引数にとってBooleanを返すメソッド
      • 普通に頑張ってそれぞれのメソッドを実装してもいいけど、Orderedトレイトを使うと簡単に実装できる

   1 class Rational(n:Int, d:Int) extends Ordered[Rational]{
   2   def compare(that:Rational) = //compareの値が正、負、0のどれかになるかという結果をもとに比較演算子の結果を求めるような仕組みになっている
   3     (this.numer * that.denom) - (that.numer * this.denom) //numerは分子, denomは分母に対応するフィールド
   4 }

積み重ね可能な変更

  • 整数のキューを考える

   1 abstract class IntQueue {
   2   def get():Int
   3   def put(x:Int)
   4 }
   5 
   6 import scala.collection.mutable.ArrayBuffer
   7 class BasicIntQueue extends IntQueue {
   8   private val buf = new ArrayBuffer[Int] //mutableになっている
   9 
  10   def get() = buf.remove(0)
  11   def put(x:Int){ buf += x } 
  12 }
  • 整数を追加するときに整数を2倍にして追加できるようにするトレイトをつくってみる

   1 trait Doubling extends IntQueue {
   2   abstract override def put(x:Int){super.put(2 * x)}
   3 }
  • トレイト宣言でextendsキーワードを用いるとIntQueueクラスを拡張したクラスにしかミックスインできないようになる

  • extendsで明示的に親クラスを指定することでsuperキーワードを使って親クラスのメソッドを参照できる
    • 実際にはIntQueueクラスではputメソッドは抽象メソッドになっているので、具体的な実装を行なっているBasicIntQueueクラスのputメソッドが呼び出される

      • という認識でいいのかな
  • abstract overrideと書くことによってコンパイラに対して、既存のputメソッドに対して更に変更を加えているんだよということを示している

   1 class MyQueue extends BasicIntQueue with Doubling
   2 
   3 val queue = new MyQueue
   4 queue.put(10)
   5 queue.get() //20が出てくる
   6 
  • いちいち使い捨てのクラスを作るのもアレだよね
    • newするときに一緒にミックスインの指定ができる

   1 val queue = new BasicIntQueue with Doubling
   2 queue.put(10)
   3 queue.get() //20が出てくる
   4 
  • 複数のトレイトで積み重ねをやってみる

   1 // 待ち行列の値に1加えるトレイト
   2 trait Incrementing extends IntQueue {
   3   abstract override def put(x:int){super.put(x +1)}
   4 }
   5 
   6 // 待ち行列に負の数を加えないようにするトレイト
   7 trait Filtering extends IntQueue {
   8   abstract override def put(x:int){ if(x >= 0) super.put(x)}
   9 }

   1 val queue = new BasicIntQueue with Incrementing with Filtering
   2 
   3 queue.put(-2);queue.put(-1);queue.put(0);
   4 
   5 queue.get() //1が出てくる
   6 
   7 queue.get() //エラー
   8 
  • 上の挙動から複数のミックスインは右から行われることがわかる
  • つまりトレイトを適用する順番によって挙動が変わる!!
    • これは強力だけど、同時に危険性もある

多重継承ではなくトレイトによるミックスインを採用した理由

  • 菱型継承をした時の問題を解決するため

ミックスインの線形化

  • 複数のトレイトをミックスインするときどの順番にミックスインされるのか

   1 class Animal
   2 
   3 trait Furry extends Animal
   4 trait HasLegs extends Animal
   5 trait FourLegged extends HasLegs
   6 
   7 class Cat extends Animal with Furry with FourLegged

   1 AnimalAnyRefAny //Animalはこのような階層構造になっている
   2 FurryAnimalAnyRefAny //Furryトレイトは次のような構造
   3 CatFourLeggedHasLegsFurryAnimalAnyRefAny //Catクラスはこのような構造
   4 
  • 自分から始めて右から順番に適用されると思っておけばよいかと

alstamber/Scala (最終更新日時 2013-03-09 01:51:21 更新者 alstamber)