functional way

18
Functional Way

Upload: kent-ohashi

Post on 16-Apr-2017

133 views

Category:

Software


0 download

TRANSCRIPT

Functional Way

自己紹介: lagénorhynque

(defprofile lagénorhynque [Kent OHASHI] :github/twitter @lagenorhynque :company 株式会社オプト :languages [Clojure Haskell Python Scala Go English français Deutsch русский] :interests [プログラミング 語学 数学])

「フィボナッチ数」( )の計算を例にFibonacci number

関数型プログラミングの基本的な概念を紹介

1. 再帰 (recursion)2. 末尾再帰 (tail recursion)3. 高階関数 (higher-order function)4. 遅延評価 (lazy evaluation)

フィボナッチ数

番目のフィボナッチ数 は、以下のように定義される。i Fi

F0

F1

Fi

===

01

+ , i ~ 2Fi+2 Fi+1

と続き、0, 1, 1, 2, 3, 5, 8, 13, 21, 34, . . .

直前の2項の和が次の項になっている。

0. ループ手続き型( )言語の基本パターンprocedural

# Pythondef fibonacci(i): a, b = 0, 1 for n in range(i): a, b = b, a + b return a

副作用( )命令型( )

side e�ectimperative

変数やループ構造が目立ち、数学的な定義(プログラムの仕様)との関係が分かりづらい

登場する変数が多くなったり、処理が複雑になったりすると、状態の変化をたどるのが困難になりうる

1. 再帰 ( )recursion関数型( )言語の基本パターンfunctional

-- Haskellfibonacci1 :: Int -> Integerfibonacci1 0 = 0fibonacci1 1 = 1fibonacci1 i = fibonacci1 (i - 2) + fibonacci1 (i - 1)

;; Clojure(defn fibonacci1 [i] (cond (= i 0) 0N (= i 1) 1N :else (+ (fibonacci1 (- i 2)) (fibonacci1 (- i 1)))))

パターンマッチング( )宣言型( )参照透過性( )

pattern matchingdeclarative

referential transparency

数学的な再帰的定義をほぼそのまま表現した、シンプルなコード可変状態がないため状態の変化を管理する必要がなくなり、並列/並行処理として実行するのも比較的容易

関数呼出しの繰り返しによりスタックオーバーフローが発生する可能性がある

フィボナッチ数の場合、同一の計算が繰り返されて計算量が指数的に増大してしまう→メモ化( )を検討memoization

2. 末尾再帰 ( )tail recursion関数内部で最後に実行される処理が再帰呼出しになっている再帰

-- Haskellfibonacci2 :: Int -> Integerfibonacci2 i = fib i 0 1 where fib 0 a _ = a fib n a b = fib (n - 1) b (a + b)

;; Clojure(defn fibonacci2 [i] (letfn [(fib [n a b] (if (zero? n) a (recur (dec n) b (+ a b))))] (fib i 0N 1N)))

多くの関数型言語では末尾再帰関数が末尾呼出し最適化(tail calloptimization)により命令型のループと同等の処理に変換され、スタックオーバーフローが防止できるコードの処理内容も命令型ループによく似ている

3. 高階関数 ( )higher-order function引数として関数を受け取る、または戻り値として関数を返す関数

-- Haskellfibonacci3 :: Int -> Integerfibonacci3 i = fst $ foldl' fib (0, 1) [1..i] where fib (a, b) _ = (b, a + b)

;; Clojure(defn fibonacci3 [i] (letfn [(fib [[a b] _] [b (+ a b)])] (first (reduce fib [0N 1N] (range 0 i)))))

典型的な繰り返し処理は抽象化されたライブラリの高階関数に任せ、固有のロジックを持った関数の実装に集中することで、効率良くコーディングすることができ、コードの可読性も向上するオブジェクト指向プログラミングのデザインパターンの多くは高階関数によって同等の目的を果たせる

4. 遅延評価 ( )lazy evaluation式の評価を計算で必要になるまで遅らせる評価戦略

cf. 先行評価( )eager evaluation

-- Haskellfibonacci4 :: Int -> Integerfibonacci4 i = fibs !! i where fibs = map fst $ iterate (\(a, b) -> (b, a + b)) (0, 1)

;; Clojure(defn fibonacci4 [i] (let [fibs (map first (iterate (fn [[a b]] [b (+ a b)]) [0N 1N]))] (nth fibs i)))

-- Haskellfibonacci5 :: Int -> Integerfibonacci5 i = fibs !! i where fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

;; Clojure(defn fibonacci5 [i] (letfn [(fibs [a b] (cons a (lazy-seq (fibs b (+ a b)))))] (nth (fibs 0N 1N) i)))

Haskellでは遅延評価がデフォルトの評価戦略Clojureは先行評価が基本だが、遅延評価されるシーケンス(遅延シーケンス)が利用できる特に巨大なデータ構造や無限に続くデータ構造を扱う場合に、シンプルな定義と効率を両立させることができる

Further ReadingHaskell

『プログラミングHaskell』『すごいHaskellたのしく学ぼう!』『関数プログラミング実践入門』

Clojure

『プログラミングClojure』Exploring Clojure with Factorial Computation

Scala

『Scalaスケーラブルプログラミング』『Scala関数型デザイン&プログラミング』

Erlang

『すごいErlangゆかいに学ぼう!』Elixir

『プログラミングElixir』OCaml

『プログラミングの基礎』

cf. 今回の発表の元ネタ: BasicsOfFunctionalProgramming.md