Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.

For the best experience please use the latest Chrome, Safari or Firefox browser.

Scala文法入門

forの使い方

by @kazzna

2015-8-13

自己紹介

Scalaを趣味で使い始めて約半年。
初スライドです。

最近はGo言語で
簡単なスクリプトを書いています。
ゴウランガ!

対象者

以下のようなScalaの文法を理解している人。
  • var
  • if
  • while

非対象者

Scalaのコンパイルに成功したことがある人。
ところで皆様
Structure Theorem*
ってご存知でしょうか。
* 日本語だと「構造化定理」

1966年にコラド・ベームジュゼッペ・ヤコピーニによって証明された定理で、

「任意のフローチャートは 『逐次』『分岐』『繰り返し』 の組み合わせに変換できる」

というすばらしい定理です。

1. 逐次
Scalaではコードは上から順に実行されます。
2. 分岐
if (scala.isEasy) {
 println("ベイビー・サブミッション!")
} else {
 println("ショッギョ・ムッジョ!")
}
3. 繰り返し
while (!understand(scala)) {
 println("アイエエエ!")
 learn(scala)
}

これだけですべてのプログラム記述可能!!

では for とは

for
入れ子になった flatMap
平坦化 できます。

コワイ!!

flatMap?

入れ子?

何が嬉しいの?

flatMap!!
trait F[A] {
def flatMap[B](f: A => F[B]): F[B]

// 参考に、以下はmapの型。
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) // => Stack(3, 2, 1, 4, 5)
<<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) // => Stack(3, 2, 1, 4, 5)

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)
  }
}
  1. 戻り値はStackAction[B]型
  2. StackAction[B]を手に入れるためにfを実行したい
  3. fの引数はA
  4. Aを手に入れるためにはthis.runを実行
  5. this.runの引数はStack[Int]
  6. Stack[Int]がない!!!
  7. 仕方がないので、新しいStackActionnewして、外からStack[Int]を(いつか)貰う
  8. StackAction[B]のrunは(Stack[Int], B)を返さないといけないので、調整。
  9. this.runで(s2, a)を作りだし、f(a)で2つ目のStackActionを作成。
  10. できた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) // => Stack(2, 1, 3, 4)

入れ子の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) // => Stack(2, 1, 3, 4)

これの素晴らしいところは、組み上げた結果も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) // => Stack(1, 3, 2, 4)

引数に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) // => Stack(1, 3, 4)
println(a) // => 2

まとめ

  1. forは大量のflatMapを読みやすく記述できる。
  2. flatMapで帰ってくる外の型は必ずflatMapの元の型と同じ。
  3. 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に慣れることができるのではと思います。

Use a spacebar or arrow keys to navigate