勉強帳 (2)

Scala By Example を読みます。

資料: http://www.scala-lang.org/docu/files/ScalaByExample.pdf

1. Introduction

いんとろだくしょん

2. A First Example

imperative に書いたクイックソートの例。

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)
}

だいたい見たまま。ポイントは

  • var は mutable な変数の宣言、val は immutable な変数の宣言。
  • 局所関数を定義できる。自由変数も OK 。これはいいな。

functional に書いたクイックソートの例。

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 <)))
  }
}

(pivot >) は x => pivot > x の略記。Haskell と同じ。ただし (< pivot) とは書けない。例によって、記号を特別扱いしているわけじゃない。なので

val a = Array(1, 2, 3)
(a concat)(a) // ArrayBufferRO(1, 2, 3, 1, 2, 3)

ということもできる。


Scala は while を自分で定義できるぜ!の例。

def While (p: => Boolean) (s: => Unit) {
  if (p) { s; While(p)(s) }
}

(p: => Boolean, s: => Unit) でなく (p: => Boolean) (s: => Unit) なのは、カリー化した関数を定義するためであろう。
引数の型が Boolean でなく => Boolean なところがポイントなんだろうけど、わかったようなわからないような。
あと、return キーワードと、メソッド本体の前に = をつけるとかつけないとか書いてあるけど、よくわからない。

def foo         { 1 }         // foo: Unit
def foo: Unit   { 1 }         // error: illegal start of declaration
def foo: Int    { 1 }         // error: illegal start of declaration
def foo       = { 1 }         // foo: Int
def foo: Unit = { 1 }         // foo: Unit
def foo: Int  = { 1 }         // foo: Int

def foo         { return 1 }  // foo: Unit
def foo: Unit   { return 1 }  // error: illegal start of declaration
def foo: Int    { return 1 }  // error: illegal start of declaration
def foo       = { return 1 }  // error: method foo has return statement; needs result type
def foo: Unit = { return 1 }  // foo: Unit
def foo: Int  = { return 1 }  // foo: Int

うーん、難しい。return 1 しても () が帰ることがあるとか不気味。

3. Programming with Actors and Messages

Actor を使ってオークションをシミュレーションするような感じの例。

  • メッセージを種別ごとにケースクラスで設計している。
  • メッセージ送信は client ! Status(maxBid, closing) とか。例によって ! はただの識別子。この文法は使い出があるなあ。

他に面白い話はなかった。

4. Expressions and Simple Functions

ここから文法ごとにフォーカスを当ててこまごまと説明。

4.1. Expressions And Simple Functions

定義と値定義の違い。

  • 定義 (def x = e) のときは e を評価しない。x が使われるたびに e が評価される。
  • 値定義 (val x = e) のときはすぐに e を評価する。x は e の評価結果に置き換わる (e を再評価しない) 。

んー。

式から値への段階的な簡単化を簡約 (reduction) という。うん。

4.2. Parameters

関数の仮引数には型指定が必須。うーん、まあしょうがないのかな。

call-by-value と call-by-name の話。Scala ではデフォルトで call-by-value だけど、仮引数の型を => ではじめれば call-by-name になるよ。

def loop: Int = loop

def first(x: Int, y: Int) = x
first(1, loop)  // 無限ループ

def first(x: Int, y: => Int) = x
first(1, loop)  // 1 が返る

うっひょー。関数を使う人 (= 呼び出す人) には、実引数が評価されるかどうかわからんところが熱いですね。

4.3. Conditional Expressions

if-else は値を返すよ。普通のこと。
Scala の真偽値は true と false 。! 、&& 、|| は、まあ普通に動く。ということは ! だけは識別子じゃないのかな。

4.4. Example: Square Roots by Newton's Method

スルー。

4.5. Nested Functions

さっきも出てきた関数のネスト定義。というか局所関数。
関数型プログラミングのスタイルでは小さいヘルパ関数をよく作るんだけど、名前空間を汚さずに関数を作れるし使える局所関数はとても便利。Ruby でできないのが歯がゆい。Ruby は呼び出し形式によってメソッドを関数と見立てるパラダイムなのでしょうがないんだけど、Scala がこのあたりをどうしてるかはまだわからない。どうもしてないのかな。


{ ... } はブロックで、ブロックは式の一種。ブロックの中に入るのが文か式かよくわからないけど、式っぽいことが書いてある。けど実際には文みたいのが入ってることが多いことからみて、Ruby 同様に文と式の区別はたぶん曖昧なんだろう。要するに、Ruby の ( ... ) と同じと思えばよさそうか。{ ... } の中の補助定義はこの中だけで見える。
ブロックの中の定義の後にはセミコロンが必須。ただし以下のいずれか場合には、行の最後に勝手にセミコロンが補完される。

  • その行が、式の終わりにならない語で終わっている場合 (ピリオドとか中置演算子とか)
  • 次の行が、式の始めにならない語で始まってる場合
  • () か [] の中の場合 (これらの中には複数の文が入り得ないのでわかるとのこと)

以下は全部 leagl:

def f(x: Int) = x + 1;
f(1) + f(2)

def g1(x: Int) = x + 1
g(1) + g(2)

def g2(x: Int) = {x + 1}; /* `;' 必須 */ g2(1) + g2(2)

def h1(x) =
  x +
  y
h1(1) * h1(2)

def h2(x: Int) = (
  x    // 括弧がないとダメ
  + y  // でないと x の後にセミコロンが挿入されちゃう
)
h2(1) / h2(2)


ということで、定義の扱いがおぼろげながら見えてきた。

  • def f = { 1 } の { ... } は式をまとめてるだけ。実際、def f = 1 と書いてもいい。def が右辺値をすぐに評価しないので、関数定義になる。
  • val f = { 1 } だとすぐに評価されるので、val f = { println("foo!"); 1 }; f; f とかやっても foo! は一回しか出力されない。
  • call-by-name な仮引数を使うことで、Ruby のブロックみたいな記述ができる?
def foo(f: => Unit) { f; f; f }
foo({ println("foo!") })  // foo! を 3 回表示
foo { println("foo!") }   // 同じ、括弧が省略できる条件はわかってない

まあ、Ruby のブロックみたいな記述ができるのは狙ってるわけではないかな。このブロックは仮引数が取れないし。


以上、長々と書いたけど大したことは言ってない。

4.6. Tail Recursion

末尾再帰は除去されるよの話。ただし Java VM の制限上、完璧には除去できない。
Scala の仕様としては、自分自身へ直接再帰する場合は末尾再帰除去することを保証しますとのこと。裏返すと、相互再帰は保証しない。バイトコードでも同じメソッド内ならジャンプできますからね。でも、うーん。

まとめ

ブロックの雰囲気がつかめた。def の = や return のあたりは、普通はどういう書き方をすると狙って設計されてるのかまだよくわからない。
それにしても、マニュアルがとてもよくできていると思う。英語は非常に平易だし、説明もトップダウンすぎないしボトムアップすぎない。読み物として面白い。もちろん、言語自体がおもしろいからこそだけど。作者自身がこのマニュアルを書いてるって、すばらしいことですね。
Scala By Example の続きはまた明日。