introduction to scala functional programming

Post on 11-Jun-2015

459 Views

Category:

Software

2 Downloads

Preview:

Click to see full reader

TRANSCRIPT

関数型プログラミング

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

top related