勉強帳 (4)

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

9. Lists

リストの作り方。

val fruit = List("apples", "oranges", "pears")
val nums = List(1, 2, 3, 4)
val diag3 = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1))
val empty = List()

リテラルはない?それともこれがリテラル

9.1. Using List

この辺はさくさくと。
型は List[T] 。List[String] とか List[Int] とか List[List[Int] ] とか。コンストラクタは Nil と :: 。head 、tail 、isEmpty は普通に使える。

empty.isEmpty   = true
fruit.isEmpty   = false
fruit.head      = "apples"
fruit.tail.head = "oranges"
diag3.head      = List(1, 0, 0)
Nil.head        // java.util.NoSuchElementException: head of empty list

パターンマッチも。

{ case Nil => println("nil"), case x::xs => println("cons") }
9.2. Definition of class List I: First Order Methods

abstract class List[+A] 。つまり co-variant 。
主なメソッドの定義の紹介。imEmpty 、head 、tail 、length 、last 、init 、take 、drop 、split 。n 番目を返すメソッドは apply 。apply の syntax sugar によって、xs(0) とか書ける。かっこいい。
zip 。普通。

もただの識別子。ただし、: で終わる識別子を中置で使った場合は、右のオペランドをレシーバとする呼び出しになる、という特別扱いがある。
x :: y  // y.::(x)
x + y   // x.+(y)

ただし、評価順序はどちらも x 、y の順。あと、: で終わる演算子は結合の優先順位も変わる。

x :: y :: z  // x :: (y :: z)
x + y + z    // (x + y) + z

DSL 野郎は覚えとけ。

メソッドの定義はこう。
def ::[B >: A](x: B): List[B] = new scala.::(x, this)

ふんふん。クラス名も :: なところが熱いですね。
リストの連結は ::: 。Haskell でいうと ++ のやつ。ただし結合の優先順位は違う。あ、でも Scala の結合順位の方が効率がいいかも? A ::: B ::: C なら O(A の長さ + B の長さ) だけど、A ++ B ++ C だと O(A の長さ * 2 + B の長さ) になっちゃいそう。
あとは反転。

9.3. Example: Merge sort

スルー

9.4. Definition of class List II: Higher-Order Methods

map の話。

xs map (x => x * factor)

Ruby の each に相当する (値を返さない) のは foreach 。

List(1, 2, 3) foreach println

filter 。

List(1, 2, 3, 4, 5) filter (x => x % 2 == 1)  // List(1, 3, 5)

forall と exists 。

List(1, 2, 3, 4, 5) forall (3 ==)  // false
List(1, 2, 3, 4, 5) exists (3 ==)  // true

List.range(n, m) は n から m-1 までのリストを作る。(n...m) みたいな。

List.range(2, 5)  // List(2, 3, 4)

reduceLeft と foldLeft 。

List.range(1, 11) reduceLeft { (x, y) => x + y }   // 55
(List.range(1, 11) foldLeft 0){ (x, y) => x + y }  // 55

reduceRight と foldRight も。

List.range(1, 11) reduceRight { (x, y) => x + y }   // 55
(List.range(1, 11) foldRight 0){ (x, y) => x + y }  // 55

うーん、{ (x, y) => x + y } が冗長な感じ。
で、ここが熱い。foldLeft と foldRight には /: と :\ という alias がある。

(0 /: List.range(1, 11)) { (x, y) => x + y }  // 55
(List.range(1, 11) :\ 0) { (x, y) => x + y }  // 55

: がリストをあらわして、スラッシュが下向きの矢印をあらわす AA みたいなイメージで。
あと flatMap 。Haskell の concatMap 。Ruby でも欲しいんだよなこれ。

List(0, 1, 2).flatMap (x => List(x, 42))  // List(0, 42, 1, 42, 2, 42)

10. For-Comprehensions

for 内包表記。集合の内包表記と for ループをつなぐ記法?の話。
name と age のフィールドを持つオブジェクトのリスト persons に対して、以下は同値。

for (p <- persons if p.age > 20) yield p.name
person filter (p => p.age > 20) map (p => p.name)

20 歳より年寄りの名前のリストを作る。Python 流の記法かなんかですか?んー、内包表記は Haskell 流のがいいなあ。
for (s) yield e の s には generator と definition と filter が任意個書ける (ただし最初は必ず generator) 。

  • generator: val x <- e みたいなもの。ただし e はリスト。
  • definition: val x = e みたいなもの。普通の定義と何か違うかは不明。
  • filter: Boolean 型の式。これが true のときだけ yield する。
for (x <- List.range(1, 30) if x % 3 != 0 && x % 5 != 0) yield println(x)

んー。
for (s) じゃなくて for {s} でもいいよ。その場合はセミコロンで区切らなくていいよ。

for { i <- List.range(1, 10)
      j <- List.range(1, i)
      if isPrime(i+j) } yield (i, j)

といっても for (s) の方でも if p.age > 20 をセミコロンで区切ってないんだけどな。文法がよくわからない。
こういうことも書けるらしい。

for ((x, y) <- xs zip ys) yield x * y

つまり <- の左には任意のパターンが持ってこれるみたい。ということは。

val (x, y) = (1, 2)  // 動く
val 0 = 0            // 動く
val 0 = 1            // scala.MatchError: 1

おお。かなり let と同じなんですね。

10.1. The N-Queens Problem

for 内包表記の使用例 1 。

10.2. Querying with For-Comprehensions

for 内包表記の使用例 2 。

10.3. Translation of For-Comprehensions

for 内包表記がどう実装されているか。以下の 3 つの変換規則で map と flatMap と filter を使う式に変換されるらしい。

for (x <- e) yield e'
//==>  e.map(x => e')

for (x <- e if f; s) yield e'
//==>  for (x <- e.filter(x => f); s) yield e'

for (x <- e; y <- e'; s) yield e''
//==>  e.flatMap(x => for (y <- e'; s) yield e'')

なるほど、x <- e if f というのがひとかたまりなのか。

10.4. For-Loops
for (s) yield e

だと値を返す (map) けれど、値がいらないとき (foreach) は

for (s) e

と書けばいいよ。

10.5. Generalizing For

for 内包表記は上記の変換規則に従うだけなので、map 、flatMap 、filter を提供するオブジェクトなら List 以外にも使えるよ。データベースのインターフェイスXML tree なんかに使ってみてね。

ただし! map と flatMap と filter の型が適切でない場合、for 内包表記の変換結果が well-typed じゃなくなることがあるから気をつけてね、とのこと。

def map[B](f: A => B): C[B]
def flatMap[B](f: A => C[B]): C[B]
def filter(p: A => Boolean): C[A]

という型にすればいいらしい。その後、「このインターフェイスを定義した trait を用意して、その trait を継承してないと for 内包表記を使えないようにしたいよね。でもそれには Scala の型システムは弱すぎてダメなんだ」みたいなことが書いてあった。
まあ well-typed にならなくても型チェッカで検出してくれるからいいと思うんだけど、何か問題かな。map だけ提供する e だと、for (x <- e) は通るけど for (x <- e if f) は通らないのが嫌、両方通したくない、ということかな。

11. Mutable State

書き換え可能な変数の話。って今までちゃんと出てきてなかったのか。すげえ。

11.1. Stateful Objects

Scala で書き換え可能な状態は全部 var で作られる。

var x = "foo"
x = "bar"

var は定義と同時に初期化もしないとダメ。var x: Int は怒られる。初期値を考えたくない場合は、以前出てきたデフォルト値の _ を使う。

var x : Int = _

マニュアルには誤植があって、var が val になってたからちょっと悩んだ。


状態のあるオブジェクトを作りたかったら var をメンバとして持たせる。3 章に出てきた感じ。
状態を入れると参照透過じゃなくなるよ。操作的等価性とは「x と y を呼び出す任意の操作列の結果と、その操作列中の y をすべて x に置き換えた操作列の結果が同じなら、x と y は等価」。例えば、状態を持つ BankAccount クラスに対して、

val x = new BankAccount; val y = new BankAccount
x deposit 30; y withdraw 20  // 足りない
x deposit 30; x withdraw 20  // 足りる

という風に結果が変わるから等価じゃない。

val x = new BankAccount; val y = x

なら等価だよ。代入を入れたら、簡約だけのモデルじゃ扱えない。

11.2. Imperative Control Structures

while や do-while は書けるよ。else のない if も書けるよ。return も書けるよ。
でもこいつらは利便性のためだけにあるんだ。だって Scala ではこれらを自分で定義できるから。

def whileLoop(condition: => Boolean)(command: => Unit) {
  if (condition) {
    command; whileLoop(condition)(command)
  } else ()
}

と書いてあるけど、return ってどう書くんだろう。break や continue はないらしい。

11.3. Extended Example: Discrete Event Simulation

回路のシミュレーションの例。なかなか気合入った例。でもスルー

11.4. Summary

pure functional な書き方と imperative な書き方は適材適所だよ!

12. Computing with Streams

Stream (遅延リスト) の話。
なんだかんだ言っても、基本的には functional な書き方の方がいいわけです。例えば、start から end までの素数の和を求めるプログラム。

def sumPrimes(start: Int, end: Int): Int = {
  var i = stgart
  var acc = 0
  while (i < end) {
    if (isPrime(i)) acc += i
    i += 1
  }
  acc
}
def sumPrimes(start: Int, end: Int) =
  sum(range(start, end) filter isPrime)

明らかに functional の方がいい。でも、一旦 start から end までのリストを作るので、効率はよくない。


そこで Stream 。List とほぼ互換のインターフェイスを持つけど、cons の 2 つ目の引数が遅延評価される。よって、

def sumPrimes(start: Int, end: Int) =
  sum(Stream.range(start, end) filter isPrime)

とすることで、無駄なリストを作らないようにできる。


ただし、Stream に :: と ::: はない。理由はしょうもなくて、x :: xs が xs.::(x) の略記なので、xs を遅延することができないというもの。うーん、かっこ悪い。それぞれ Stream.cons(x, xs) と xs append ys を使えとのこと。

13. Iterators

Stream の imperative 版。外部イテレータだと思う。定義。

trait Iterator[+A] {
  def hasNext: Boolean
  def next: A
  ...
}

見ればわかるとおりだけど、使い方。

val it = Iterator.range(1, 100)
while (it.hasNext) {
  val x = it.next
  println(x * x)
}

あと定義の仕方。0 ばかりの無限リスト。trait ってインスタンス化できるんだ。

val it = new Iterator[Int] {
  def hasNext = true
  def next = 0
}
13.1. Iterator Methods

Iterator には List や Stream を模倣するメソッドがいろいろ定義されている。append 、map 、flatMap 、foreach 、filter 、zip など。

de append[B >: A](that: Iterator[B]): Iterator[B] = new Iterator[B] {
  def hasNext = Iterator.this.hasNext || that.hasNext
  def next = if (Iterator.this.hasNext) Iterator.this.next else that.next
}

filter だけは次の要素を保持しておかないといけないので、Iterator を継承した BufferedIterator を返す。

13.2. Constructing Iterators

自明な内容だった。Iterator.empty 、Iterator.fromArray 、Iterator.range などが定義されているよ。

13.3. Using Iterators
Iterator.fromArray(xs) foreach (x => println(x))
for (x <- Iterator.fromArray(xs)) println(x)

お好きなように。
Iterator.from(n) というのもあるみたい。Haskell の [n..] 。

14. Lazy Values

lazy val foo = { println("foo"); 1 }

うおお。val の前に lazy をつけると、初期化を遅延できるみたい。インタプリタで実行してみると

scala> lazy val foo = { println("foo"); 1 }
foo
foo: Int = 1

あれ?遅延されないじゃん。プログラムをファイルにしてやってみると

object t {
  def main(args: Array[String]) {
    lazy val x : Int = { println("foo"); 1 }
    println("before")
    println(x)
    println(x)
  }
}
$ scalac t.scala && scala t
before
foo
1
1

おお、遅延されてる。あー、"foo: Int = 1" って表示をするために参照してしまったのか。
必要になるまでデータベースアクセスとか重い処理を遅延させられる、というメリットが書いてある。


他に、複数のモジュールからなるプログラムの初期化順序を制御するのにも使えるらしい。

class Symbol(val compiler: Compiler) {
  import compiler.types._

  def Add = new Symbol("+", FunType(List(IntType, IntType), IntType))
  def Sub = new Symbol("-", FunType(List(IntType, IntType), IntType))

  class Symbol(name: String, tpe: Type) {
    override def toString = name + ": " + tpe
  }
}

なんと、そこで import できるのか。

class Types(val compiler: Compiler) {
  import compiler.symtab._

  abstract class Type
  case class FunType(args: List[Type], res: Type) extends Type
  case class NamedType(sym: Symbol) extends Type
  case object IntType extends Type
}

Types も同様に。

class Compiler {
  val symtab = new Symbols(this)
  val types = new Types(this)
}

とすると、new Symbol(this) の中で types が必要になって、でも types はまだ初期化されていないからランタイムエラーになる。そこで

class Compiler {
  lazy val symtab = new Symbols(this)
  lazy val types = new Types(this)
}

とすれば解決。とのことなんだけど、lazy つけないでコンパイルしてみても、どうにもランタイムエラーが出ない。なんかよくわからん。


あと、lazy val なら定義の中で自分自身を再帰的に参照できる。

lazy val x : Int = 1 + x

15. Implicit Parameters and Conversinos

既存のライブラリをカスタマイズするとかに使える技。

implicit parameter

題材として、半群とモノイドを表すクラス。そして文字列のモノイドと整数のモノイド。

abstract class SemiGroup[A] {
 def add(x: A, y: A): A
}
abstract class Monoid[A] extends SemiGroup[A] {
 def unit: A
}
object stringMonoid extends Monoid[String] {
  def add(x: String, y: String) = x.concat(y)
  def unit = ""
}
object intMonoid extends Monoid[Int] {
  def add(x: Int, y: Int) = x + y
  def unit = 0
}

で、sum 関数を考える。

def sum[A](xs: List[A])(m: Monoid[A]): A = (m.unit /: xs)(m.add)

使い方はこう。

sum(List("a", "bc", "def"))(stringMonoid)
sum(List(1, 2, 3))(intMonoid)

で、最初の引数の型からどのモノイドを与えるべきかはわかるのに、いちいち書くのがダサいよね。という導入。半群とかモノイドとか言われると、実際にどういう状況で必要になるかぴんと来ないわけだけど、まあ要するに、「String または Int を引数に受け取るメソッド」みたいのを定義したいときの話だな。


このためにはまず implicit キーワードを仮引数につける。

def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
  (m.unit /: xs)(m.add)

implicit がつけられるのは一番最後の仮引数だけ。さらに、stringMonoid とかの定義にも implicit をつける。

implicit object stringMonoid extends Monoid[String] {
  def add(x: String, y: String) = x.concat(y)
  def unit = ""
}
implicit object intMonoid extends Monoid[Int] {
  def add(x: Int, y: Int) = x + y
  def unit = 0
}

以上が準備。


このとき、implicit 仮引数に対応する引数だけが省略されたメソッド呼び出しがあったとする。

sum(List(1, 2, 3))

このときコンパイラは、「このメソッド呼び出しのある場所から、prefix などなしで参照できる識別子の中で、implicit がつけられているもの」を探し出して、オーバーロードの要領でもっとも specific なものを選んで、それを勝手に与える。この sum の場合は、stringMonoid と intMonoid が見えていて、さらに sum の第一引数が List[Int] なので、intMonoid が採用される。
stringMonoid と stringMonoid2 があって、どっちも extends Monoid[String] だったらどうなるんだろうと思って試したところ、複数あって決まんねーぞ、とエラーになった。

scala> sum[String](List("a", "bc", "def"))
<console>:10: error: ambiguous implicit values:
 both object stringMonoid2 in object $iw of type object stringMonoid2
 and object stringMonoid in object $iw of type object stringMonoid
 match expected type Monoid[String]
       sum[String](List("a", "bc", "def"))

なるほど。

implicit conversion

型 S が期待される文脈に型 T の式が書かれてて、継承関係とかでもない (型 T を型 S と見なせない) ときは、implicit conversion が探される。implicit parameter 同様に、その式のある箇所から「prefix などなしで参照できる識別子の中で、implicit がつけられているもの」を探して、その中で型 (T => S) と見なせるものを採用する。

implicit def int2ordered(x: Int): Ordered[Int] = new Ordered[Int] {
  def compare(y: Int): Int =
    if (x < y) -1
    else if (x > y) 1
    else 0
}

とかあったとする。

def foo(x: Ordered[Int]) = ...
foo(1)

というように、型 Ordered[Int] が期待される文脈に型 Int の式 1 があったら、

foo(int2ordered(1))

という風に勝手に変換しちゃうぜ、という話っぽい。
あと、implicit conversion はメンバの選択にも適用されるらしい。つまり、E.x とあって E の型に x が無かったら、I(E).x となるような implicit comversion を探すそうな。
いやあ、自分の足を打ちぬけそうな機能ですね。

view bound

ジェネリクスの章で出てきた A <% B の話。

def sort[A <% Ordered[A]](xs: List[A]): List[A] = {
  if (xs.length <= 1) xs
  else {
    val pivot = xs(xs.length / 2)
    val left  = xs filter (x => x <  pivot)
    val mid   = xs filter (x => x == pivot)
    val right = xs filter (x => x >  pivot)
    sort(left) ::: mid ::: sort(right)
  }
}

は、A 自体が比較できなくても、A から Ordered[A] への implicit conversion があればそれを使って比較してソートする関数。
view bound は implicit parameter の syntax sugar だそうで、以下のような implicit parameter の c (もちろん適当に fresh な変数) が追加されて、中で比較が必要なところで適宜 c が呼ばれる、みたいな。

def sort[A](xs: List[A])(implicit c: A => Ordered[A]): List[A] = {
  if (xs.length <= 1) xs
  else {
    val pivot = xs(xs.length / 2)
    val left  = xs filter (x => c(x) <  pivot)
    val mid   = xs filter (x =>   x  == pivot)
    val right = xs filter (x => c(x) >  pivot)
    sort(left)(c) ::: mid ::: sort(right)(c)
  }
}


以下余談。最初 x == pivot のところを c(x) == pivot にしてたら、なんか sort(List(3,2,1)) とかが List() になって、おっかしーなーと試行錯誤したところ、

(5 : Ordered[Int]) == 5  // false

となることに気づいた。trait Ordered の定義を見てみたら == はない。つまり == はルートクラスに定義された物理等価で、Ordered のインスタンスと Int のインスタンスを比較してるから false になるんだろう。

16. Hindley/Milner Type Inference

Mini-ML のインタプリタの題材を実演して見せる章。いまさらだけど、Scala って Java プログラマをだまして型付き関数型言語のファンにさせる計画なんだろうか。

内容は MinCaml型推論で習った通りなのでスルー。以下は気になった点。

  • sealed abstarct class Foo という定義ができるらしい。sealed クラスは「そのクラスが定義されている定義の列の外で、そのクラスを継承するサブクラスやデータコンストラクタを作ることができない」ようなクラスらしい。
sealed abstract class FooBar {}
case class Foo(x: Int) extends FooBar {}
case class Bar(x: Int) extends FooBar {}

sealed abstract class Hoge {}
case class Baz(x: Int) extends FooBar {}

とやったらエラーになるかなと思ったら、ならなかった。「定義の列の外」とは、別ファイルのことか。

  • Subst が Function1[Type, Type] を継承してる理由がわからない。s(t) と書きたいだけなら apply を定義するだけでいいと思うんだけど。Function1 に関係する implicit conversion でも使ってるのかな。
  • パターンマッチには @ が書ける。Haskell の @ と同じだろう。

17. Abstractions for Concurrency

最後の章。並行 (だっけ、並列だっけ)。

17.1. Signals and Monitors

AnyRef に以下のメソッドが定義されている。

def synchronized[A](e: => A): A
def wait()
def wait(msec: Long)
def notify()
def notifyAll()

同期のプリミティブすぎて synchronized 以外はどう使えばいいんだったか悩む。Java の Object かなにかにあるのと同じかな。

BoundedBuffer の定義と、それの使い方

class BoundedBuffer[A](N: Int) {
  var in = 0, out = 0, n = 0
  val elems = new Array[A](N)
  def put(x: A) = synchronized {
    while (n >= N) wait()
    elems(in) = x ; in = (in + 1) % N ; n = n + 1
    if (n == 1) notifyAll()
  }
  def get: A = synchronized {
    while (n == 0) wait()
    val x = elems(out) ; out = (out + 1) % N ; n = n 1
    if (n == N 1) notifyAll()
    x
  }
}
import scala.concurrent.ops._
...
val buf = new BoundedBuffer[String](10)
spawn { while (true) { val s = produceString ; buf.put(s) } }
spawn { while (true) { val s = buf.get ; consumeString(s) } }

concurrent.ops に定義されてる spawn の定義はこう。

def spawn(p: => Unit) {
  val t = new Thread() { override def run() = p }
  t.start()
}
17.2. SyncVars

しんくろないずどなばりゅー

17.3. Futures
import scala.concurrent.ops._
...
val x = future(someLengthyComputation)
anotherLengthyComputation
val y = f(x()) + g(x())

future の引数は別のスレッドで計算される。x() が呼ばれた時、計算が終わっていたらすぐに結果を返し、計算が終わっていなかったら終わるまで待ってから結果を返す。

def future[A](p: => A): Unit => A = {
  val result = new SyncVar[A]
  fork { result.set(p) }
  (() => result.get)
}

いやー、callee 側の遅延評価の指示は熱いなあ。でも、帰り値が遅延できないから Unit => A を返すことになってるところがもう一歩。直接 A を返す場合でも caller 側で lazy val x = future(someLengthyComputation) ってすればいいんだろうけど、これを callee 側で指示できれば最高だった。

17.4. Parallel Computations
17.5. Semaphores
17.6. Readers/Writers
17.7. Asynchronous Channels
17.8. Synchoronous Channels
17.9. Workers
17.10. Mailboxes
17.11. Actors

よくある並列プログラミングのコード例ばかりで、Scala 特有の話はなく、あんまり面白くないのでスルー
ぜんぜん関係ないけど、class の定義の中にメソッド呼び出しが書けるらしかった。いつ実行されるんだろう。Java の static {} 相当だろうか。

まとめ

Scala By Exapmle 読み終わった。けど、省略された面白い話がいっぱいあるみたいだし、疑問点も結構残ったし、まだまだ調べねば。
次は Scala Reference Manuals 読むかなあ。それとも何か作ってみるかなあ。練習のために、作りたいわけではないものを作るというのが苦手。