Upload
suguru-hamazaki
View
472
Download
2
Embed Size (px)
Citation preview
関数型プログラミング
2014-05-11 at ゆかむ勉強会 Suguru Hamazaki
Introduction to Scala Functional Programming
入�門
ご説明内容Agenda
Scalaって?
関数型プログラミングって?
ステートマシンをどうやって実装するの?
もっと抽象化できるよ!
便利な ライブラリーがあるよ!
プログラミング言語Scala Programming Language
オブジェクト指向と 関数型の融合
Have the best of both worlds. Construct elegant class hierarchies
for maximum code reuse and extensibility, implement their behavior
using higher-order functions. Or anything in-between.
— from http://www.scala-lang.org/
Object-Oriented Meets Functional
But …
パラダイムシフトは難しいA paradigm shift is difficult
関数型プログラミングFunctional Programming
純粋関数Pure Function
純粋関数Pure Function
• 関数の評価結果が引数の値のみによって決まり、同じ値を与えると常に同じ値の結果を返す
• 関数の評価によって、観測可能な副作用が発生しない
fx y
副作用の例Side effects examples
• 変数に再代入する
• データの構造を破壊的に変更する
• オブジェクトのフィールドに値をセットする
• 例外を投げる、エラー時に終了する
• コンソールに出力する、ユーザー入力を読む
• ファイルを読み書きする
• スクリーンに描画する
Variable reassigning
Destructive mutation
Setting field values
Throwing exceptions or stopping at failure
Console output or reading user input
File I/O
Displaying to screen
純粋関数プログラムを書くWrite programs using only pure functions
のみで
えっ?
コインのモデルA coin model
head tail
flip
flip
stay stay
オブジェクト指向的コイン
case class OoCoin(private var head: Boolean) { def flip() = { head = !head } def stay() = {} // do nothing def get = head}
Object-Oriented Coin
オブジェクト指向的コイン
case class OoCoin(private var head: Boolean) { def flip() = { head = !head } def stay() = {} // do nothing def get = head}
var (variable) で宣言し
たフィールドに再代入
Object-Oriented Coin
val c = OoCoin(true)c.flip()c.stay()c.flip()println("Showing a head? " + c.get)
参照透過性Referential Transparency
ある式の中で、その式の
値を変えることなく、等しいもの同士を置換 できること
val a = 5sumOfSquares(a + 1, a * 2)
def square(i: Int) = i * i def sumOfSquares(i1: Int, i2: Int) = square(i1) + square(i2)
val a = 5sumOfSquares(a + 1, a * 2)
def square(i: Int) = i * i def sumOfSquares(i1: Int, i2: Int) = square(i1) + square(i2)
純粋な関数
val a = 5sumOfSquares(a + 1, a * 2)
def square(i: Int) = i * i def sumOfSquares(i1: Int, i2: Int) = square(i1) + square(i2)
純粋な関数
再代入できない val
置換モデルsumOfSquares(a + 1, a * 2)
sumOfSquares(5 + 1, 5 * 2)
sumOfSquares(6, 10)
square(6) + square(10)
(6 * 6) + (10 * 10)
36 + 100
136
Substitution Model
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-10.html
再び、コインの例を見てみるOK, let’s go back to the coin model
val c = OoCoin(true)c.flip()c.stay()c.flip()println("Showing a head? " + c.get)
val c = OoCoin(true)c.flip()c.stay()c.flip()println("Showing a head? " + c.get)
mutable なデータ構造と非純粋関数
val c = OoCoin(true)c.flip()c.stay()c.flip()println("Showing a head? " + c.get)
mutable なデータ構造と非純粋関数
参照透過性は?
不変なコインと
純粋関数を使う
Use immutable coins
and pure functions
case class Coin(head: Boolean)!object Coin1 { def flip(c: Coin) = Coin(!c.head) def stay(c: Coin) = c}
case class Coin(head: Boolean)!object Coin1 { def flip(c: Coin) = Coin(!c.head) def stay(c: Coin) = c}
case class のフィー
ルドはデフォルトで
immutable
case class Coin(head: Boolean)!object Coin1 { def flip(c: Coin) = Coin(!c.head) def stay(c: Coin) = c}
case class のフィー
ルドはデフォルトで
immutable
受け取ったCoinは変更せず、新しいCoinを生成
val c0 = Coin(true)val c1 = flip(c0)val c2 = stay(c1)val c3 = flip(c2)println("Showing a head? " + c3.head)
val c0 = Coin(true)val c1 = flip(c0)val c2 = stay(c1)val c3 = flip(c2)println("Showing a head? " + c3.head)
コインの操作クラスで表現してみる
を
Express an ‘Action’ of a coin with a class
case class CoinAction(action: Coin => Coin) extends (Coin => Coin) { def apply(c: Coin) = action(c) def +(next: CoinAction): CoinAction = CoinAction { c0 => val c1 = action(c0) next(c1) }}
case class CoinAction(action: Coin => Coin) extends (Coin => Coin) { def apply(c: Coin) = action(c) def +(next: CoinAction): CoinAction = CoinAction { c0 => val c1 = action(c0) next(c1) }}
Coinの状態を遷移さ
せる関数をラップ
case class CoinAction(action: Coin => Coin) extends (Coin => Coin) { def apply(c: Coin) = action(c) def +(next: CoinAction): CoinAction = CoinAction { c0 => val c1 = action(c0) next(c1) }}
Coinの状態を遷移さ
せる関数をラップ 自身も関数として扱える
case class CoinAction(action: Coin => Coin) extends (Coin => Coin) { def apply(c: Coin) = action(c) def +(next: CoinAction): CoinAction = CoinAction { c0 => val c1 = action(c0) next(c1) }}
Coinの状態を遷移さ
せる関数をラップ 自身も関数として扱える
「コインを受け取った時に、自
身の action を実行してから、次
の操作を実行する」という関数
case class CoinAction(action: Coin => Coin) extends (Coin => Coin) { def apply(c: Coin) = action(c) def +(next: CoinAction): CoinAction = CoinAction { c0 => val c1 = action(c0) next(c1) }}
Coinの状態を遷移さ
せる関数をラップ 自身も関数として扱える
関数として呼び出された際に、
ラップした関数を実行
「コインを受け取った時に、自
身の action を実行してから、次
の操作を実行する」という関数
First class Functions
• 引数として渡せる
• 戻り値として返せる
• 変数に代入できる
val flip = CoinAction(c => Coin(!c.head))val stay = CoinAction(c => c)
flip, stay を CoinAction の
インスタンスとして定義
val action = flip + stay + flipval c = action(Coin(true))println("Showing a head? " + c.head)
+() メソッドで操作を1つにまとめる
val action = flip + stay + flipval c = action(Coin(true))println("Showing a head? " + c.head)
+() メソッドで操作を1つにまとめる
でも、途中の結果も欲しい時は?
case class CoinAction[A](action: Coin => (Coin, A)) extends (Coin => (Coin, A)) { def apply(c: Coin) = action(c) def +[B](next: CoinAction[B]): CoinAction[B] = flatMap(_ => next) def map[B](f: A => B): CoinAction[B] = CoinAction { c0 => val (c1, a) = apply(c0) (c1, f(a)) } def flatMap[B](f: A => CoinAction[B]): CoinAction[B] = CoinAction { c0 => val (c1, a) = apply(c0) f(a)(c1) }}
case class CoinAction[A](action: Coin => (Coin, A)) extends (Coin => (Coin, A)) { def apply(c: Coin) = action(c) def +[B](next: CoinAction[B]): CoinAction[B] = flatMap(_ => next) def map[B](f: A => B): CoinAction[B] = CoinAction { c0 => val (c1, a) = apply(c0) (c1, f(a)) } def flatMap[B](f: A => CoinAction[B]): CoinAction[B] = CoinAction { c0 => val (c1, a) = apply(c0) f(a)(c1) }}
Coinと一緒に 任意の型の結果を返す
case class CoinAction[A](action: Coin => (Coin, A)) extends (Coin => (Coin, A)) { def apply(c: Coin) = action(c) def +[B](next: CoinAction[B]): CoinAction[B] = flatMap(_ => next) def map[B](f: A => B): CoinAction[B] = CoinAction { c0 => val (c1, a) = apply(c0) (c1, f(a)) } def flatMap[B](f: A => CoinAction[B]): CoinAction[B] = CoinAction { c0 => val (c1, a) = apply(c0) f(a)(c1) }}
Coinと一緒に 任意の型の結果を返す
遷移した結果を 任意の型に変換する
case class CoinAction[A](action: Coin => (Coin, A)) extends (Coin => (Coin, A)) { def apply(c: Coin) = action(c) def +[B](next: CoinAction[B]): CoinAction[B] = flatMap(_ => next) def map[B](f: A => B): CoinAction[B] = CoinAction { c0 => val (c1, a) = apply(c0) (c1, f(a)) } def flatMap[B](f: A => CoinAction[B]): CoinAction[B] = CoinAction { c0 => val (c1, a) = apply(c0) f(a)(c1) }}
Coinと一緒に 任意の型の結果を返す
遷移した結果を 任意の型に変換する
次の操作と組み合わせる。ただし、前の結果を元に次の操作を生成する
関数として受け取る
val flip = CoinAction { c => val head = !c.head (Coin(head), head)}val stay = CoinAction(c => (c, c.head))
val flip = CoinAction { c => val head = !c.head (Coin(head), head)}val stay = CoinAction(c => (c, c.head))
Coinと一緒に Boolean 型の結果を
返す
val action = flip.flatMap { _ => stay.flatMap { _ => flip } }val (c, _) = action(Coin(true))println("Showing a head? " + c.head)
val action = flip.flatMap { _ => stay.flatMap { _ => flip } }val (c, _) = action(Coin(true))println("Showing a head? " + c.head)
flip + stay + flip と同様のコード
val action = flip.flatMap { _ => stay.flatMap { _ => flip } }val (c, _) = action(Coin(true))println("Showing a head? " + c.head)
flip + stay + flip と同様のコード
ただし、単なる CoinAction ではなく、CoinAction を返す関数を渡している
val action = flip.flatMap { b1 => stay.flatMap { _ => flip.map { b3 => (b1, b3) } } }val (_, (b1, b3)) = action(Coin(true))println("1st occurrence is a head? " + b1)println("3rd occurrence is a head? " + b3)
val action = flip.flatMap { b1 => stay.flatMap { _ => flip.map { b3 => (b1, b3) } } }val (_, (b1, b3)) = action(Coin(true))println("1st occurrence is a head? " + b1)println("3rd occurrence is a head? " + b3)
結果が Tuple になるよう map() で変換
val action = flip.flatMap { b1 => stay.flatMap { _ => flip.map { b3 => (b1, b3) } } }val (_, (b1, b3)) = action(Coin(true))println("1st occurrence is a head? " + b1)println("3rd occurrence is a head? " + b3)
結果が Tuple になるよう map() で変換
flatMap() が受けるのは、今の操作の結果から、次回以降の操作を作る関数。
val action = flip.flatMap { b1 => stay.flatMap { _ => flip.map { b3 => (b1, b3) } } }val (_, (b1, b3)) = action(Coin(true))println("1st occurrence is a head? " + b1)println("3rd occurrence is a head? " + b3)
結果が Tuple になるよう map() で変換
flatMap() が受けるのは、今の操作の結果から、次回以降の操作を作る関数。なので、b1, b3 がネストした内部ブロックで利用
できる
val action = for { b1 <- flip _ <- stay b3 <- flip} yield (b1, b3)val (_, (b1, b3)) = action(Coin(true))println("1st occurrence is a head? " + b1)println("3rd occurrence is a head? " + b3)
map(), flatMap() があれば、for-comprehension が使える
val action = for { s1 <- flip.map(b1 => "1st occurrence is a head? " + b1) _ <- stay s3 <- flip.map(b3 => "3rd occurrence is a head? " + b3)} yield (s1 + "\n" + s3)val (_, s) = action(Coin(true))println(s)
val action = for { s1 <- flip.map(b1 => "1st occurrence is a head? " + b1) _ <- stay s3 <- flip.map(b3 => "3rd occurrence is a head? " + b3)} yield (s1 + "\n" + s3)val (_, s) = action(Coin(true))println(s)
この時点で、CoinAction[Boolean] をCoinAction[String] に変換して
しまう
CoinActionを抽象化
case class CoinAction[A](action: Coin => (Coin, A)) extends (Coin => (Coin, A)) { def apply(c: Coin) = action(c) def +[B](next: CoinAction[B]): CoinAction[B] = flatMap(_ => next) def map[B](f: A => B): CoinAction[B] = CoinAction { c0 => val (c1, a) = apply(c0) (c1, f(a)) } def flatMap[B](f: A => CoinAction[B]): CoinAction[B] = CoinAction { c0 => val (c1, a) = apply(c0) f(a)(c1) }}
case class CoinAction[A](action: Coin => (Coin, A)) extends (Coin => (Coin, A)) { def apply(c: Coin) = action(c) def +[B](next: CoinAction[B]): CoinAction[B] = flatMap(_ => next) def map[B](f: A => B): CoinAction[B] = CoinAction { c0 => val (c1, a) = apply(c0) (c1, f(a)) } def flatMap[B](f: A => CoinAction[B]): CoinAction[B] = CoinAction { c0 => val (c1, a) = apply(c0) f(a)(c1) }}
Coin のメソッドをどこからも呼び出してない
case class State[S, +A](run: S => (S, A)) { def map[B](f: A => B): State[S, B] = State { s => val (s2, a) = run(s) (s2, f(a)) } def flatMap[B](f: A => State[S, B]): State[S, B] = State { s => val (s2, a) = run(s) f(a).run(s2) }}
Coin を型パラメーター S として抽象化
Functional Programming in Scala http://www.manning.com/bjarnason/ より抜粋、一部改変
type CoinAction[A] = State[Coin, A]val flip: CoinAction[Boolean] = State { c => val head = !c.head (Coin(head), head)}val stay: CoinAction[Boolean] = State(c => (c, c.head))
CoinAction を type alias として定義
先ほどと同じように使えます
Wait …
それでできるよScalaz
Scalaz
Scalaz provides purely functional data structures to complement those
from the Scala standard library.— from http://typelevel.org/projects/scalaz/
import scalaz.Statetype CoinAction[A] = State[Coin, A]
scalaz の State を使う
先ほどと同じように使えます
まとめSummary
ScalaはOOとFPがミックスした マルチパラダイム言語
関数型プログラミングでは純粋関数を使う
状態遷移を表わすCoinAction を作り、ステート
マシンを実装した
CoinAction を State として抽象化
Scalaz の State を紹介
詳しい解説ドキュメント& 完全なソースコードはこちら
https://github.com/hamazy/scala-fp-calisthenics
Q&A
Image Credits