Scala文法入門
forの使い方
by @kazzna
2015-8-13
自己紹介
Scalaを趣味で使い始めて約半年。
初スライドです。
最近はGo言語で
簡単なスクリプトを書いています。
ゴウランガ!
対象者
非対象者
Scalaのコンパイルに成功したことがある人。
ところで皆様
Structure Theorem*
ってご存知でしょうか。
1966年にコラド・ベームとジュゼッペ・ヤコピーニによって証明された定理で、
「任意のフローチャートは
『逐次』『分岐』『繰り返し』
の組み合わせに変換できる」
というすばらしい定理です。
- 1. 逐次
- Scalaではコードは上から順に実行されます。
- 2. 分岐
-
if (scala.isEasy) {
println("ベイビー・サブミッション!")
} else {
println("ショッギョ・ムッジョ!")
}
- 3. 繰り返し
-
while (!understand(scala)) {
println("アイエエエ!")
learn(scala)
}
for は
入れ子になった flatMap を
平坦化 できます。
- flatMap!!
-
trait F[A] {
def flatMap[B](f: A => F[B]): F[B]
def map[B](f: A => B): F[B]
}
ところでみなさん
Scalaで書くならvalとか使って
immutableに書きたいですよね?
<<Mutable>>
var stack = scala.collection.mutable.Stack(1, 2, 3, 4, 5)
var a = stack.pop()
var b = stack.pop()
var c = stack.pop()
stack.push(a)
stack.push(b)
stack.push(c)
println(stack)
<<Immutable>>
val stack = scala.collection.immutable.Stack(1, 2, 3, 4, 5)
val (a, s2) = stack.pop2
val (b, s3) = s2.pop2
val (c, s4) = s3.pop2
val s5 = s4.push(a)
val s6 = s5.push(b)
val s7 = s6.push(c)
println(s7)
valとか使ってimmutableに
書きたい ですよね!!
ここで次のような型を導入します。
case class StackAction[A](run: Stack[Int] => (Stack[Int], A))
「Stack[Int]を受け取って、変更したStack[Int]と付属の値Aを返す関数」
をパラメータとして持つ型
とても関数型っぽいですね。
コンパニオンにpushとpopを。
object StackAction {
def push(i: Int): StackAction[Unit] =
StackAction(s => (s.push(i), ()))
def pop: StackAction[Int] = StackAction{s =>
val (a, s2) = s.pop2
(s2, a)
}
def point[A](a: A): StackAction[A] = StackAction(s => (s, a))
}
☆手続き型では、
まずStackがあって、そこに値をpushする。
var stack = Stack(1, 2, 3)
stack.push(12)
☆関数型では、まず値をpushする関数があって、そこに対象のStackを渡す。
val pushZwolf = StackAction.push(12)
val (stack, _) = pushZwolf.run(Stack(1, 2, 3))
これにflatMapを生やしましょう。
case class StackAction[A](run: Stack[Int] => (Stack[Int], A)) {
def flatMap[B](f: A => StackAction[B]): StackAction[B] =
new StackAction[B]{s =>
val (s2, a) = this.run(s)
val stackActionB = f(a)
stackActionB.run(s2)
}
}
- 戻り値はStackAction[B]型
- StackAction[B]を手に入れるためにfを実行したい
- fの引数はA型
- Aを手に入れるためにはthis.runを実行
- this.runの引数はStack[Int]
- Stack[Int]がない!!!
- 仕方がないので、新しいStackActionをnewして、外からStack[Int]を(いつか)貰う
- StackAction[B]のrunは(Stack[Int], B)を返さないといけないので、調整。
- this.runで(s2, a)を作りだし、f(a)で2つ目のStackActionを作成。
- できたStackActionをrun(s2)して、結果を取得。
popした値をそのままpush
StackAction.pop.flatMap(a => StackAction.push(a))
↓
new StackAction{s =>
val (s2, a) = StackAction.pop.run(s)
StackAction.push(a).run(s2)
}
ついでにmapも。
case class StackAction[A](run: Stack[Int] => (Stack[Int], A)) {
def map[B](f: A => B): StackAction[B] =
this.flatMap(a => StackAction.point(f(a)))
}
すると、swapがこう書けます。
def swap: StackAction[Unit] = {
StackAction.pop.flatMap{a =>
StackAction.pop.flatMap{b =>
StackAction.push(a).flatMap{_ =>
StackAction.push(b).map(_ => ())
}
}
}
}
val (s, _) = swap.run(Stack(1, 2, 3, 4))
println(s)
入れ子のflatMapだ!!
これでforが使えますね。
書き直したswapがこちら!
def swap: StackAction[Unit] = for {
a <- StackAction.pop
b <- StackAction.pop
_ <- StackAction.push(a)
_ <- StackAction.push(b)
} yield ()
val (s, _) = swap.run(Stack(1, 2, 3, 4))
println(s)
これの素晴らしいところは、組み上げた結果もStackActionなことです。
def swapInside: StackAction[Unit] = for {
a <- StackAction.pop
_ <- swap
_ <- StackAction.push(a)
} yield ()
val (s, _) = swapInside.run(Stack(1, 2, 3, 4))
println(s)
引数にStackActionを受け取ればもっと柔軟に
def keepSurface[A](a: StackAction[A]): StackAction[A] = for {
p <- StackAction.pop
r <- a
_ <- StackAction.push(p)
} yield r
val act = keepSurface(StackAction.pop)
val (s, a) = act.run(Stack(1, 2, 3, 4))
println(s)
println(a)
まとめ
- forは大量のflatMapを読みやすく記述できる。
- flatMapで帰ってくる外の型は必ずflatMapの元の型と同じ。
- forで作った結果をforの中で使える。
ちなみに
これを抽象化したものがStateと呼ばれます。
case class State[S, A](def run: S => (S, A)) {...}
def put(s: S): State[S, Unit] = State(_ => (s, ()))
def get: State[S, S] = State(s => (s, s))
これでpushやpopなどを作る練習をすると
forに慣れることができるのではと思います。