関数型言語テイスティング
Haskell, Scala, Clojure, Elixirを
比べて味わう関数型プログラミングの旨さ
#fp_matsuri #fp_matsuri_c
1
のシニアエンジニア
スタートアップの起業家と投資家のための業務効
率化/連携プラットフォームを開発している
主要技術スタック: Kotlin & TypeScript
の運営にも協力
関数型プログラミング(言語)とLispの熱烈な愛好者
(特に) と がエレガントで好き
の運営スタッフ(座長のひとり)
lagénorhynque🐬カマイルカ
株式会社スマートラウンド
Server-Side Kotlin Meetup
Clojure Haskell
関数型まつり2025
2
[参考] 🐬の関数型言語学習/利用経験
Most Used Languages
Clojure 42.14%
Haskell 19.94%
Scala 10.82%
Python 5.10%
TypeScript 5.03%
Java 4.95%
Elixir 4.10%
Ruby 2.98%
Common Lisp 2.56%
Kotlin 2.38%
GitHubでのコード量× リポジトリ数
3
関数型言語経験: 約12年
cf. プログラミング経験: 約13年
プログラミング学習初期から関数型言語と戯れて
きた(そして非関数型言語に未だに馴染めない😇)
仕事で: Clojure, Scala
趣味で: Clojure, Haskell, Erlang/Elixir, etc.
4
初期(2013年〜)の学びの主な機会
関数型プログラミング(言語)に関する技術書/記事
コミュニティイベント
勉強会: , , ,
, , etc.
もくもく会: ,
読書会:
,
カンファレンス: Haskell Day, ScalaMatsuri,
Erlang & Elixir Fest
Tamachi.clj Shibuya.lisp 渋谷java
tokyo.ex rpscala
Haskellもくもく会 Laketown.clj
関数型プログラミング勉強会
(Scala) SICP読書会
5
オーム社の より
『7つの言語7つの世界』
書籍詳細ページ
6
(すごいH本)
オーム社の より
『すごいHaskellたのしく学ぼう!』
書籍詳細ページ
7
オーム社の より
『プログラミングHaskell』
書籍詳細ページ
8
オーム社の より
『プログラミングClojure 第2版』
書籍詳細ページ
9
(すごいE本)
達人出版会の より
『すごいErlangゆかいに学ぼう!』
書籍詳細ページ
10
(FP in
Scala)
インプレスブックスの より
『Scala関数型デザイン&プログラミング』
書籍詳細ページ 11
(SICP, 魔術師本)
Wikipedia英語版の より
Structure and Interpretation of Computer Programs
Structure and Interpretation of Computer Programs
12
1. きっかけ
2. 「関数型プログラミング」
3. 🍷テイスティング
1. 4つの関数型言語の基本
2. 関数
3. データ
4. 評価
5. ポリモーフィズム
4. 関数型プログラマのメンタルモデル
13
1. きっかけ
14
関数型プログラミング(言語)に関する解説で
🐬は以下のような表現をよく目にする:
近年、関数型プログラミング(言語)の
エッセンスが多くの主流言語に取り
入れられてきました。
15
関数型プログラミング(言語)の
「エッセンス」??? 🤔
16
エッセンス(essence)
語源: 🇬🇧essence < essentia < esse + -ia (≒ 🇬🇧
beingness, あるということ)
語義: 「本質(的に重要なもの)」
抽出物(エキス)という派生的/比喩的な意味も
本来、単なる成分/要素のような意味ではない
cf. 🇫🇷哲学の文脈で関連する表現
l'existence précède l'essence (実存は本質に先立
つ)
L'essentiel est invisible pour les yeux. (🦊「大切
なものは目に見えないんだよ。
」
)
17
ワイン🍷テイスティングでは色(視覚)・香り(嗅覚)・
味(味覚)から吟味するように、
4つの関数型言語を例にその味わいを多角的に探り、
「エッセンス」(= 本質)を見出したい
→関数型言語テイスティング
18
2. 「関数型プログラミング」
19
(現在の)🐬によるFPとFPLの定義
関数型プログラミング:= 純粋関数を基本要素とし
てその組み合わせによってプログラムを構成してい
くプログラミングスタイル
→言語を問わず実践可能(実践しやすさは異なる)
関数型言語:= 関数型プログラミングが言語/標準ラ
イブラリレベルで十分に支援される(そして関数型
プログラミングスタイルがユビキタスな)言語
→例えばJavaScript/TypeScriptやJava、Kotlin、
古典的なLisp方言は含めない
20
重視する
もの
(values)
⾔語
(languages)
処理系/実装
(implementations)
パターン
(patterns)
Lisp
式指向
(expression-oriented)
不変性
(immutability)
宣⾔型プログラミング
(declarative programming)
(型)安全性
((type) safety)
合成可能性
(composability)
永続性
(persistence)
純粋性
(purity)
Clojure
Erlang
Elixir
Haskell
OCaml
Standard ML
ML
適⽤
(apply)
評価
(eval)
抽象構⽂⽊
(abstract syntax tree; AST)
マクロ
(macro)
参照透過性
(referential transparency)
再帰
(recursion)
遅延評価
(lazy evaluation)
メモ化
(memoization)
並⾏プログラミング
(concurrent programming)
アクターモデル
(actor model)
STM
(software transactional
memory)
CSP
(communicating sequential
processes)
継続
(continuation)
⾼階関数
(higher-order function) 関数合成
(function composition)
カリー化
(currying)
部分適⽤
(partial application)
関数型
プログラミング
(functional
programming)
理論
(theories)
数学
(mathematics)
圏論
(category theory)
型システム
(type system)
ラムダ計算
(lambda calculus)
メタプログラミング
(metaprogramming)
形式⼿法
(formal methods)
定理証明⽀援系
(theorem prover)
評価
(evaluation)
制御
(control)
関数
(function)
パイプ演算⼦
(pipe operator)
メソッドチェーン
(method chaining)
代数的データ型
(algebraic data type; ADT)
パターンマッチ
(pattern matching)
クロージャー/関数閉包
(closure)
オブジェクト
(object)
Idris
Elm
パーサーコンビネーター
(parser combinator)
離散数学
(discrete mathematics)
契約プログラミング
(contract programming)
抽象データ型
(abstract data type)
データ
(data)
直和型
(sum type)
直積型
(product type)
篩型
(refinement type)
依存型
(dependent type)
カプセル化
(encapsulation)
ポリモーフィズム/多相
(polymorphism)
サブタイプ多相
(subtype polymorphism)
パラメータ多相
(parametric polymorphism)
アドホック多相
(ad hoc polymorphism)
型クラス
(type class)
マルチメソッド
(multimethod)
プロトコル
(protocol)
変性
(variance)
継承
(inheritance)
カリー=ハワード同型対応
(Curry–Howard
correspondence)
分配束縛
(destructuring)
純粋関数型データ構造
(purely functional
data structure)
永続データ構造
(persistent data structure)
Coq (Rocq)
Scala
構⽂解析
(parse)
オーバーロード/多重定義
(overloading)
命令型プログラミング
(imperative programming)
⽂指向
(statement-oriented)
副作⽤
(side effect)
破壊的更新
(mutation)
可変性
(mutability)
pipes & filters
goroutines & channels
プロパティベーステスト
(property-based testing)
⼊出⼒
(I/O)
データ指向プログラミング
(data-oriented programming)
ファンクター
(functor)
モナド
(monad)
リスト
(list)
遅延リスト/ストリーム
(lazy list/stream)
同図像性
(homoiconicity)
超循環評価器
(meta-circular evaluator)
セルフホスティング
(self-hosting)
ベクター
(vector)
全域関数
(total function)
部分関数
(partial function)
オブジェクト指向プログラミング
(object-oriented
programming)
アプリカティブ
(applicative)
トレイト
(trait)
Agda
必要呼び
(call by need)
F#
ジェネリクス
(generics)
Lean
Gleam
末尾再帰
(tail recursion)
意味論
(semantics)
型推論
(type inference)
再帰型
(recursive type)
GoFデザインパターン
(GoF Design Patterns)
※ 🐬が思い浮かぶ概念/用語を連想的に列挙したもの(網羅的でも体系的でもない)
🐬の「関数型プログラミング」コンセプトマップ
21
重視する
もの
(values)
式指向
(expression-oriented)
不変性
(immutability)
宣⾔型プログラミング
(declarative programming)
(型)安全性
((type) safety)
合成可能性
(composability)
永続性
(persistence)
純粋性
(purity)
参照透過性
(referential transparency)
命令型プログラミング
(imperative programming)
⽂指向
(statement-oriented)
副作⽤
(side effect)
破壊的更新
(mutation)
可変性
(mutability)
⼊出⼒
(I/O)
22
⾔語
(languages)
Lisp
Clojure
Erlang
Elixir
Haskell
OCaml
Standard ML
ML
Idris
Elm
Coq (Rocq)
Scala
Agda
F#
Lean
Gleam
23
理論
(theories)
数学
(mathematics)
圏論
(category theory)
型システム
(type system)
ラムダ計算
(lambda calculus)
形式⼿法
(formal methods)
定理証明⽀援系
(theorem prover)
離散数学
(discrete mathematics)
契約プログラミング
(contract programming)
篩型
(refinement type)
依存型
(dependent type)
カリー=ハワード同型対応
(Curry–Howard
correspondence)
プロパティベーステスト
(property-based testing)
意味論
(semantics)
型推論
(type inference)
24
処理系/実装
(implementations)
適⽤
(apply)
評価
(eval)
抽象構⽂⽊
(abstract syntax tree; AST)
マクロ
(macro)
メタプログラミング
(metaprogramming)
パーサーコンビネーター
(parser combinator)
構⽂解析
(parse)
同図像性
(homoiconicity)
超循環評価器
(meta-circular evaluator)
セルフホスティング
(self-hosting)
25
パターン
(patterns)
再帰
(recursion)
遅延評価
(lazy evaluation)
メモ化
(memoization)
並⾏プログラミング
(concurrent programming)
アクターモデル
(actor model)
STM
(software transactional
memory)
CSP
(communicating sequential
processes)
継続
(continuation)
⾼階関数
(higher-order function) 関数合成
(function composition)
カリー化
(currying)
部分適⽤
(partial application)
評価
(evaluation)
制御
(control)
関数
(function)
パイプ演算⼦
(pipe operator)
メソッドチェーン
(method chaining)
代数的データ型
(algebraic data type; ADT)
パターンマッチ
(pattern matching)
クロージャー/関数閉包
(closure)
オブジェクト
(object)
抽象データ型
(abstract data type)
データ
(data)
直和型
(sum type)
直積型
(product type)
カプセル化
(encapsulation)
ポリモーフィズム/多相
(polymorphism)
サブタイプ多相
(subtype polymorphism)
パラメータ多相
(parametric polymorphism)
アドホック多相
(ad hoc polymorphism)
型クラス
(type class)
マルチメソッド
(multimethod)
プロトコル
(protocol)
変性
(variance)
継承
(inheritance)
分配束縛
(destructuring)
純粋関数型データ構造
(purely functional
data structure)
永続データ構造
(persistent data structure)
オーバーロード/多重定義
(overloading)
pipes & filters
goroutines & channels
データ指向プログラミング
(data-oriented programming)
ファンクター
(functor)
モナド
(monad)
リスト
(list)
遅延リスト/ストリーム
(lazy list/stream)
ベクター
(vector)
全域関数
(total function)
部分関数
(partial function)
オブジェクト指向プログラミング
(object-oriented
programming)
アプリカティブ
(applicative)
トレイト
(trait)
必要呼び
(call by need)
ジェネリクス
(generics)
末尾再帰
(tail recursion)
再帰型
(recursive type)
GoFデザインパターン
(GoF Design Patterns)
26
3. 🍷テイスティング
1. 4つの関数型言語の基本
2. 関数
3. データ
4. 評価
5. ポリモーフィズム
27
3-1. 🍷4つの関数型言語の基本
Haskell, Scala, Clojure, Elixir
28
⾔語
(languages)
Lisp
Clojure
Erlang
Elixir
Haskell
OCaml
Standard ML
ML
Idris
Elm
Coq (Rocq)
Scala
Agda
F#
Lean
Gleam
29
paradigm FP OOP,
FP
FP FP
typing static static dynamic dynamic
first
appeared
1990 2004 2007 2012
related ML ML Lisp Lisp
🐬's note lazy/pure
FPL
standard
OOPL
in
FPL's
skin
modern
functional
Lisp
Erlang +
Ruby +
Clojure
Haskell Scala Clojure Elixir
30
モジュール定義とトップレベル変数
{- Haskell -}
-- モジュールとそのエクスポートリスト
module SomeModule(a) where
-- パブリック変数
a :: Int
a = 1
-- プライベート変数
b :: Int
b = 2
/* Scala */
// (シングルトン)オブジェクト
object SomeModule:
// パブリック変数
val a: Int = 1
// プライベート変数
private val b: Int = 2
31
;;; Clojure
;; 名前空間
(ns some-module)
;; パブリック変数
(def a 1)
;; プライベート変数
(def ^:private b 2)
## Elixir
# モジュール
defmodule SomeModule do
# パブリックな0引数関数
def a, do: 1
# プライベートな0引数関数
defp b, do: 2
end
32
トップレベル関数
{- Haskell -}
module SimpleMath(square) where
-- パブリック関数
square :: Int -> Int
square n = n * n
-- プライベート関数
double :: Int -> Int
double n = n * 2
/* Scala */
object SimpleMath:
// パブリックメソッド
def square(n: Int): Int =
n * n
// プライベートメソッド
private def double(n: Int): Int =
n * 2
33
;;; Clojure
(ns simple-math)
;; パブリック関数
(defn square [n] (* n n))
;; プライベート関数
(defn- double' [n] (* n 2))
## Elixir
defmodule SimpleMath do
# パブリック関数
def square(n), do: n * n
# プライベート関数
defp double(n), do: n * 2
end
34
モジュールの利用
{- Haskell: ghciコマンドによるREPL -}
λ> :l SimpleMath
...
λ> import qualified SimpleMath as M -- モジュールを別名で参照
λ> M.square 3
9
it :: Int
λ> map M.square [0..10]
[0,1,4,9,16,25,36,49,64,81,100]
it :: [Int]
/* Scala: scalaコマンドによるREPL */
scala> :l SimpleMath.scala
// defined object SimpleMath
scala> import SimpleMath as M // オブジェクトを別名で参照
scala> M.square(3)
val res0: Int = 9
scala> (0 to 10).map(M.square)
val res1: IndexedSeq[Int] = Vector(0, 1, 4, 9, 16, 25, 36,
49, 64, 81, 100)
35
;;; Clojure: cljコマンドによるREPL
user=> (clojure.main/load-script "simple_math.clj")
#'simple-math/double'
user=> (require '[simple-math :as m]) ; 名前空間を別名で参照
nil
user=> (m/square 3)
9
user=> (map m/square (range 0 (inc 10)))
(0 1 4 9 16 25 36 49 64 81 100)
## Elixir: iexコマンドによるREPL
# `iex simple_math.ex` でファイルを読み込んで起動
iex(1)> alias SimpleMath, as: M # モジュールを別名で参照
SimpleMath
iex(2)> M.square(3)
9
iex(3)> Enum.map(0..10, &M.square/1)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
36
ローカル束縛(変数)
{- Haskell -}
λ> :{ -- REPLでの複数行入力の開始
λ| -- ML系言語でお馴染み(?)のlet式
λ| let x = 5
λ| y = M.square(x)
λ| in "square " ++ show x ++ " = " ++ show y
λ| :} -- REPLでの複数行入力の終了
"square 5 = 25"
it :: [Char]
/* Scala */
scala> val x = 5
val x: Int = 5
scala> val y = M.square(x)
val y: Int = 25
scala> s"square($x) = $y"
val res2: String = square(5) = 25
37
;;; Clojure
;; Lisp系言語でお馴染み(?)のlet式
user=> (let [x 5
y (m/square x)]
(str "(square " x ") = " y))
"(square 5) = 25"
## Elixir
iex(4)> x = 5
5
iex(5)> y = M.square(x)
25
iex(6)> "square(#{x}) = #{y}"
"square(5) = 25"
38
無名関数(ラムダ式)
{- Haskell -}
λ> (n -> n * n * n) 2
8
it :: Num a => a
λ> map (n -> n * n * n) [0..10]
[0,1,8,27,64,125,216,343,512,729,1000]
it :: (Num b, Enum b) => [b]
/* Scala */
scala> ((n: Int) => n * n * n)(2)
val res0: Int = 8
scala> (0 to 10).map(n => n * n * n)
val res1: IndexedSeq[Int] = Vector(0, 1, 8, 27, 64, 125, 216,
343, 512, 729, 1000)
39
;;; Clojure
user=> ((fn [n] (* n n n )) 2)
8
user=> (#(* % % %) 2) ; 無名関数の略記法
8
user=> (map (fn [n] (* n n n )) (range 0 (inc 10)))
(0 1 8 27 64 125 216 343 512 729 1000)
user=> (map #(* % % %) (range 0 (inc 10)))
(0 1 8 27 64 125 216 343 512 729 1000)
## Elixir
iex(1)> (fn n -> n * n * n end).(2)
8
iex(2)> (&(&1 * &1 * &1)).(2) # 無名関数の略記法
8
iex(3)> Enum.map(0..10, fn n -> n * n * n end)
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
iex(4)> Enum.map(0..10, &(&1 * &1 * &1))
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
40
再帰と無限リスト(1): 階乗
{- Haskell -}
module Factorial where
factorial :: Integral a => a -> a
factorial 0 = 1
factorial n = n * factorial (n - 1)
factorialSeq :: Integral a => [a]
factorialSeq = scanl (*) 1 [1..]
-- import Factorial (factorialSeq)
λ> take 10 factorialSeq
[1,1,2,6,24,120,720,5040,40320,362880]
it :: Integral a => [a]
λ> factorialSeq !! 100
9332621544394415268169923885626670049071596826438162146859296
3895217599993229915608941463976156518286253697920827223758251
185210916864000000000000000000000000
it :: Integral a => a
41
/* Scala */
object Factorial:
def factorial(n: Int): BigInt = n match
case 0 => 1
case _ => n * factorial(n - 1)
def factorialSeq: LazyList[BigInt] =
LazyList.from(1).scanLeft(BigInt(1))(_ * _)
// import Factorial.factorialSeq
scala> factorialSeq.take(10).toList
val res0: List[BigInt] = List(1, 1, 2, 6, 24, 120, 720, 5040,
40320, 362880)
scala> factorialSeq(100)
val res1: BigInt = 933262154439441526816992388562667004907159
6826438162146859296389521759999322991560894146397615651828625
3697920827223758251185210916864000000000000000000000000
42
;;; Clojure
(ns factorial)
(defn factorial [n]
(if (zero? n)
1
(*' n (factorial (dec' n)))))
(defn factorial-seq []
(reductions *' 1 (iterate inc' 1)))
;; (require '[factorial :refer [factorial-seq]])
user=> (take 10 (factorial-seq))
(1 1 2 6 24 120 720 5040 40320 362880)
user=> (nth (factorial-seq) 100)
9332621544394415268169923885626670049071596826438162146859296
3895217599993229915608941463976156518286253697920827223758251
185210916864000000000000000000000000N
43
## Elixir
defmodule Factorial do
def factorial(0), do: 1
def factorial(n), do: n * factorial(n - 1)
def factorial_seq do
Stream.concat(
[1],
Stream.scan(Stream.from_index(1), &*/2)
)
end
end
# import Factorial, only: [factorial_seq: 0]
iex(2)> Enum.take(factorial_seq, 10)
[1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]
iex(3)> Enum.at(factorial_seq, 100)
9332621544394415268169923885626670049071596826438162146859296
3895217599993229915608941463976156518286253697920827223758251
185210916864000000000000000000000000
44
再帰と無限リスト(2): フィボナッチ数
{- Haskell -}
module Fibonacci where
fib :: Integral a => a -> a
fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)
fibSeq :: Integral a => [a]
fibSeq = map fst $ iterate ((a, b) -> (b, a + b)) (0, 1)
-- import Fibonacci (fibSeq)
λ> take 10 fibSeq
[0,1,1,2,3,5,8,13,21,34]
it :: Integral a => [a]
λ> take 3 $ drop 100 fibSeq
[354224848179261915075,573147844013817084101,
927372692193078999176]
it :: Integral a => [a]
45
/* Scala */
object Fibonacci:
def fib(n: Int): BigInt = n match
case 0 => 0
case 1 => 1
case _ => fib(n - 1) + fib(n - 2)
def fibSeq: LazyList[BigInt] =
LazyList.iterate((BigInt(0), BigInt(1))) {
case (a, b) => (b, a + b)
}.map(_(0))
// import Fibonacci.fibSeq
scala> fibSeq.take(10).toList
val res2: List[BigInt] = List(0, 1, 1, 2, 3, 5, 8, 13, 21,
34)
scala> fibSeq.drop(100).take(3).toList
val res3: List[BigInt] = List(354224848179261915075,
573147844013817084101, 927372692193078999176)
46
;;; Clojure
(ns fibonacci)
(defn fib [n]
(case n
0 0
1 1
(+' (fib (-' n 1))
(fib (-' n 2)))))
(defn fib-seq []
(->> [0 1]
(iterate (fn [[a b]] [b (+' a b)]))
(map first)))
;; (require '[fibonacci :refer [fib-seq]])
user=> (take 10 (fib-seq))
(0 1 1 2 3 5 8 13 21 34)
user=> (->> (fib-seq) (drop 100) (take 3))
(354224848179261915075N 573147844013817084101N
927372692193078999176N)
47
## Elixir
defmodule Fibonacci do
def fib(0), do: 0
def fib(1), do: 1
def fib(n), do: fib(n - 1) + fib(n - 2)
def fib_seq do
{0, 1}
|> Stream.iterate(fn {a, b} -> {b, a + b} end)
|> Stream.map(&elem(&1, 0))
end
end
# import Fibonacci, only: [fib_seq: 0]
iex(2)> Enum.take(fib_seq, 10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
iex(3)> fib_seq |> Stream.drop(100) |> Enum.take(3)
[354224848179261915075, 573147844013817084101,
927372692193078999176]
48
[PR] のグッズ👕
関数型まつり公式オンラインストア
49
3-2. 🍷関数
50
パターン
(patterns)
再帰
(recursion)
遅延評価
(lazy evaluation)
メモ化
(memoization)
並⾏プログラミング
(concurrent programming)
アクターモデル
(actor model)
STM
(software transactional
memory)
CSP
(communicating sequential
processes)
継続
(continuation)
⾼階関数
(higher-order function) 関数合成
(function composition)
カリー化
(currying)
部分適⽤
(partial application)
評価
(evaluation)
制御
(control)
関数
(function)
パイプ演算⼦
(pipe operator)
メソッドチェーン
(method chaining)
代数的データ型
(algebraic data type; ADT)
パターンマッチ
(pattern matching)
クロージャー/関数閉包
(closure)
オブジェクト
(object)
抽象データ型
(abstract data type)
データ
(data)
直和型
(sum type)
直積型
(product type)
カプセル化
(encapsulation)
ポリモーフィズム/多相
(polymorphism)
サブタイプ多相
(subtype polymorphism)
パラメータ多相
(parametric polymorphism)
アドホック多相
(ad hoc polymorphism)
型クラス
(type class)
マルチメソッド
(multimethod)
プロトコル
(protocol)
変性
(variance)
継承
(inheritance)
分配束縛
(destructuring)
純粋関数型データ構造
(purely functional
data structure)
永続データ構造
(persistent data structure)
オーバーロード/多重定義
(overloading)
pipes & filters
goroutines & channels
データ指向プログラミング
(data-oriented programming)
ファンクター
(functor)
モナド
(monad)
リスト
(list)
遅延リスト/ストリーム
(lazy list/stream)
ベクター
(vector)
全域関数
(total function)
部分関数
(partial function)
オブジェクト指向プログラミング
(object-oriented
programming)
アプリカティブ
(applicative)
トレイト
(trait)
必要呼び
(call by need)
ジェネリクス
(generics)
末尾再帰
(tail recursion)
再帰型
(recursive type)
GoFデザインパターン
(GoF Design Patterns)
51
⾼階関数
(higher-order function) 関数合成
(function composition)
カリー化
(currying)
部分適⽤
(partial application)
関数
(function)
パイプ演算⼦
(pipe operator)
メソッドチェーン
(method chaining)
クロージャー/関数閉包
(closure)
オブジェクト
(object)
pipes & filters
全域関数
(total function)
部分関数
(partial function)
オブジェクト指向プログラミング
(object-oriented
programming)
GoFデザインパターン
(GoF Design Patterns)
52
式を評価すると値が得られる
部分式を関数として抽出(分解)し関数を組み合わせ
(合成)て式を構成する
→関数が簡潔に楽に組み合わせられると旨い😋
53
部分適用
{- Haskell -}
λ> :t map -- 関数はカリー(1引数関数の連鎖)化されている
map :: (a -> b) -> [a] -> [b]
λ> :t (+) -- 演算子もカリー化されている
(+) :: Num a => a -> a -> a
λ> :t (+ 1) -- x -> x + 1 と等価(セクション記法による部分適用)
(+ 1) :: Num a => a -> a
λ> :t map (+ 1) -- xs -> map (+ 1) xs と等価(部分適用)
map (+ 1) :: Num b => [b] -> [b]
λ> map (+ 1) [0..9]
[1,2,3,4,5,6,7,8,9,10]
it :: (Num b, Enum b) => [b]
λ> f x y z = x * y * z
f :: Num a => a -> a -> a -> a
λ> :t f 2 3 -- 2番目の引数まで部分適用
f 2 3 :: Num a => a -> a
λ> f 2 3 4
24
it :: Num a => a
54
/* Scala */
scala> :t (1 to 10).map // メソッドが関数になる(eta-expansion)
(Int => Any) => IndexedSeq[Any]
scala> :t (_: Int) + (_: Int) // (x, y) => x + y と等価
(Int, Int) => Int
scala> :t (_: Int) + 1 // x => x + 1 と等価(部分適用)
Int => Int
scala> (0 to 9).map(_ + 1)
val res0: IndexedSeq[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9,
10)
scala> def f(x: Int)(y: Int)(z: Int): Int = x * y * z
def f(x: Int)(y: Int)(z: Int): Int
scala> f(2)(3) // 2番目の引数まで部分適用
val res1: Int => Int = Lambda/0x00001ff001554c40@478c84aa
scala> f(2)(3)(4)
val res2: Int = 24
55
;;; Clojure
user=> #(+ %1 %2 %3) ; (fn [x y z] (+ x y z)) と等価
#object[user$eval245$fn__246 0x7103ab0 "user$eval245$fn__246@
7103ab0"]
user=> #(+ % 1) ; (fn [x] (+ x 1)) と等価(部分適用)
#object[user$eval235$fn__236 0x4c03a37 "user$eval235$fn__236@
4c03a37"]
user=> (map #(+ % 1) (range 0 (inc 9)))
(1 2 3 4 5 6 7 8 9 10)
user=> (defn f [x y z] (* x y z))
#'user/f
user=> (partial f 2 3) ; 2番目の引数まで部分適用 = #(f 2 3 %)
#object[clojure.core$partial$fn__5929 0x4ba89729 "clojure.cor
e$partial$fn__5929@4ba89729"]
user=> ((partial f 2 3) 4)
24
user=> (f 2 3 4)
24
56
## Elixir
iex(1)> &(&1 + &2) # fn (x, y) -> x + y end と等価
&:erlang.+/2
iex(2)> &(&1 + 1) # fn x -> x + 1 end と等価(部分適用)
#Function<42.81571850/1 in :erl_eval.expr/6>
iex(3)> Enum.map(0..9, &(&1 + 1))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
iex(4)> f = fn x -> fn y -> fn z -> x * y * z end end end
#Function<42.81571850/1 in :erl_eval.expr/6>
iex(5)> f.(2).(3) # 2番目の引数まで部分適用
#Function<42.81571850/1 in :erl_eval.expr/6>
iex(6)> f.(2).(3).(4)
24
57
関数合成
ref.
{- Haskell: import Fibonacci (fibSeq) -}
λ> sumOfEvenFibs upper = sum (takeWhile (<= upper) (filter
even (drop 2 fibSeq)))
sumOfEvenFibs :: Integral a => a -> a
λ> :{
λ| sumOfEvenFibs' upper = sum
λ| $ takeWhile (<= upper) -- f $ x = f x
λ| $ filter even -- (関数適用演算子)
λ| $ drop 2 fibSeq
λ| :}
sumOfEvenFibs' :: Integral a => a -> a
λ> sumOfEvenFibs' 4000000
4613732
it :: Integral a => a
#2 Even Fibonacci Numbers - Project Euler
58
{- Haskell -}
λ> :{
λ| sumOfEvenNums :: Integral a => a -> [a] -> a
λ| sumOfEvenNums upper = sum
λ| . takeWhile (<= upper) -- f . g = x -> f (g x)
λ| . filter even -- (関数合成演算子)
λ| :}
sumOfEvenNums :: Integral a => a -> [a] -> a
λ> sumOfEvenNums 4000000 $ drop 2 fibSeq
4613732
it :: Integral a => a
59
/* Scala: import Fibonacci.fibSeq */
scala> def sumOfEvenFibs(upper: BigInt) =
| fibSeq
| .drop(2)
| .filter(_ % 2 == 0)
| .takeWhile(_ <= upper)
| .sum
|
def sumOfEvenFibs(upper: BigInt): BigInt
scala> sumOfEvenFibs(4000000)
val res0: BigInt = 4613732
60
/* Scala */
scala> extension (nums: Seq[BigInt]) // 拡張メソッドの定義
| def sumOfEvenNums(upper: BigInt): BigInt =
| nums
| .filter(_ % 2 == 0)
| .takeWhile(_ <= upper)
| .sum
|
def sumOfEvenNums(ns: Seq[BigInt])(upper: BigInt): BigInt
scala> fibSeq.drop(2).sumOfEvenNums(4000000)
val res1: BigInt = 4613732
61
/* Scala */
scala> def sumOfEvenNums2(upper: BigInt)(nums: Seq[BigInt]):
BigInt =
| nums
| .filter(_ % 2 == 0)
| .takeWhile(_ <= upper)
| .sum
|
def sumOfEvenNums2(upper: BigInt)(nums: Seq[BigInt]): BigInt
scala> import scala.util.chaining._ // pipeメソッドを導入
scala> fibSeq.drop(2).pipe(sumOfEvenNums2(4000000))
val res2: BigInt = 4613732
62
;;; Clojure: (require '[fibonacci :refer [fib-seq]])
user=> (defn sum-of-even-fibs [upper]
(apply +
(take-while #(<= % upper)
(filter even?
(drop 2 (fib-seq))))))
#'user/sum-of-even-fibs
user=> (defn sum-of-even-fibs' [upper]
(->> (fib-seq) ; (->> x f g) = (g (f x))
(drop 2) ; (thread-lastマクロ)
(filter even?)
(take-while #(<= % upper))
(apply +)))
#'user/sum-of-even-fibs'
user=> (sum-of-even-fibs' 4000000)
4613732
63
;;; Clojure
user=> (defn sum-of-even-fibs'' [upper]
(transduce
;; 関数合成関数compでトランスデューサー(transducer)を合成
(comp (drop 2)
(filter even?)
(take-while #(<= % upper)))
+
(fib-seq)))
#'user/sum-of-even-fibs''
user=> user=> (sum-of-even-fibs'' 4000000)
4613732
user=> (defn sum-of-even-nums [upper nums]
(transduce (comp (filter even?)
(take-while #(<= % upper)))
+
nums))
#'user/sum-of-even-nums
user=> (->> (fib-seq) (drop 2) (sum-of-even-nums 4000000))
4613732
64
## Elixir: import Fibonacci, only: [fib_seq: 0]
iex(2)> require Integer
Integer
iex(3)> sum_of_even_fibs = fn upper ->
...(3)> Enum.sum(Stream.take_while(Stream.filter(
Stream.drop(fib_seq, 2), &Integer.is_even/1),
&(&1 <= upper)))
...(3)> end
#Function<42.81571850/1 in :erl_eval.expr/6>
iex(4)> sum_of_even_fibs2 = fn upper ->
...(4)> fib_seq # x |> f |> g = g(f(x))
...(4)> |> Stream.drop(2) # (パイプ演算子)
...(4)> |> Stream.filter(&Integer.is_even/1)
...(4)> |> Stream.take_while(&(&1 <= upper))
...(4)> |> Enum.sum()
...(4)> end
#Function<42.81571850/1 in :erl_eval.expr/6>
iex(5)> sum_of_even_fibs2.(4000000)
4613732
65
## Elixir
iex(6)> sum_of_even_nums = fn nums, upper ->
...(6)> nums
...(6)> |> Stream.filter(&Integer.is_even/1)
...(6)> |> Stream.take_while(&(&1 <= upper))
...(6)> |> Enum.sum()
...(6)> end
#Function<41.81571850/2 in :erl_eval.expr/6>
iex(7)> fib_seq |> Stream.drop(2) |>
sum_of_even_nums.(4000000)
4613732
66
3-3. 🍷データ
67
パターン
(patterns)
再帰
(recursion)
遅延評価
(lazy evaluation)
メモ化
(memoization)
並⾏プログラミング
(concurrent programming)
アクターモデル
(actor model)
STM
(software transactional
memory)
CSP
(communicating sequential
processes)
継続
(continuation)
⾼階関数
(higher-order function) 関数合成
(function composition)
カリー化
(currying)
部分適⽤
(partial application)
評価
(evaluation)
制御
(control)
関数
(function)
パイプ演算⼦
(pipe operator)
メソッドチェーン
(method chaining)
代数的データ型
(algebraic data type; ADT)
パターンマッチ
(pattern matching)
クロージャー/関数閉包
(closure)
オブジェクト
(object)
抽象データ型
(abstract data type)
データ
(data)
直和型
(sum type)
直積型
(product type)
カプセル化
(encapsulation)
ポリモーフィズム/多相
(polymorphism)
サブタイプ多相
(subtype polymorphism)
パラメータ多相
(parametric polymorphism)
アドホック多相
(ad hoc polymorphism)
型クラス
(type class)
マルチメソッド
(multimethod)
プロトコル
(protocol)
変性
(variance)
継承
(inheritance)
分配束縛
(destructuring)
純粋関数型データ構造
(purely functional
data structure)
永続データ構造
(persistent data structure)
オーバーロード/多重定義
(overloading)
pipes & filters
goroutines & channels
データ指向プログラミング
(data-oriented programming)
ファンクター
(functor)
モナド
(monad)
リスト
(list)
遅延リスト/ストリーム
(lazy list/stream)
ベクター
(vector)
全域関数
(total function)
部分関数
(partial function)
オブジェクト指向プログラミング
(object-oriented
programming)
アプリカティブ
(applicative)
トレイト
(trait)
必要呼び
(call by need)
ジェネリクス
(generics)
末尾再帰
(tail recursion)
再帰型
(recursive type)
GoFデザインパターン
(GoF Design Patterns)
68
代数的データ型
(algebraic data type; ADT)
パターンマッチ
(pattern matching)
抽象データ型
(abstract data type)
データ
(data)
直和型
(sum type)
直積型
(product type)
カプセル化
(encapsulation)
分配束縛
(destructuring)
純粋関数型データ構造
(purely functional
data structure)
永続データ構造
(persistent data structure)
データ指向プログラミング
(data-oriented programming)
ファンクター
(functor)
モナド
(monad)
リスト
(list)
遅延リスト/ストリーム
(lazy list/stream)
ベクター
(vector)
オブジェクト指向プログラミング
(object-oriented
programming)
アプリカティブ
(applicative)
再帰型
(recursive type)
69
不変なデータ構造を関数を介して変換する
データ型を定義したり構築したり分解したり
→不変データ構造が標準で豊富で、データ型の定義/
利用が楽だと旨い😋
70
標準の不変/永続コレクション
{- Haskell -}
λ> :t [1, 2, 3] -- (連結)リスト
[1, 2, 3] :: Num a => [a]
λ> 0 : [1, 2, 3] -- 先頭に要素追加(cons)
[0,1,2,3]
it :: Num a => [a]
λ> import qualified Data.Vector as V
λ> :t V.fromList [1, 2, 3] -- ベクター(可変長配列)
V.fromList [1, 2, 3] :: Num a => V.Vector a
λ> V.snoc (V.fromList [1, 2, 3]) 4 -- 末尾に要素追加(snoc)
[1,2,3,4]
it :: Num a => V.Vector a
71
{- Haskell -}
λ> import qualified Data.Set as S
λ> :t S.fromList [1, 2, 3] -- セット
S.fromList [1, 2, 3] :: (Ord a, Num a) => S.Set a
λ> S.insert 4 $ S.fromList [1, 2, 3]
fromList [1,2,3,4]
it :: (Ord a, Num a) => S.Set a
λ> import qualified Data.Map as M
λ> :t M.fromList [("a", 1), ("b", 2), ("c", 3)] -- マップ
M.fromList [("a", 1), ("b", 2), ("c", 3)]
:: Num a => M.Map String a
λ> M.insert "d" 4 $ M.fromList [("a", 1), ("b", 2), ("c", 3)]
fromList [("a",1),("b",2),("c",3),("d",4)]
it :: Num a => M.Map String a
72
{- Haskell -}
λ> [x * y | x <- [1..2], y <- [1..9]] -- リスト内包表記
[1,2,3,4,5,6,7,8,9,2,4,6,8,10,12,14,16,18]
it :: (Enum a, Num a) => [a]
λ> :{
λ| do -- do記法(a.k.a. モナド内包表記)
λ| x <- [1..2]
λ| y <- [1..9]
λ| return $ x * y
λ| :}
[1,2,3,4,5,6,7,8,9,2,4,6,8,10,12,14,16,18]
it :: (Num b, Enum b) => [b]
λ> :{
λ| do -- Maybe, Eitherなど任意のモナドで利用できる
λ| x <- Just 2
λ| y <- Just 3
λ| return $ x * y
λ| :}
Just 6
it :: Num b => Maybe b
73
/* Scala */
scala> List(1, 2, 3) // (連結)リスト
val res0: List[Int] = List(1, 2, 3)
scala> 0 :: List(1, 2, 3) // 先頭に要素追加(cons)
val res1: List[Int] = List(0, 1, 2, 3)
scala> Vector(1, 2, 3) // ベクター(可変長配列)
val res2: Vector[Int] = Vector(1, 2, 3)
scala> Vector(1, 2, 3) :+ 4 // 末尾に要素追加
val res3: Vector[Int] = Vector(1, 2, 3, 4)
74
/* Scala */
scala> Set(1, 2, 3) // セット
val res4: Set[Int] = Set(1, 2, 3)
scala> Set(1, 2, 3) + 4 // 要素追加
val res5: Set[Int] = Set(1, 2, 3, 4)
scala> Map("a" -> 1, "b" -> 2, "c" -> 3) // マップ
val res6: Map[String, Int] = Map(a -> 1, b -> 2, c -> 3)
scala> Map("a" -> 1, "b" -> 2, "c" -> 3) + ("d" -> 4)
val res7: Map[String, Int] = Map(a -> 1, b -> 2, c -> 3, d ->
4)
75
/* Scala */
scala> for // for式(a.k.a. for内包表記)
| x <- 1 to 2
| y <- 1 to 9
| yield x * y
val res8: IndexedSeq[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9,
2, 4, 6, 8, 10, 12, 14, 16, 18)
scala> for // Option, EitherなどflatMap/mapを備えた型で利用できる
| x <- Some(2)
| y <- Some(3)
| yield x * y
val res9: Option[Int] = Some(6)
76
;;; Clojure
user=> (type '(1 2 3)) ; (連結)リスト
clojure.lang.PersistentList
user=> (conj '(1 2 3) 0) ; 先頭に要素追加
(0 1 2 3)
user=> (cons 0 '(1 2 3)) ; シーケンスとして要素追加
(0 1 2 3)
user=> (type [1 2 3]) ; ベクター(可変長配列)
clojure.lang.PersistentVector
user=> (conj [1 2 3] 4) ; 末尾に要素追加
[1 2 3 4]
user=> (cons 0 [1 2 3]) ; シーケンスとして要素追加
(0 1 2 3)
77
;;; Clojure
user=> (type #{1 2 3}) ; セット
clojure.lang.PersistentHashSet
user=> (conj #{1 2 3} 4) ; 要素追加
#{1 4 3 2}
user=> (cons 0 #{1 2 3}) ; シーケンスとして要素追加
(0 1 3 2)
user=> (type {:a 1 :b 2 :c 3}) ; マップ
clojure.lang.PersistentArrayMap
user=> (assoc {:a 1 :b 2 :c 3} :d 4) ; エントリー追加
{:a 1, :b 2, :c 3, :d 4}
user=> (cons [:z 0] {:a 1 :b 2 :c 3}) ; シーケンスとして要素追加
([:z 0] [:a 1] [:b 2] [:c 3])
78
;;; Clojure
user=> (for [x (range 1 (inc 2)) ; forマクロ
y (range 1 (inc 9))] ; (a.k.a. シーケンス内包表記)
(* x y))
(1 2 3 4 5 6 7 8 9 2 4 6 8 10 12 14 16 18)
;; 様々なデータがシーケンス(論理的なリスト)として扱える(seqable)
user=> (take 2 "abc") ; 文字列
(a b) ; 文字シーケンス
user=> (take 2 (java.util.List/of 1 2 3)) ; Javaのリスト
(1 2) ; Clojureのシーケンス
user=> (take 2 {:a 1 :b 2 :c 3}) ; マップ
([:a 1] [:b 2]) ; エントリー(ベクター)シーケンス
user=> (require '[clojure.java.io :as io])
nil
user=> (with-open [r (io/reader "fibonacci.clj")] ; ファイル
(->> r line-seq (take 3) doall))
("(ns fibonacci)" "" "(defn fib [n]") ; テキスト行シーケンス
79
## Elixir
iex(1)> i [1, 2, 3] # (連結)リスト
Term
[1, 2, 3]
Data type
List
...
iex(2)> [0 | [1, 2, 3]] # 先頭に要素追加(cons)
[0, 1, 2, 3]
iex(3)> i [a: 1, b: 2, c: 3] # キーワードリスト
Term
[a: 1, b: 2, c: 3]
Data type
List
...
iex(4)> [{:z, 0} | [a: 1, b: 2, c: 3]] # 先頭に要素追加
[z: 0, a: 1, b: 2, c: 3]
80
## Elixir
iex(5)> i MapSet.new([1, 2, 3]) # セット
Term
MapSet.new([1, 2, 3])
Data type
MapSet
...
iex(6)> MapSet.put(MapSet.new([1, 2, 3]), 4)
MapSet.new([1, 2, 3, 4])
iex(7)> i %{a: 1, b: 2, c: 3} # マップ
Term
%{c: 3, b: 2, a: 1}
Data type
Map
...
iex(8)> Map.put(%{a: 1, b: 2, c: 3}, :d, 4)
%{c: 3, b: 2, a: 1, d: 4}
81
## Elixir
iex(9)> for x <- 1..2, # Enumerableに対する内包表記
...(9)> y <- 1..9 do
...(9)> x * y
...(9)> end
[1, 2, 3, 4, 5, 6, 7, 8, 9, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# EnumerableであればEnumモジュールの関数で扱える
iex(10)> Enum.take([a: 1, b: 2, c: 3], 2)
[a: 1, b: 2]
iex(11)> Enum.take(MapSet.new([1, 2, 3]), 2)
[1, 2]
iex(12)> Enum.take(%{a: 1, b: 2, c: 3}, 2)
[c: 3, b: 2]
iex(13)> Enum.take([1, 2, 3], 2)
[1, 2]
82
データ型の定義と値の構築・分解/分岐
{- Haskell -}
λ> :{
λ| data Tree a -- 代数的データ型の定義
λ| = Leaf !a
λ| | Branch { left :: !(Tree a)
λ| , right :: !(Tree a)
λ| }
λ| deriving (Show, Eq)
λ| :}
type Tree :: * -> *
data Tree a = ...
left :: Tree a -> Tree a
right :: Tree a -> Tree a
83
{- Haskell -}
λ> t = Branch (Branch (Leaf 1) (Branch (Leaf 2) (Leaf 3)))
(Leaf 4)
t :: Num a => Tree a
[Prelude]
λ> left t
Branch {left = Leaf 1, right = Branch {left = Leaf 2, right =
Leaf 3}}
it :: Num a => Tree a
λ> right t
Leaf 4
it :: Num a => Tree a
λ> :{
λ| size :: Tree a -> Int
λ| size (Leaf _) = 1 -- 関数の引数でのパターンマッチ
λ| size (Branch l r) = 1 + size l + size r
λ| :}
size :: Tree a -> Int
λ> size t
7
it :: Int
84
/* Scala */
scala> enum Tree[+A]: // 代数的データ型の定義
| case Leaf(value: A)
| case Branch(left: Tree[A], right: Tree[A])
|
// defined class Tree
scala> import Tree.*
scala> val t: Branch[Int] = Branch(Branch(Leaf(1), Branch(
Leaf(2), Leaf(3))), Leaf(4))
val t: Tree.Branch[Int] = Branch(Branch(Leaf(1),Branch(
Leaf(2),Leaf(3))),Leaf(4))
scala> t.left
val res0: Tree[Int] = Branch(Leaf(1),Branch(Leaf(2),Leaf(3)))
scala> t.right
val res1: Tree[Int] = Leaf(4)
85
/* Scala */
scala> extension [A](tree: Tree[A]) // 拡張メソッドの定義
| def size: Int = tree match
| case Leaf(_) => 1
| case Branch(l, r) => 1 + l.size + r.size
|
def size[A](tree: Tree[A]): Int
scala> t.size
val res2: Int = 7
86
;;; Clojure
user=> (defrecord Leaf [value]) ; レコードの定義
user.Leaf
user=> (defrecord Branch [left right])
user.Branch
user=> (def t
(->Branch (->Branch (->Leaf 1)
(->Branch (->Leaf 2)
(->Leaf 3)))
(->Leaf 4)))
#'user/t
user=> (:left t)
#user.Branch{:left #user.Leaf{:value 1}, :right #user.Branch{
:left #user.Leaf{:value 2}, :right #user.Leaf{:value 3}}}
user=> (:right t)
#user.Leaf{:value 4}
87
;;; Clojure
user=> (defmulti size class) ; マルチメソッドの定義
#'user/size
user=> (defmethod size Leaf [_] 1)
#object[clojure.lang.MultiFn 0x6fc3e1a4 "clojure.lang.MultiFn
@6fc3e1a4"] ; ↓ 関数の引数での分配束縛
user=> (defmethod size Branch [{:keys [left right]}]
(+ 1 (size left) (size right)))
#object[clojure.lang.MultiFn 0x6fc3e1a4 "clojure.lang.MultiFn
@6fc3e1a4"]
user=> (size t)
7
88
## Elixir
iex(1)> defmodule Leaf do # 構造体の定義
...(1)> defstruct [:value]
...(1)> end
{:module, Leaf, ...}
iex(2)> defmodule Branch do
...(2)> defstruct [:left, :right]
...(2)> end
{:module, Branch, ...}
iex(3)> t = %Branch{left: %Branch{left: %Leaf{value: 1},
right: %Branch{left: %Leaf{value: 2}, right: %Leaf{value:
3}}}, right: %Leaf{value: 4}}
%Branch{...}
iex(4)> t.left
%Branch{left: %Leaf{value: 1}, right: %Branch{left: %Leaf{
value: 2}, right: %Leaf{value: 3}}}
iex(5)> t.right
%Leaf{value: 4}
89
## Elixir
iex(6)> defmodule Tree do
...(6)> def size(%Leaf{}), do: 1 # 関数の引数でのパターンマッチ
...(6)> def size(%Branch{left: left, right: right}) do
...(6)> 1 + size(left) + size(right)
...(6)> end
...(6)> end
{:module, Tree, ...}
iex(7)> Tree.size(t)
7
90
3-4. 🍷評価
91
パターン
(patterns)
再帰
(recursion)
遅延評価
(lazy evaluation)
メモ化
(memoization)
並⾏プログラミング
(concurrent programming)
アクターモデル
(actor model)
STM
(software transactional
memory)
CSP
(communicating sequential
processes)
継続
(continuation)
⾼階関数
(higher-order function) 関数合成
(function composition)
カリー化
(currying)
部分適⽤
(partial application)
評価
(evaluation)
制御
(control)
関数
(function)
パイプ演算⼦
(pipe operator)
メソッドチェーン
(method chaining)
代数的データ型
(algebraic data type; ADT)
パターンマッチ
(pattern matching)
クロージャー/関数閉包
(closure)
オブジェクト
(object)
抽象データ型
(abstract data type)
データ
(data)
直和型
(sum type)
直積型
(product type)
カプセル化
(encapsulation)
ポリモーフィズム/多相
(polymorphism)
サブタイプ多相
(subtype polymorphism)
パラメータ多相
(parametric polymorphism)
アドホック多相
(ad hoc polymorphism)
型クラス
(type class)
マルチメソッド
(multimethod)
プロトコル
(protocol)
変性
(variance)
継承
(inheritance)
分配束縛
(destructuring)
純粋関数型データ構造
(purely functional
data structure)
永続データ構造
(persistent data structure)
オーバーロード/多重定義
(overloading)
pipes & filters
goroutines & channels
データ指向プログラミング
(data-oriented programming)
ファンクター
(functor)
モナド
(monad)
リスト
(list)
遅延リスト/ストリーム
(lazy list/stream)
ベクター
(vector)
全域関数
(total function)
部分関数
(partial function)
オブジェクト指向プログラミング
(object-oriented
programming)
アプリカティブ
(applicative)
トレイト
(trait)
必要呼び
(call by need)
ジェネリクス
(generics)
末尾再帰
(tail recursion)
再帰型
(recursive type)
GoFデザインパターン
(GoF Design Patterns)
92
遅延評価
(lazy evaluation)
メモ化
(memoization)
評価
(evaluation)
必要呼び
(call by need)
93
純粋関数、不変データを前提とすると、評価の順序
やタイミングの自由度が高まる
必要になるまで評価を先送りしたり(遅延評価)、評
価済みの値を再利用したり(メモ化)
→目的に合わせて評価を制御できると旨い😋
94
評価の制御と遅延コレクション
{- Haskell: 言語のデフォルトの評価戦略は遅延(非正格)評価 -}
-- 関数の定義: call by need
λ> f x y = if x > 0 then x else y
f :: (Ord a, Num a) => a -> a -> a
-- 引数yにアクセスされなければエラーが生じない
λ> f 1 (2 `div` 0)
1
it :: Integral a => a
λ> f 0 (2 `div` 0)
*** Exception: divide by zero
-- bang patternを利用した関数の定義: call by value
λ> g !x !y = if x > 0 then x else y
g :: (Ord a, Num a) => a -> a -> a
-- 引数x, yは直ちに評価される
λ> g 1 (2 `div` 0)
*** Exception: divide by zero
95
{- Haskell -}
-- 無限に続く整数のリストから先頭3要素を取り出す
λ> take 3 [0..]
[0,1,2]
it :: (Num a, Enum a) => [a]
-- 3番目の要素にアクセスしなければエラーが生じない
λ> [1, 2, 3 `div` 0] !! 2
*** Exception: divide by zero
λ> take 2 [1, 2, 3 `div` 0]
[1,2]
96
{- Haskell -}
-- データ型の定義
λ> data Pair = Pair { l :: Int, r :: Int }
...
-- r にアクセスしなければエラーが生じない
λ> l $ Pair 1 (2 `div` 0)
1
it :: Int
λ> r $ Pair 1 (2 `div` 0)
*** Exception: divide by zero
-- 値コンストラクタに正格性フラグを利用したデータ型の定義
λ> data Pair' = Pair' { l :: !Int, r :: !Int }
...
-- r にアクセスしなくてもエラーが生じる
λ> l $ Pair' 1 (2 `div` 0)
*** Exception: divide by zero
97
/* Scala: 言語の評価戦略は積極(正格)評価 */
// 関数の定義: call by value
scala> def f(x: Int, y: Int): Int =
| if (x > 0) x else y
|
def f(x: Int, y: Int): Int
// 引数x, yは直ちに評価される
scala> f(1, 2 / 0)
java.lang.ArithmeticException: / by zero
// 名前渡しパラメータを利用した関数の定義: call by name
scala> def g(x: => Int, y: => Int): Int =
| if (x > 0) x else y // この場合、xは最大2回評価される
| // (回避するには評価結果をローカル束縛(メモ化)して再利用)
def g(x: => Int, y: => Int): Int
// 引数yにアクセスされなければエラーが生じない
scala> g(1, 2 / 0)
val res1: Int = 1
scala> g(0, 2 / 0)
java.lang.ArithmeticException: / by zero
98
/* Scala */
// 無限に続く整数の遅延リストから先頭3要素を取り出す
scala> LazyList.from(0).take(3).toList
val res3: List[Int] = List(0, 1, 2)
// 3番目の要素にアクセスしなければエラーが生じない
scala> (1 #:: 2 #:: (3 / 0) #:: LazyList.empty)(2)
java.lang.ArithmeticException: / by zero
scala> (1 #:: 2 #:: (3 / 0) #:: LazyList.empty).take(2)
.toList
val res5: List[Int] = List(1, 2)
99
;;; Clojure: 言語の評価戦略は積極(正格)評価
;; 関数の定義: call by value
user=> (defn f [x y]
(if (pos? x) x y))
#'user/f
;; 引数x, yは直ちに評価される
user=> (f 1 (/ 2 0))
Execution error (ArithmeticException) at user/eval373 ...
Divide by zero
;; マクロの定義: call by name
user=> (defmacro g [x y]
`(if (pos? ~x) ~x ~y)) ; この場合、xは最大2回評価される
#'user/g ; (回避するには評価結果をローカル束縛(メモ化)して再利用)
;; 引数yにアクセスされなければエラーが生じない
user=> (g 1 (/ 2 0))
1
user=> (g 0 (/ 2 0))
Execution error (ArithmeticException) at user/eval382 ...
Divide by zero
100
;;; Clojure
;; 無限に続く整数の遅延シーケンスから先頭3要素を取り出す
user=> (take 3 (range))
(0 1 2)
;; 3番目の要素にアクセスしなければエラーが生じない
user=> (nth (lazy-seq
(cons 1 (lazy-seq
(cons 2 (lazy-seq
(cons (/ 3 0) nil))))))
2)
Execution error (ArithmeticException) at user/eval428$fn$f...
Divide by zero
user=> (take 2 (lazy-seq
(cons 1 (lazy-seq
(cons 2 (lazy-seq
(cons (/ 3 0) nil)))))))
(1 2)
101
;;; Clojure
;; 最も基本的な高階関数map, filterさえ遅延シーケンスを返す
user=> (type (map inc (range 0 (inc 9))))
clojure.lang.LazySeq
user=> (type (filter odd? (range 0 (inc 9))))
clojure.lang.LazySeq
user=> (require '[clojure.java.io :as io])
nil
;; line-seqも遅延シーケンスを返すため実体化前にリソース解放するとエラー
user=> (with-open [r (io/reader "fibonacci.clj")]
(->> r line-seq (take 3)))
Error printing return value (IOException) at java.io.Buffered
Reader/ensureOpen (BufferedReader.java:123).
Stream closed
user=> (with-open [r (io/reader "fibonacci.clj")]
(->> r line-seq (take 3) doall)) ; doallで直ちに実体化
("(ns fibonacci)" "" "(defn fib [n]")
102
## Elixir: 言語の評価戦略は積極(正格)評価
# 関数の定義: call by value
iex(1)> defmodule Some do
...(1)> def f(x, y) do
...(1)> if x > 0, do: x, else: y
...(1)> end
...(1)> end
{:module, Some, ...}
# 引数x, yは直ちに評価される
iex(2)> Some.f(1, 2 / 0)
** (ArithmeticError) bad argument in arithmetic expression...
103
## Elixir
# マクロの定義: call by name
iex(3)> defmodule Other do
...(3)> defmacro g(x, y) do
...(3)> quote do
...(3)> if unquote(x) > 0, # この場合、xは最大2回評価される
...(3)> do: unquote(x), else: unquote(y)
...(3)> end
...(3)> end # (回避するには評価結果をローカル束縛(メモ化)して再利用)
...(3)> end
{:module, Other, ...}
iex(4)> require Other
Other
# 引数yにアクセスされなければエラーが生じない
iex(5)> Other.g(1, 2 / 0)
1
iex(6)> Other.g(0, 2 / 0)
** (ArithmeticError) bad argument in arithmetic expression...
104
## Elixir
# 無限に続く整数のストリームから先頭3要素を取り出す
iex(7)> Enum.take(Stream.from_index, 3)
[0, 1, 2]
# 3番目の要素にアクセスしなければエラーが生じない
iex(8)> 1..3 |> Stream.map(fn n -> if n < 3, do: n, else:
n / 0 end) |> Enum.at(2)
** (ArithmeticError) bad argument in arithmetic expression...
iex(9)> 1..3 |> Stream.map(fn n -> if n < 3, do: n, else:
n / 0 end) |> Enum.take(2)
[1, 2]
105
BONUS: (評価の制御といえば)マクロ
i.e. ASTレベルでのコンパイル時メタプログラミング
式(op arg0 arg1 ...) の個々の引数arg0,
arg1, ...がオペレータop に適用される前に関数
action-fn の呼び出しを差し込めるマクロ
;;; Clojure
(defmacro apply-after [action-fn op & args] ; マクロの定義
(let [args (->> args
(map (juxt identity pr-str))
(map-indexed (fn [i [expr s]]
`(doto ~expr
(~action-fn ~i ~s)))))]
`(~op ~@args)))
106
;;; Clojure
user=> (defn inspect [v i s]
(println (str "arg" i ":") s "=>" v))
#'user/inspect
user=> (apply-after inspect + (- 1 2 3) (* 4 5) (/ 6 7))
arg0: (- 1 2 3) => -4
arg1: (* 4 5) => 20
arg2: (/ 6 7) => 6/7
118/7 ; (+ (- 1 2 3) (* 4 5) (/ 6 7)) と等価
user=> (apply-after inspect if (odd? 2)
(println :odd)
(println :even))
arg0: (odd? 2) => false
:even ; (println :even) による標準出力
arg2: (println :even) => nil
nil ; (if (odd? 2) (println :odd) (println :even)) と等価
107
;;; Clojure
user=> (macroexpand-1 ; マクロ展開(1段階)
'(apply-after inspect if (odd? 2)
(println :odd)
(println :even)))
(if (clojure.core/doto (odd? 2)
(inspect 0 "(odd? 2)"))
(clojure.core/doto (println :odd)
(inspect 1 "(println :odd)"))
(clojure.core/doto (println :even)
(inspect 2 "(println :even)")))
nil
108
3-5. 🍷ポリモーフィズム
109
パターン
(patterns)
再帰
(recursion)
遅延評価
(lazy evaluation)
メモ化
(memoization)
並⾏プログラミング
(concurrent programming)
アクターモデル
(actor model)
STM
(software transactional
memory)
CSP
(communicating sequential
processes)
継続
(continuation)
⾼階関数
(higher-order function) 関数合成
(function composition)
カリー化
(currying)
部分適⽤
(partial application)
評価
(evaluation)
制御
(control)
関数
(function)
パイプ演算⼦
(pipe operator)
メソッドチェーン
(method chaining)
代数的データ型
(algebraic data type; ADT)
パターンマッチ
(pattern matching)
クロージャー/関数閉包
(closure)
オブジェクト
(object)
抽象データ型
(abstract data type)
データ
(data)
直和型
(sum type)
直積型
(product type)
カプセル化
(encapsulation)
ポリモーフィズム/多相
(polymorphism)
サブタイプ多相
(subtype polymorphism)
パラメータ多相
(parametric polymorphism)
アドホック多相
(ad hoc polymorphism)
型クラス
(type class)
マルチメソッド
(multimethod)
プロトコル
(protocol)
変性
(variance)
継承
(inheritance)
分配束縛
(destructuring)
純粋関数型データ構造
(purely functional
data structure)
永続データ構造
(persistent data structure)
オーバーロード/多重定義
(overloading)
pipes & filters
goroutines & channels
データ指向プログラミング
(data-oriented programming)
ファンクター
(functor)
モナド
(monad)
リスト
(list)
遅延リスト/ストリーム
(lazy list/stream)
ベクター
(vector)
全域関数
(total function)
部分関数
(partial function)
オブジェクト指向プログラミング
(object-oriented
programming)
アプリカティブ
(applicative)
トレイト
(trait)
必要呼び
(call by need)
ジェネリクス
(generics)
末尾再帰
(tail recursion)
再帰型
(recursive type)
GoFデザインパターン
(GoF Design Patterns)
110
ポリモーフィズム/多相
(polymorphism)
サブタイプ多相
(subtype polymorphism)
パラメータ多相
(parametric polymorphism)
アドホック多相
(ad hoc polymorphism)
型クラス
(type class)
マルチメソッド
(multimethod)
プロトコル
(protocol)
変性
(variance)
継承
(inheritance)
オーバーロード/多重定義
(overloading)
オブジェクト指向プログラミング
(object-oriented
programming)
トレイト
(trait)
ジェネリクス
(generics)
111
ポリモーフィズム(多相/多態性)というと、オブジェ
クト指向言語での継承による仕組み(= サブタイプ
多相の一例)が想起されやすいかもしれない
関数型言語では異なる機構が提供されていることも
多い
→簡潔に柔軟に関数の振る舞いをポリモーフィック
にできると旨い😋
112
サブタイプ多相に関する仕組み
/* Scala */
scala> trait Solid: // トレイトの定義
| def surfaceArea: Double
| def volume: Double
|
// defined trait Solid
scala> case class Cuboid( // トレイトSolidを実装したクラスの定義
| a: Double,
| b: Double,
| c: Double,
| ) extends Solid:
| def surfaceArea: Double =
| 2.0 * (a * b + b * c + c * a)
| def volume: Double =
| a * b * c
113
/* Scala */
scala> case class Sphere(r: Double) extends Solid:
| def surfaceArea: Double =
| 4.0 * Math.PI * Math.pow(r, 2)
| def volume: Double =
| 4.0 / 3.0 * Math.PI * Math.pow(r, 3)
scala> def solidProps(x: Solid): Map[String, Double] =
| Map(
| "surface area" -> x.surfaceArea,
| "volume" -> x.volume,
| )
|
def solidProps(x: Solid): Map[String, Double]
scala> Seq(Cuboid(2, 3, 4), Sphere(2)).map(solidProps)
val res0: Seq[Map[String, Double]] = List(Map(surface area ->
52.0, volume -> 24.0), Map(surface area ->
50.26548245743669, volume -> 33.510321638291124))
114
アドホック多相に関する仕組み
{- Haskell -}
λ> :{
λ| class Solid a where -- 型クラスの定義
λ| surfaceArea :: a -> Double
λ| volume :: a -> Double
λ| :}
type Solid :: Constraint
λ> :{
λ| data Cuboid = Cuboid
λ| { a :: !Double
λ| , b :: !Double
λ| , c :: !Double }
λ|
λ| data Sphere = Sphere { r :: !Double }
λ| :}
type Cuboid :: *
...
115
{- Haskell -}
λ> :{
λ| instance Solid Cuboid where -- 型クラスのインスタンスの定義
λ| surfaceArea (Cuboid a b c) =
λ| 2 * (a * b + b * c + c * a)
λ| volume (Cuboid a b c) =
λ| a * b * c
λ|
λ| instance Solid Sphere where
λ| surfaceArea (Sphere r) =
λ| 4 * pi * r ^ 2
λ| volume (Sphere r) =
λ| 4 / 3 * pi * r ^ 3
λ| :}
116
{- Haskell -}
λ> import qualified Data.Map as M
λ> :{
λ| solidProps :: Solid a => a -> M.Map String Double
λ| solidProps x = M.fromList
λ| [("surface area", surfaceArea x), ("volume", volume x)]
λ| :}
solidProps :: Solid a => a -> M.Map String Double
λ> solidProps $ Cuboid 2 3 4
fromList [("surface area",52.0),("volume",24.0)]
it :: M.Map String Double
λ> solidProps $ Sphere 2
fromList [("surface area",50.26548245743669),("volume",
33.510321638291124)]
it :: M.Map String Double
117
/* Scala */
scala> trait Solid2[A]: // 型クラスの定義
| extension (a: A)
| def surfaceArea: Double
| def volume: Double
|
// defined trait Solid2
scala> case class Cuboid2(
| a: Double,
| b: Double,
| c: Double,
| )
// defined case class Cuboid2
scala> case class Sphere2(r: Double)
// defined case class Sphere2
118
/* Scala */
scala> given Solid2[Cuboid2] with // 型クラスのインスタンスの定義
| extension (x: Cuboid2)
| def surfaceArea: Double =
| 2.0 * (x.a * x.b + x.b * x.c + x.c * x.a)
| def volume: Double =
| x.a * x.b * x.c
| given Solid2[Sphere2] with
| extension (x: Sphere2)
| def surfaceArea: Double =
| 4.0 * Math.PI * Math.pow(x.r, 2)
| def volume: Double =
| 4.0 / 3.0 * Math.PI * Math.pow(x.r, 3)
// defined object given_Solid2_Cuboid2
// defined object given_Solid2_Sphere2
119
/* Scala */
scala> def solidProps[A: Solid2](x: A): Map[String, Double] =
| Map(
| "surface area" -> x.surfaceArea,
| "volume" -> x.volume,
| )
|
def solidProps[A](x: A)(using evidence$1: Solid2[A]):
Map[String, Double]
scala> solidProps(Cuboid2(2, 3, 4))
val res0: Map[String, Double] = Map(surface area -> 52.0,
volume -> 24.0)
scala> solidProps(Sphere2(2))
val res1: Map[String, Double] = Map(surface area ->
50.26548245743669, volume -> 33.510321638291124)
120
;;; Clojure
user=> (defprotocol Solid ; プロトコルの定義
(surface-area [this])
(volume [this]))
Solid
user=> (defrecord Cuboid [a b c])
user.Cuboid
user=> (defrecord Sphere [r])
user.Sphere
121
;;; Clojure
user=> (extend-protocol Solid ; プロトコルの実装
Cuboid
(surface-area [{:keys [a b c]}]
(* 2 (+ (* a b)
(* b c)
(* c a))))
(volume [{:keys [a b c]}]
(* a b c))
Sphere
(surface-area [{:keys [r]}]
(* 4 Math/PI (Math/pow r 2)))
(volume [{:keys [r]}]
(* 4/3 Math/PI (Math/pow r 3))))
nil
122
;;; Clojure
user=> (defn solid-props [x]
{:surface-area (surface-area x)
:volume (volume x)})
#'user/solid-props
user=> (solid-props (->Cuboid 2 3 4))
{:surface-area 52, :volume 24}
user=> (solid-props (->Sphere 2))
{:surface-area 50.26548245743669, :volume 33.51032163829112}
123
## Elixir
iex(1)> defprotocol Solid do # プロトコルの定義
...(1)> def surface_area(x)
...(1)> def volume(x)
...(1)> end
{:module, Solid, ...}
iex(2)> defmodule Cuboid do
...(2)> defstruct [:a, :b, :c]
...(2)> end
{:module, Cuboid, ...}
iex(3)> defmodule Sphere do
...(3)> defstruct [:r]
...(3)> end
{:module, Sphere, ...}
124
## Elixir
iex(4)> defimpl Solid, for: Cuboid do # プロトコルの実装
...(4)> def surface_area(%Cuboid{a: a, b: b, c: c}) do
...(4)> 2 * (a * b + b * c + c * a)
...(4)> end
...(4)> def volume(%Cuboid{a: a, b: b, c: c}) do
...(4)> a * b * c
...(4)> end
...(4)> end
{:module, Solid.Cuboid, ...}
iex(5)> defimpl Solid, for: Sphere do
...(5)> def surface_area(%Sphere{r: r}) do
...(5)> 4 * :math.pi() * r ** 2
...(5)> end
...(5)> def volume(%Sphere{r: r}) do
...(5)> 4 / 3 * :math.pi() * r ** 3
...(5)> end
...(5)> end
{:module, Solid.Sphere, ...}
125
## Elixir
iex(6)> solid_props = fn x ->
...(6)> %{
...(6)> surface_area: Solid.surface_area(x),
...(6)> volume: Solid.volume(x)
...(6)> }
...(6)> end
#Function<42.81571850/1 in :erl_eval.expr/6>
iex(7)> solid_props.(%Cuboid{a: 2, b: 3, c: 4})
%{surface_area: 52, volume: 24}
iex(8)> solid_props.(%Sphere{r: 2})
%{
surface_area: 50.26548245743669,
volume: 33.510321638291124
}
126
パラメータ多相に関する仕組み
{- Haskell -}
λ> :t id -- 関数idの型(type)は?
id :: a -> a -- 型a
λ> id 42 -- idを数値に適用したとき
42
it :: Num a => a -- 型クラスNumの制約付きの型a
λ> :t Just -- 1引数の値コンストラクタJust
Just :: a -> Maybe a
λ> :t Just 42 -- Justを数値に適用したとき
Just 42 :: Num a => Maybe a
λ> :t Nothing -- 0引数の値コンストラクタNothing
Nothing :: Maybe a
-- cf. 型Maybe aの定義(抜粋)
data Maybe a = Nothing | Just a
127
{- Haskell -}
λ> :k Maybe -- 型コンストラクタMaybeのカインド(kind)は?
Maybe :: * -> * -- 型レベルでの関数に相当
λ> :k Num a => Maybe a -- Maybeを型aに適用したとき
Num a => Maybe a :: * -- 型レベルでの値に相当
λ> :k Functor -- 型クラスFunctor
Functor :: (* -> *) -> Constraint -- 型レベルでの高階関数に相当
λ> :k Functor Maybe -- FunctorをMaybeに適用したとき
Functor Maybe :: Constraint
-- cf. 型クラスFunctorの定義(抜粋)
class Functor f where
fmap :: (a -> b) -> f a -> f b
128
型(type) カインド(kind)
値(value):
a
型(type):
*
関数(function)/値コンスト
ラクタ(value constructor):
a -> b
型コンストラクタ
(type constructor):
* -> *
高階関数(higher-order
function):
(a -> b) -> f a -> f
b
高カインド型(higher-
kinded type):
(* -> *) -> *
129
/* Scala */
scala> [A] => (x: A) => identity(x)
val res0: [A] => (x: A) => A = Lambda/0x00002000015018d8@3...
scala> identity(42)
val res1: Int = 42
scala> [A] => (x: A) => Some(x)
val res2: [A] => (x: A) => Some[A] = Lambda/0x000020000150...
scala> val x: Option[Int] = Some(42) // SomeはOptionの派生型
val x: Option[Int] = Some(42)
scala> val y: Option[Int] = None // NoneはOptionの派生型
val y: Option[Int] = None
scala> val z: Option[Any] = x // 型パラメータAは共変(covariant)
val z: Option[Any] = Some(42)
// cf. 関数identityの定義(抜粋)
def identity[A](x: A): A = x
// cf. 型Option[+A]の定義(抜粋)
sealed abstract class Option[+A] extends IterableOnce[A]
with Product with Serializable
final case class Some[+A](value: A) extends Option[A]
case object None extends Option[Nothing]
130
/* Scala */
// 型クラスFunctorの定義
scala> trait Functor[F[_]]:
| extension [A](x: F[A])
| def fmap[B](f: A => B): F[B]
|
// defined trait Functor
// Optionに対するインスタンスの定義
scala> given Functor[Option] with
| extension [A](x: Option[A])
| def fmap[B](f: A => B): Option[B] =
| x.map(f)
|
// defined object given_Functor_Option
scala> (x, y)
val res3: (Option[Int], Option[Int]) = (Some(42),None)
scala> x.fmap(_ + 1)
val res4: Option[Int] = Some(43)
scala> y.fmap(_ + 1)
val res5: Option[Int] = None
131
4. 関数型プログラマの
メンタルモデル
132
重視する
もの
(values)
式指向
(expression-oriented)
不変性
(immutability)
宣⾔型プログラミング
(declarative programming)
(型)安全性
((type) safety)
合成可能性
(composability)
永続性
(persistence)
純粋性
(purity)
参照透過性
(referential transparency)
命令型プログラミング
(imperative programming)
⽂指向
(statement-oriented)
副作⽤
(side effect)
破壊的更新
(mutation)
可変性
(mutability)
⼊出⼒
(I/O)
133
関数型言語使い/関数型プログラミング実践者の発想
⛓️適切な制約が解放をもたらす
引数に対して戻り値を返す以外のことをする関数
は信頼できない😱→ 純粋関数を基本に
いつでもどこでも破壊的に更新できるデータ構造
は怖い😱→ 不変/永続データを基本に
とりうる値が分からないのは不安😱→不正値を
表現不能に、より(型)安全に
🧱単純で安定したブロックを基礎に全体を構成し
たい
→式指向に、宣言的に、合成可能に
134
おわりに
Haskell, Scala, Clojure, Elixirを例に、関数型言語で
楽しめる基本的な旨みについて探ってみた
関数型プログラミングの本質、そこから導かれる不
可欠な要素について考えるきっかけになれば幸い
関数型プログラミング(言語)と を
引き続きぜひご賞味ください🍷
「関数型まつり」
135
Further Reading
言語横断
: Scala, Erlang, Clojure,
Haskell
続編 : Elm,
Elixir, Idris
(通称:
SICP, 魔術師本): サンプルコードはScheme
(通称: TAPL): サンプルコードはOCaml
(通称: PFDS): サンプルコ
ードはStandard ML, Haskell
『7つの言語7つの世界』
Seven More Languages in Seven Weeks
『計算機プログラムの構造と解釈第2版』
『型システム入門プログラミング言語と型の理論』
『純粋関数型データ構造』
136
Haskell
(通称: すごいH
本)
『プログラミングHaskell 第2版』
『すごいHaskellたのしく学ぼう!』
『
[増補改訂]関数プログラミング実践入門』
『Haskell入門関数型プログラミング言語の基礎と
実践』
137
Scala
(通称:
FP in Scala)
(通称:
コップ本)
最新版
『なっとく!関数型プログラミング』
『実践Scala入門』
『Scala関数型デザイン&プログラミング』
『Scalaスケーラブルプログラミング第4版』
Programming in Scala, Fifth Edition
Functional Programming Patterns in Scala and
Clojure
138
Clojure
(通称: 孔雀本)
最新版
『プログラミングClojure 第2版』
Programming Clojure, Third Edition
Getting Clojure
Clojure Applied
『関数型デザイン原則、パターン、実践』
139
Elixir & Erlang
(通称: 飛行機本)
(通称: すごいE
本)
『プログラミングElixir(第2版)
』
『Elixir実践入門』
『プログラミングErlang』
『すごいErlangゆかいに学ぼう!』
140

関数型言語テイスティング: Haskell, Scala, Clojure, Elixirを比べて味わう関数型プログラミングの旨さ

  • 1.
    関数型言語テイスティング Haskell, Scala, Clojure,Elixirを 比べて味わう関数型プログラミングの旨さ #fp_matsuri #fp_matsuri_c 1
  • 2.
    のシニアエンジニア スタートアップの起業家と投資家のための業務効 率化/連携プラットフォームを開発している 主要技術スタック: Kotlin &TypeScript の運営にも協力 関数型プログラミング(言語)とLispの熱烈な愛好者 (特に) と がエレガントで好き の運営スタッフ(座長のひとり) lagénorhynque🐬カマイルカ 株式会社スマートラウンド Server-Side Kotlin Meetup Clojure Haskell 関数型まつり2025 2
  • 3.
    [参考] 🐬の関数型言語学習/利用経験 Most UsedLanguages Clojure 42.14% Haskell 19.94% Scala 10.82% Python 5.10% TypeScript 5.03% Java 4.95% Elixir 4.10% Ruby 2.98% Common Lisp 2.56% Kotlin 2.38% GitHubでのコード量× リポジトリ数 3
  • 4.
    関数型言語経験: 約12年 cf. プログラミング経験:約13年 プログラミング学習初期から関数型言語と戯れて きた(そして非関数型言語に未だに馴染めない😇) 仕事で: Clojure, Scala 趣味で: Clojure, Haskell, Erlang/Elixir, etc. 4
  • 5.
    初期(2013年〜)の学びの主な機会 関数型プログラミング(言語)に関する技術書/記事 コミュニティイベント 勉強会: , ,, , , etc. もくもく会: , 読書会: , カンファレンス: Haskell Day, ScalaMatsuri, Erlang & Elixir Fest Tamachi.clj Shibuya.lisp 渋谷java tokyo.ex rpscala Haskellもくもく会 Laketown.clj 関数型プログラミング勉強会 (Scala) SICP読書会 5
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
    (SICP, 魔術師本) Wikipedia英語版の より Structureand Interpretation of Computer Programs Structure and Interpretation of Computer Programs 12
  • 13.
    1. きっかけ 2. 「関数型プログラミング」 3.🍷テイスティング 1. 4つの関数型言語の基本 2. 関数 3. データ 4. 評価 5. ポリモーフィズム 4. 関数型プログラマのメンタルモデル 13
  • 14.
  • 15.
  • 16.
  • 17.
    エッセンス(essence) 語源: 🇬🇧essence <essentia < esse + -ia (≒ 🇬🇧 beingness, あるということ) 語義: 「本質(的に重要なもの)」 抽出物(エキス)という派生的/比喩的な意味も 本来、単なる成分/要素のような意味ではない cf. 🇫🇷哲学の文脈で関連する表現 l'existence précède l'essence (実存は本質に先立 つ) L'essentiel est invisible pour les yeux. (🦊「大切 なものは目に見えないんだよ。 」 ) 17
  • 18.
  • 19.
  • 20.
  • 21.
    重視する もの (values) ⾔語 (languages) 処理系/実装 (implementations) パターン (patterns) Lisp 式指向 (expression-oriented) 不変性 (immutability) 宣⾔型プログラミング (declarative programming) (型)安全性 ((type) safety) 合成可能性 (composability) 永続性 (persistence) 純粋性 (purity) Clojure Erlang Elixir Haskell OCaml StandardML ML 適⽤ (apply) 評価 (eval) 抽象構⽂⽊ (abstract syntax tree; AST) マクロ (macro) 参照透過性 (referential transparency) 再帰 (recursion) 遅延評価 (lazy evaluation) メモ化 (memoization) 並⾏プログラミング (concurrent programming) アクターモデル (actor model) STM (software transactional memory) CSP (communicating sequential processes) 継続 (continuation) ⾼階関数 (higher-order function) 関数合成 (function composition) カリー化 (currying) 部分適⽤ (partial application) 関数型 プログラミング (functional programming) 理論 (theories) 数学 (mathematics) 圏論 (category theory) 型システム (type system) ラムダ計算 (lambda calculus) メタプログラミング (metaprogramming) 形式⼿法 (formal methods) 定理証明⽀援系 (theorem prover) 評価 (evaluation) 制御 (control) 関数 (function) パイプ演算⼦ (pipe operator) メソッドチェーン (method chaining) 代数的データ型 (algebraic data type; ADT) パターンマッチ (pattern matching) クロージャー/関数閉包 (closure) オブジェクト (object) Idris Elm パーサーコンビネーター (parser combinator) 離散数学 (discrete mathematics) 契約プログラミング (contract programming) 抽象データ型 (abstract data type) データ (data) 直和型 (sum type) 直積型 (product type) 篩型 (refinement type) 依存型 (dependent type) カプセル化 (encapsulation) ポリモーフィズム/多相 (polymorphism) サブタイプ多相 (subtype polymorphism) パラメータ多相 (parametric polymorphism) アドホック多相 (ad hoc polymorphism) 型クラス (type class) マルチメソッド (multimethod) プロトコル (protocol) 変性 (variance) 継承 (inheritance) カリー=ハワード同型対応 (Curry–Howard correspondence) 分配束縛 (destructuring) 純粋関数型データ構造 (purely functional data structure) 永続データ構造 (persistent data structure) Coq (Rocq) Scala 構⽂解析 (parse) オーバーロード/多重定義 (overloading) 命令型プログラミング (imperative programming) ⽂指向 (statement-oriented) 副作⽤ (side effect) 破壊的更新 (mutation) 可変性 (mutability) pipes & filters goroutines & channels プロパティベーステスト (property-based testing) ⼊出⼒ (I/O) データ指向プログラミング (data-oriented programming) ファンクター (functor) モナド (monad) リスト (list) 遅延リスト/ストリーム (lazy list/stream) 同図像性 (homoiconicity) 超循環評価器 (meta-circular evaluator) セルフホスティング (self-hosting) ベクター (vector) 全域関数 (total function) 部分関数 (partial function) オブジェクト指向プログラミング (object-oriented programming) アプリカティブ (applicative) トレイト (trait) Agda 必要呼び (call by need) F# ジェネリクス (generics) Lean Gleam 末尾再帰 (tail recursion) 意味論 (semantics) 型推論 (type inference) 再帰型 (recursive type) GoFデザインパターン (GoF Design Patterns) ※ 🐬が思い浮かぶ概念/用語を連想的に列挙したもの(網羅的でも体系的でもない) 🐬の「関数型プログラミング」コンセプトマップ 21
  • 22.
    重視する もの (values) 式指向 (expression-oriented) 不変性 (immutability) 宣⾔型プログラミング (declarative programming) (型)安全性 ((type) safety) 合成可能性 (composability) 永続性 (persistence) 純粋性 (purity) 参照透過性 (referentialtransparency) 命令型プログラミング (imperative programming) ⽂指向 (statement-oriented) 副作⽤ (side effect) 破壊的更新 (mutation) 可変性 (mutability) ⼊出⼒ (I/O) 22
  • 23.
  • 24.
    理論 (theories) 数学 (mathematics) 圏論 (category theory) 型システム (type system) ラムダ計算 (lambdacalculus) 形式⼿法 (formal methods) 定理証明⽀援系 (theorem prover) 離散数学 (discrete mathematics) 契約プログラミング (contract programming) 篩型 (refinement type) 依存型 (dependent type) カリー=ハワード同型対応 (Curry–Howard correspondence) プロパティベーステスト (property-based testing) 意味論 (semantics) 型推論 (type inference) 24
  • 25.
    処理系/実装 (implementations) 適⽤ (apply) 評価 (eval) 抽象構⽂⽊ (abstract syntax tree;AST) マクロ (macro) メタプログラミング (metaprogramming) パーサーコンビネーター (parser combinator) 構⽂解析 (parse) 同図像性 (homoiconicity) 超循環評価器 (meta-circular evaluator) セルフホスティング (self-hosting) 25
  • 26.
    パターン (patterns) 再帰 (recursion) 遅延評価 (lazy evaluation) メモ化 (memoization) 並⾏プログラミング (concurrent programming) アクターモデル (actormodel) STM (software transactional memory) CSP (communicating sequential processes) 継続 (continuation) ⾼階関数 (higher-order function) 関数合成 (function composition) カリー化 (currying) 部分適⽤ (partial application) 評価 (evaluation) 制御 (control) 関数 (function) パイプ演算⼦ (pipe operator) メソッドチェーン (method chaining) 代数的データ型 (algebraic data type; ADT) パターンマッチ (pattern matching) クロージャー/関数閉包 (closure) オブジェクト (object) 抽象データ型 (abstract data type) データ (data) 直和型 (sum type) 直積型 (product type) カプセル化 (encapsulation) ポリモーフィズム/多相 (polymorphism) サブタイプ多相 (subtype polymorphism) パラメータ多相 (parametric polymorphism) アドホック多相 (ad hoc polymorphism) 型クラス (type class) マルチメソッド (multimethod) プロトコル (protocol) 変性 (variance) 継承 (inheritance) 分配束縛 (destructuring) 純粋関数型データ構造 (purely functional data structure) 永続データ構造 (persistent data structure) オーバーロード/多重定義 (overloading) pipes & filters goroutines & channels データ指向プログラミング (data-oriented programming) ファンクター (functor) モナド (monad) リスト (list) 遅延リスト/ストリーム (lazy list/stream) ベクター (vector) 全域関数 (total function) 部分関数 (partial function) オブジェクト指向プログラミング (object-oriented programming) アプリカティブ (applicative) トレイト (trait) 必要呼び (call by need) ジェネリクス (generics) 末尾再帰 (tail recursion) 再帰型 (recursive type) GoFデザインパターン (GoF Design Patterns) 26
  • 27.
    3. 🍷テイスティング 1. 4つの関数型言語の基本 2.関数 3. データ 4. 評価 5. ポリモーフィズム 27
  • 28.
  • 29.
  • 30.
    paradigm FP OOP, FP FPFP typing static static dynamic dynamic first appeared 1990 2004 2007 2012 related ML ML Lisp Lisp 🐬's note lazy/pure FPL standard OOPL in FPL's skin modern functional Lisp Erlang + Ruby + Clojure Haskell Scala Clojure Elixir 30
  • 31.
    モジュール定義とトップレベル変数 {- Haskell -} --モジュールとそのエクスポートリスト module SomeModule(a) where -- パブリック変数 a :: Int a = 1 -- プライベート変数 b :: Int b = 2 /* Scala */ // (シングルトン)オブジェクト object SomeModule: // パブリック変数 val a: Int = 1 // プライベート変数 private val b: Int = 2 31
  • 32.
    ;;; Clojure ;; 名前空間 (nssome-module) ;; パブリック変数 (def a 1) ;; プライベート変数 (def ^:private b 2) ## Elixir # モジュール defmodule SomeModule do # パブリックな0引数関数 def a, do: 1 # プライベートな0引数関数 defp b, do: 2 end 32
  • 33.
    トップレベル関数 {- Haskell -} moduleSimpleMath(square) where -- パブリック関数 square :: Int -> Int square n = n * n -- プライベート関数 double :: Int -> Int double n = n * 2 /* Scala */ object SimpleMath: // パブリックメソッド def square(n: Int): Int = n * n // プライベートメソッド private def double(n: Int): Int = n * 2 33
  • 34.
    ;;; Clojure (ns simple-math) ;;パブリック関数 (defn square [n] (* n n)) ;; プライベート関数 (defn- double' [n] (* n 2)) ## Elixir defmodule SimpleMath do # パブリック関数 def square(n), do: n * n # プライベート関数 defp double(n), do: n * 2 end 34
  • 35.
    モジュールの利用 {- Haskell: ghciコマンドによるREPL-} λ> :l SimpleMath ... λ> import qualified SimpleMath as M -- モジュールを別名で参照 λ> M.square 3 9 it :: Int λ> map M.square [0..10] [0,1,4,9,16,25,36,49,64,81,100] it :: [Int] /* Scala: scalaコマンドによるREPL */ scala> :l SimpleMath.scala // defined object SimpleMath scala> import SimpleMath as M // オブジェクトを別名で参照 scala> M.square(3) val res0: Int = 9 scala> (0 to 10).map(M.square) val res1: IndexedSeq[Int] = Vector(0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100) 35
  • 36.
    ;;; Clojure: cljコマンドによるREPL user=>(clojure.main/load-script "simple_math.clj") #'simple-math/double' user=> (require '[simple-math :as m]) ; 名前空間を別名で参照 nil user=> (m/square 3) 9 user=> (map m/square (range 0 (inc 10))) (0 1 4 9 16 25 36 49 64 81 100) ## Elixir: iexコマンドによるREPL # `iex simple_math.ex` でファイルを読み込んで起動 iex(1)> alias SimpleMath, as: M # モジュールを別名で参照 SimpleMath iex(2)> M.square(3) 9 iex(3)> Enum.map(0..10, &M.square/1) [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100] 36
  • 37.
    ローカル束縛(変数) {- Haskell -} λ>:{ -- REPLでの複数行入力の開始 λ| -- ML系言語でお馴染み(?)のlet式 λ| let x = 5 λ| y = M.square(x) λ| in "square " ++ show x ++ " = " ++ show y λ| :} -- REPLでの複数行入力の終了 "square 5 = 25" it :: [Char] /* Scala */ scala> val x = 5 val x: Int = 5 scala> val y = M.square(x) val y: Int = 25 scala> s"square($x) = $y" val res2: String = square(5) = 25 37
  • 38.
    ;;; Clojure ;; Lisp系言語でお馴染み(?)のlet式 user=>(let [x 5 y (m/square x)] (str "(square " x ") = " y)) "(square 5) = 25" ## Elixir iex(4)> x = 5 5 iex(5)> y = M.square(x) 25 iex(6)> "square(#{x}) = #{y}" "square(5) = 25" 38
  • 39.
    無名関数(ラムダ式) {- Haskell -} λ>(n -> n * n * n) 2 8 it :: Num a => a λ> map (n -> n * n * n) [0..10] [0,1,8,27,64,125,216,343,512,729,1000] it :: (Num b, Enum b) => [b] /* Scala */ scala> ((n: Int) => n * n * n)(2) val res0: Int = 8 scala> (0 to 10).map(n => n * n * n) val res1: IndexedSeq[Int] = Vector(0, 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000) 39
  • 40.
    ;;; Clojure user=> ((fn[n] (* n n n )) 2) 8 user=> (#(* % % %) 2) ; 無名関数の略記法 8 user=> (map (fn [n] (* n n n )) (range 0 (inc 10))) (0 1 8 27 64 125 216 343 512 729 1000) user=> (map #(* % % %) (range 0 (inc 10))) (0 1 8 27 64 125 216 343 512 729 1000) ## Elixir iex(1)> (fn n -> n * n * n end).(2) 8 iex(2)> (&(&1 * &1 * &1)).(2) # 無名関数の略記法 8 iex(3)> Enum.map(0..10, fn n -> n * n * n end) [0, 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000] iex(4)> Enum.map(0..10, &(&1 * &1 * &1)) [0, 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000] 40
  • 41.
    再帰と無限リスト(1): 階乗 {- Haskell-} module Factorial where factorial :: Integral a => a -> a factorial 0 = 1 factorial n = n * factorial (n - 1) factorialSeq :: Integral a => [a] factorialSeq = scanl (*) 1 [1..] -- import Factorial (factorialSeq) λ> take 10 factorialSeq [1,1,2,6,24,120,720,5040,40320,362880] it :: Integral a => [a] λ> factorialSeq !! 100 9332621544394415268169923885626670049071596826438162146859296 3895217599993229915608941463976156518286253697920827223758251 185210916864000000000000000000000000 it :: Integral a => a 41
  • 42.
    /* Scala */ objectFactorial: def factorial(n: Int): BigInt = n match case 0 => 1 case _ => n * factorial(n - 1) def factorialSeq: LazyList[BigInt] = LazyList.from(1).scanLeft(BigInt(1))(_ * _) // import Factorial.factorialSeq scala> factorialSeq.take(10).toList val res0: List[BigInt] = List(1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880) scala> factorialSeq(100) val res1: BigInt = 933262154439441526816992388562667004907159 6826438162146859296389521759999322991560894146397615651828625 3697920827223758251185210916864000000000000000000000000 42
  • 43.
    ;;; Clojure (ns factorial) (defnfactorial [n] (if (zero? n) 1 (*' n (factorial (dec' n))))) (defn factorial-seq [] (reductions *' 1 (iterate inc' 1))) ;; (require '[factorial :refer [factorial-seq]]) user=> (take 10 (factorial-seq)) (1 1 2 6 24 120 720 5040 40320 362880) user=> (nth (factorial-seq) 100) 9332621544394415268169923885626670049071596826438162146859296 3895217599993229915608941463976156518286253697920827223758251 185210916864000000000000000000000000N 43
  • 44.
    ## Elixir defmodule Factorialdo def factorial(0), do: 1 def factorial(n), do: n * factorial(n - 1) def factorial_seq do Stream.concat( [1], Stream.scan(Stream.from_index(1), &*/2) ) end end # import Factorial, only: [factorial_seq: 0] iex(2)> Enum.take(factorial_seq, 10) [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880] iex(3)> Enum.at(factorial_seq, 100) 9332621544394415268169923885626670049071596826438162146859296 3895217599993229915608941463976156518286253697920827223758251 185210916864000000000000000000000000 44
  • 45.
    再帰と無限リスト(2): フィボナッチ数 {- Haskell-} module Fibonacci where fib :: Integral a => a -> a fib 0 = 0 fib 1 = 1 fib n = fib (n - 1) + fib (n - 2) fibSeq :: Integral a => [a] fibSeq = map fst $ iterate ((a, b) -> (b, a + b)) (0, 1) -- import Fibonacci (fibSeq) λ> take 10 fibSeq [0,1,1,2,3,5,8,13,21,34] it :: Integral a => [a] λ> take 3 $ drop 100 fibSeq [354224848179261915075,573147844013817084101, 927372692193078999176] it :: Integral a => [a] 45
  • 46.
    /* Scala */ objectFibonacci: def fib(n: Int): BigInt = n match case 0 => 0 case 1 => 1 case _ => fib(n - 1) + fib(n - 2) def fibSeq: LazyList[BigInt] = LazyList.iterate((BigInt(0), BigInt(1))) { case (a, b) => (b, a + b) }.map(_(0)) // import Fibonacci.fibSeq scala> fibSeq.take(10).toList val res2: List[BigInt] = List(0, 1, 1, 2, 3, 5, 8, 13, 21, 34) scala> fibSeq.drop(100).take(3).toList val res3: List[BigInt] = List(354224848179261915075, 573147844013817084101, 927372692193078999176) 46
  • 47.
    ;;; Clojure (ns fibonacci) (defnfib [n] (case n 0 0 1 1 (+' (fib (-' n 1)) (fib (-' n 2))))) (defn fib-seq [] (->> [0 1] (iterate (fn [[a b]] [b (+' a b)])) (map first))) ;; (require '[fibonacci :refer [fib-seq]]) user=> (take 10 (fib-seq)) (0 1 1 2 3 5 8 13 21 34) user=> (->> (fib-seq) (drop 100) (take 3)) (354224848179261915075N 573147844013817084101N 927372692193078999176N) 47
  • 48.
    ## Elixir defmodule Fibonaccido def fib(0), do: 0 def fib(1), do: 1 def fib(n), do: fib(n - 1) + fib(n - 2) def fib_seq do {0, 1} |> Stream.iterate(fn {a, b} -> {b, a + b} end) |> Stream.map(&elem(&1, 0)) end end # import Fibonacci, only: [fib_seq: 0] iex(2)> Enum.take(fib_seq, 10) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] iex(3)> fib_seq |> Stream.drop(100) |> Enum.take(3) [354224848179261915075, 573147844013817084101, 927372692193078999176] 48
  • 49.
  • 50.
  • 51.
    パターン (patterns) 再帰 (recursion) 遅延評価 (lazy evaluation) メモ化 (memoization) 並⾏プログラミング (concurrent programming) アクターモデル (actormodel) STM (software transactional memory) CSP (communicating sequential processes) 継続 (continuation) ⾼階関数 (higher-order function) 関数合成 (function composition) カリー化 (currying) 部分適⽤ (partial application) 評価 (evaluation) 制御 (control) 関数 (function) パイプ演算⼦ (pipe operator) メソッドチェーン (method chaining) 代数的データ型 (algebraic data type; ADT) パターンマッチ (pattern matching) クロージャー/関数閉包 (closure) オブジェクト (object) 抽象データ型 (abstract data type) データ (data) 直和型 (sum type) 直積型 (product type) カプセル化 (encapsulation) ポリモーフィズム/多相 (polymorphism) サブタイプ多相 (subtype polymorphism) パラメータ多相 (parametric polymorphism) アドホック多相 (ad hoc polymorphism) 型クラス (type class) マルチメソッド (multimethod) プロトコル (protocol) 変性 (variance) 継承 (inheritance) 分配束縛 (destructuring) 純粋関数型データ構造 (purely functional data structure) 永続データ構造 (persistent data structure) オーバーロード/多重定義 (overloading) pipes & filters goroutines & channels データ指向プログラミング (data-oriented programming) ファンクター (functor) モナド (monad) リスト (list) 遅延リスト/ストリーム (lazy list/stream) ベクター (vector) 全域関数 (total function) 部分関数 (partial function) オブジェクト指向プログラミング (object-oriented programming) アプリカティブ (applicative) トレイト (trait) 必要呼び (call by need) ジェネリクス (generics) 末尾再帰 (tail recursion) 再帰型 (recursive type) GoFデザインパターン (GoF Design Patterns) 51
  • 52.
    ⾼階関数 (higher-order function) 関数合成 (functioncomposition) カリー化 (currying) 部分適⽤ (partial application) 関数 (function) パイプ演算⼦ (pipe operator) メソッドチェーン (method chaining) クロージャー/関数閉包 (closure) オブジェクト (object) pipes & filters 全域関数 (total function) 部分関数 (partial function) オブジェクト指向プログラミング (object-oriented programming) GoFデザインパターン (GoF Design Patterns) 52
  • 53.
  • 54.
    部分適用 {- Haskell -} λ>:t map -- 関数はカリー(1引数関数の連鎖)化されている map :: (a -> b) -> [a] -> [b] λ> :t (+) -- 演算子もカリー化されている (+) :: Num a => a -> a -> a λ> :t (+ 1) -- x -> x + 1 と等価(セクション記法による部分適用) (+ 1) :: Num a => a -> a λ> :t map (+ 1) -- xs -> map (+ 1) xs と等価(部分適用) map (+ 1) :: Num b => [b] -> [b] λ> map (+ 1) [0..9] [1,2,3,4,5,6,7,8,9,10] it :: (Num b, Enum b) => [b] λ> f x y z = x * y * z f :: Num a => a -> a -> a -> a λ> :t f 2 3 -- 2番目の引数まで部分適用 f 2 3 :: Num a => a -> a λ> f 2 3 4 24 it :: Num a => a 54
  • 55.
    /* Scala */ scala>:t (1 to 10).map // メソッドが関数になる(eta-expansion) (Int => Any) => IndexedSeq[Any] scala> :t (_: Int) + (_: Int) // (x, y) => x + y と等価 (Int, Int) => Int scala> :t (_: Int) + 1 // x => x + 1 と等価(部分適用) Int => Int scala> (0 to 9).map(_ + 1) val res0: IndexedSeq[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) scala> def f(x: Int)(y: Int)(z: Int): Int = x * y * z def f(x: Int)(y: Int)(z: Int): Int scala> f(2)(3) // 2番目の引数まで部分適用 val res1: Int => Int = Lambda/0x00001ff001554c40@478c84aa scala> f(2)(3)(4) val res2: Int = 24 55
  • 56.
    ;;; Clojure user=> #(+%1 %2 %3) ; (fn [x y z] (+ x y z)) と等価 #object[user$eval245$fn__246 0x7103ab0 "user$eval245$fn__246@ 7103ab0"] user=> #(+ % 1) ; (fn [x] (+ x 1)) と等価(部分適用) #object[user$eval235$fn__236 0x4c03a37 "user$eval235$fn__236@ 4c03a37"] user=> (map #(+ % 1) (range 0 (inc 9))) (1 2 3 4 5 6 7 8 9 10) user=> (defn f [x y z] (* x y z)) #'user/f user=> (partial f 2 3) ; 2番目の引数まで部分適用 = #(f 2 3 %) #object[clojure.core$partial$fn__5929 0x4ba89729 "clojure.cor e$partial$fn__5929@4ba89729"] user=> ((partial f 2 3) 4) 24 user=> (f 2 3 4) 24 56
  • 57.
    ## Elixir iex(1)> &(&1+ &2) # fn (x, y) -> x + y end と等価 &:erlang.+/2 iex(2)> &(&1 + 1) # fn x -> x + 1 end と等価(部分適用) #Function<42.81571850/1 in :erl_eval.expr/6> iex(3)> Enum.map(0..9, &(&1 + 1)) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] iex(4)> f = fn x -> fn y -> fn z -> x * y * z end end end #Function<42.81571850/1 in :erl_eval.expr/6> iex(5)> f.(2).(3) # 2番目の引数まで部分適用 #Function<42.81571850/1 in :erl_eval.expr/6> iex(6)> f.(2).(3).(4) 24 57
  • 58.
    関数合成 ref. {- Haskell: importFibonacci (fibSeq) -} λ> sumOfEvenFibs upper = sum (takeWhile (<= upper) (filter even (drop 2 fibSeq))) sumOfEvenFibs :: Integral a => a -> a λ> :{ λ| sumOfEvenFibs' upper = sum λ| $ takeWhile (<= upper) -- f $ x = f x λ| $ filter even -- (関数適用演算子) λ| $ drop 2 fibSeq λ| :} sumOfEvenFibs' :: Integral a => a -> a λ> sumOfEvenFibs' 4000000 4613732 it :: Integral a => a #2 Even Fibonacci Numbers - Project Euler 58
  • 59.
    {- Haskell -} λ>:{ λ| sumOfEvenNums :: Integral a => a -> [a] -> a λ| sumOfEvenNums upper = sum λ| . takeWhile (<= upper) -- f . g = x -> f (g x) λ| . filter even -- (関数合成演算子) λ| :} sumOfEvenNums :: Integral a => a -> [a] -> a λ> sumOfEvenNums 4000000 $ drop 2 fibSeq 4613732 it :: Integral a => a 59
  • 60.
    /* Scala: importFibonacci.fibSeq */ scala> def sumOfEvenFibs(upper: BigInt) = | fibSeq | .drop(2) | .filter(_ % 2 == 0) | .takeWhile(_ <= upper) | .sum | def sumOfEvenFibs(upper: BigInt): BigInt scala> sumOfEvenFibs(4000000) val res0: BigInt = 4613732 60
  • 61.
    /* Scala */ scala>extension (nums: Seq[BigInt]) // 拡張メソッドの定義 | def sumOfEvenNums(upper: BigInt): BigInt = | nums | .filter(_ % 2 == 0) | .takeWhile(_ <= upper) | .sum | def sumOfEvenNums(ns: Seq[BigInt])(upper: BigInt): BigInt scala> fibSeq.drop(2).sumOfEvenNums(4000000) val res1: BigInt = 4613732 61
  • 62.
    /* Scala */ scala>def sumOfEvenNums2(upper: BigInt)(nums: Seq[BigInt]): BigInt = | nums | .filter(_ % 2 == 0) | .takeWhile(_ <= upper) | .sum | def sumOfEvenNums2(upper: BigInt)(nums: Seq[BigInt]): BigInt scala> import scala.util.chaining._ // pipeメソッドを導入 scala> fibSeq.drop(2).pipe(sumOfEvenNums2(4000000)) val res2: BigInt = 4613732 62
  • 63.
    ;;; Clojure: (require'[fibonacci :refer [fib-seq]]) user=> (defn sum-of-even-fibs [upper] (apply + (take-while #(<= % upper) (filter even? (drop 2 (fib-seq)))))) #'user/sum-of-even-fibs user=> (defn sum-of-even-fibs' [upper] (->> (fib-seq) ; (->> x f g) = (g (f x)) (drop 2) ; (thread-lastマクロ) (filter even?) (take-while #(<= % upper)) (apply +))) #'user/sum-of-even-fibs' user=> (sum-of-even-fibs' 4000000) 4613732 63
  • 64.
    ;;; Clojure user=> (defnsum-of-even-fibs'' [upper] (transduce ;; 関数合成関数compでトランスデューサー(transducer)を合成 (comp (drop 2) (filter even?) (take-while #(<= % upper))) + (fib-seq))) #'user/sum-of-even-fibs'' user=> user=> (sum-of-even-fibs'' 4000000) 4613732 user=> (defn sum-of-even-nums [upper nums] (transduce (comp (filter even?) (take-while #(<= % upper))) + nums)) #'user/sum-of-even-nums user=> (->> (fib-seq) (drop 2) (sum-of-even-nums 4000000)) 4613732 64
  • 65.
    ## Elixir: importFibonacci, only: [fib_seq: 0] iex(2)> require Integer Integer iex(3)> sum_of_even_fibs = fn upper -> ...(3)> Enum.sum(Stream.take_while(Stream.filter( Stream.drop(fib_seq, 2), &Integer.is_even/1), &(&1 <= upper))) ...(3)> end #Function<42.81571850/1 in :erl_eval.expr/6> iex(4)> sum_of_even_fibs2 = fn upper -> ...(4)> fib_seq # x |> f |> g = g(f(x)) ...(4)> |> Stream.drop(2) # (パイプ演算子) ...(4)> |> Stream.filter(&Integer.is_even/1) ...(4)> |> Stream.take_while(&(&1 <= upper)) ...(4)> |> Enum.sum() ...(4)> end #Function<42.81571850/1 in :erl_eval.expr/6> iex(5)> sum_of_even_fibs2.(4000000) 4613732 65
  • 66.
    ## Elixir iex(6)> sum_of_even_nums= fn nums, upper -> ...(6)> nums ...(6)> |> Stream.filter(&Integer.is_even/1) ...(6)> |> Stream.take_while(&(&1 <= upper)) ...(6)> |> Enum.sum() ...(6)> end #Function<41.81571850/2 in :erl_eval.expr/6> iex(7)> fib_seq |> Stream.drop(2) |> sum_of_even_nums.(4000000) 4613732 66
  • 67.
  • 68.
    パターン (patterns) 再帰 (recursion) 遅延評価 (lazy evaluation) メモ化 (memoization) 並⾏プログラミング (concurrent programming) アクターモデル (actormodel) STM (software transactional memory) CSP (communicating sequential processes) 継続 (continuation) ⾼階関数 (higher-order function) 関数合成 (function composition) カリー化 (currying) 部分適⽤ (partial application) 評価 (evaluation) 制御 (control) 関数 (function) パイプ演算⼦ (pipe operator) メソッドチェーン (method chaining) 代数的データ型 (algebraic data type; ADT) パターンマッチ (pattern matching) クロージャー/関数閉包 (closure) オブジェクト (object) 抽象データ型 (abstract data type) データ (data) 直和型 (sum type) 直積型 (product type) カプセル化 (encapsulation) ポリモーフィズム/多相 (polymorphism) サブタイプ多相 (subtype polymorphism) パラメータ多相 (parametric polymorphism) アドホック多相 (ad hoc polymorphism) 型クラス (type class) マルチメソッド (multimethod) プロトコル (protocol) 変性 (variance) 継承 (inheritance) 分配束縛 (destructuring) 純粋関数型データ構造 (purely functional data structure) 永続データ構造 (persistent data structure) オーバーロード/多重定義 (overloading) pipes & filters goroutines & channels データ指向プログラミング (data-oriented programming) ファンクター (functor) モナド (monad) リスト (list) 遅延リスト/ストリーム (lazy list/stream) ベクター (vector) 全域関数 (total function) 部分関数 (partial function) オブジェクト指向プログラミング (object-oriented programming) アプリカティブ (applicative) トレイト (trait) 必要呼び (call by need) ジェネリクス (generics) 末尾再帰 (tail recursion) 再帰型 (recursive type) GoFデザインパターン (GoF Design Patterns) 68
  • 69.
    代数的データ型 (algebraic data type;ADT) パターンマッチ (pattern matching) 抽象データ型 (abstract data type) データ (data) 直和型 (sum type) 直積型 (product type) カプセル化 (encapsulation) 分配束縛 (destructuring) 純粋関数型データ構造 (purely functional data structure) 永続データ構造 (persistent data structure) データ指向プログラミング (data-oriented programming) ファンクター (functor) モナド (monad) リスト (list) 遅延リスト/ストリーム (lazy list/stream) ベクター (vector) オブジェクト指向プログラミング (object-oriented programming) アプリカティブ (applicative) 再帰型 (recursive type) 69
  • 70.
  • 71.
    標準の不変/永続コレクション {- Haskell -} λ>:t [1, 2, 3] -- (連結)リスト [1, 2, 3] :: Num a => [a] λ> 0 : [1, 2, 3] -- 先頭に要素追加(cons) [0,1,2,3] it :: Num a => [a] λ> import qualified Data.Vector as V λ> :t V.fromList [1, 2, 3] -- ベクター(可変長配列) V.fromList [1, 2, 3] :: Num a => V.Vector a λ> V.snoc (V.fromList [1, 2, 3]) 4 -- 末尾に要素追加(snoc) [1,2,3,4] it :: Num a => V.Vector a 71
  • 72.
    {- Haskell -} λ>import qualified Data.Set as S λ> :t S.fromList [1, 2, 3] -- セット S.fromList [1, 2, 3] :: (Ord a, Num a) => S.Set a λ> S.insert 4 $ S.fromList [1, 2, 3] fromList [1,2,3,4] it :: (Ord a, Num a) => S.Set a λ> import qualified Data.Map as M λ> :t M.fromList [("a", 1), ("b", 2), ("c", 3)] -- マップ M.fromList [("a", 1), ("b", 2), ("c", 3)] :: Num a => M.Map String a λ> M.insert "d" 4 $ M.fromList [("a", 1), ("b", 2), ("c", 3)] fromList [("a",1),("b",2),("c",3),("d",4)] it :: Num a => M.Map String a 72
  • 73.
    {- Haskell -} λ>[x * y | x <- [1..2], y <- [1..9]] -- リスト内包表記 [1,2,3,4,5,6,7,8,9,2,4,6,8,10,12,14,16,18] it :: (Enum a, Num a) => [a] λ> :{ λ| do -- do記法(a.k.a. モナド内包表記) λ| x <- [1..2] λ| y <- [1..9] λ| return $ x * y λ| :} [1,2,3,4,5,6,7,8,9,2,4,6,8,10,12,14,16,18] it :: (Num b, Enum b) => [b] λ> :{ λ| do -- Maybe, Eitherなど任意のモナドで利用できる λ| x <- Just 2 λ| y <- Just 3 λ| return $ x * y λ| :} Just 6 it :: Num b => Maybe b 73
  • 74.
    /* Scala */ scala>List(1, 2, 3) // (連結)リスト val res0: List[Int] = List(1, 2, 3) scala> 0 :: List(1, 2, 3) // 先頭に要素追加(cons) val res1: List[Int] = List(0, 1, 2, 3) scala> Vector(1, 2, 3) // ベクター(可変長配列) val res2: Vector[Int] = Vector(1, 2, 3) scala> Vector(1, 2, 3) :+ 4 // 末尾に要素追加 val res3: Vector[Int] = Vector(1, 2, 3, 4) 74
  • 75.
    /* Scala */ scala>Set(1, 2, 3) // セット val res4: Set[Int] = Set(1, 2, 3) scala> Set(1, 2, 3) + 4 // 要素追加 val res5: Set[Int] = Set(1, 2, 3, 4) scala> Map("a" -> 1, "b" -> 2, "c" -> 3) // マップ val res6: Map[String, Int] = Map(a -> 1, b -> 2, c -> 3) scala> Map("a" -> 1, "b" -> 2, "c" -> 3) + ("d" -> 4) val res7: Map[String, Int] = Map(a -> 1, b -> 2, c -> 3, d -> 4) 75
  • 76.
    /* Scala */ scala>for // for式(a.k.a. for内包表記) | x <- 1 to 2 | y <- 1 to 9 | yield x * y val res8: IndexedSeq[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 2, 4, 6, 8, 10, 12, 14, 16, 18) scala> for // Option, EitherなどflatMap/mapを備えた型で利用できる | x <- Some(2) | y <- Some(3) | yield x * y val res9: Option[Int] = Some(6) 76
  • 77.
    ;;; Clojure user=> (type'(1 2 3)) ; (連結)リスト clojure.lang.PersistentList user=> (conj '(1 2 3) 0) ; 先頭に要素追加 (0 1 2 3) user=> (cons 0 '(1 2 3)) ; シーケンスとして要素追加 (0 1 2 3) user=> (type [1 2 3]) ; ベクター(可変長配列) clojure.lang.PersistentVector user=> (conj [1 2 3] 4) ; 末尾に要素追加 [1 2 3 4] user=> (cons 0 [1 2 3]) ; シーケンスとして要素追加 (0 1 2 3) 77
  • 78.
    ;;; Clojure user=> (type#{1 2 3}) ; セット clojure.lang.PersistentHashSet user=> (conj #{1 2 3} 4) ; 要素追加 #{1 4 3 2} user=> (cons 0 #{1 2 3}) ; シーケンスとして要素追加 (0 1 3 2) user=> (type {:a 1 :b 2 :c 3}) ; マップ clojure.lang.PersistentArrayMap user=> (assoc {:a 1 :b 2 :c 3} :d 4) ; エントリー追加 {:a 1, :b 2, :c 3, :d 4} user=> (cons [:z 0] {:a 1 :b 2 :c 3}) ; シーケンスとして要素追加 ([:z 0] [:a 1] [:b 2] [:c 3]) 78
  • 79.
    ;;; Clojure user=> (for[x (range 1 (inc 2)) ; forマクロ y (range 1 (inc 9))] ; (a.k.a. シーケンス内包表記) (* x y)) (1 2 3 4 5 6 7 8 9 2 4 6 8 10 12 14 16 18) ;; 様々なデータがシーケンス(論理的なリスト)として扱える(seqable) user=> (take 2 "abc") ; 文字列 (a b) ; 文字シーケンス user=> (take 2 (java.util.List/of 1 2 3)) ; Javaのリスト (1 2) ; Clojureのシーケンス user=> (take 2 {:a 1 :b 2 :c 3}) ; マップ ([:a 1] [:b 2]) ; エントリー(ベクター)シーケンス user=> (require '[clojure.java.io :as io]) nil user=> (with-open [r (io/reader "fibonacci.clj")] ; ファイル (->> r line-seq (take 3) doall)) ("(ns fibonacci)" "" "(defn fib [n]") ; テキスト行シーケンス 79
  • 80.
    ## Elixir iex(1)> i[1, 2, 3] # (連結)リスト Term [1, 2, 3] Data type List ... iex(2)> [0 | [1, 2, 3]] # 先頭に要素追加(cons) [0, 1, 2, 3] iex(3)> i [a: 1, b: 2, c: 3] # キーワードリスト Term [a: 1, b: 2, c: 3] Data type List ... iex(4)> [{:z, 0} | [a: 1, b: 2, c: 3]] # 先頭に要素追加 [z: 0, a: 1, b: 2, c: 3] 80
  • 81.
    ## Elixir iex(5)> iMapSet.new([1, 2, 3]) # セット Term MapSet.new([1, 2, 3]) Data type MapSet ... iex(6)> MapSet.put(MapSet.new([1, 2, 3]), 4) MapSet.new([1, 2, 3, 4]) iex(7)> i %{a: 1, b: 2, c: 3} # マップ Term %{c: 3, b: 2, a: 1} Data type Map ... iex(8)> Map.put(%{a: 1, b: 2, c: 3}, :d, 4) %{c: 3, b: 2, a: 1, d: 4} 81
  • 82.
    ## Elixir iex(9)> forx <- 1..2, # Enumerableに対する内包表記 ...(9)> y <- 1..9 do ...(9)> x * y ...(9)> end [1, 2, 3, 4, 5, 6, 7, 8, 9, 2, 4, 6, 8, 10, 12, 14, 16, 18] # EnumerableであればEnumモジュールの関数で扱える iex(10)> Enum.take([a: 1, b: 2, c: 3], 2) [a: 1, b: 2] iex(11)> Enum.take(MapSet.new([1, 2, 3]), 2) [1, 2] iex(12)> Enum.take(%{a: 1, b: 2, c: 3}, 2) [c: 3, b: 2] iex(13)> Enum.take([1, 2, 3], 2) [1, 2] 82
  • 83.
    データ型の定義と値の構築・分解/分岐 {- Haskell -} λ>:{ λ| data Tree a -- 代数的データ型の定義 λ| = Leaf !a λ| | Branch { left :: !(Tree a) λ| , right :: !(Tree a) λ| } λ| deriving (Show, Eq) λ| :} type Tree :: * -> * data Tree a = ... left :: Tree a -> Tree a right :: Tree a -> Tree a 83
  • 84.
    {- Haskell -} λ>t = Branch (Branch (Leaf 1) (Branch (Leaf 2) (Leaf 3))) (Leaf 4) t :: Num a => Tree a [Prelude] λ> left t Branch {left = Leaf 1, right = Branch {left = Leaf 2, right = Leaf 3}} it :: Num a => Tree a λ> right t Leaf 4 it :: Num a => Tree a λ> :{ λ| size :: Tree a -> Int λ| size (Leaf _) = 1 -- 関数の引数でのパターンマッチ λ| size (Branch l r) = 1 + size l + size r λ| :} size :: Tree a -> Int λ> size t 7 it :: Int 84
  • 85.
    /* Scala */ scala>enum Tree[+A]: // 代数的データ型の定義 | case Leaf(value: A) | case Branch(left: Tree[A], right: Tree[A]) | // defined class Tree scala> import Tree.* scala> val t: Branch[Int] = Branch(Branch(Leaf(1), Branch( Leaf(2), Leaf(3))), Leaf(4)) val t: Tree.Branch[Int] = Branch(Branch(Leaf(1),Branch( Leaf(2),Leaf(3))),Leaf(4)) scala> t.left val res0: Tree[Int] = Branch(Leaf(1),Branch(Leaf(2),Leaf(3))) scala> t.right val res1: Tree[Int] = Leaf(4) 85
  • 86.
    /* Scala */ scala>extension [A](tree: Tree[A]) // 拡張メソッドの定義 | def size: Int = tree match | case Leaf(_) => 1 | case Branch(l, r) => 1 + l.size + r.size | def size[A](tree: Tree[A]): Int scala> t.size val res2: Int = 7 86
  • 87.
    ;;; Clojure user=> (defrecordLeaf [value]) ; レコードの定義 user.Leaf user=> (defrecord Branch [left right]) user.Branch user=> (def t (->Branch (->Branch (->Leaf 1) (->Branch (->Leaf 2) (->Leaf 3))) (->Leaf 4))) #'user/t user=> (:left t) #user.Branch{:left #user.Leaf{:value 1}, :right #user.Branch{ :left #user.Leaf{:value 2}, :right #user.Leaf{:value 3}}} user=> (:right t) #user.Leaf{:value 4} 87
  • 88.
    ;;; Clojure user=> (defmultisize class) ; マルチメソッドの定義 #'user/size user=> (defmethod size Leaf [_] 1) #object[clojure.lang.MultiFn 0x6fc3e1a4 "clojure.lang.MultiFn @6fc3e1a4"] ; ↓ 関数の引数での分配束縛 user=> (defmethod size Branch [{:keys [left right]}] (+ 1 (size left) (size right))) #object[clojure.lang.MultiFn 0x6fc3e1a4 "clojure.lang.MultiFn @6fc3e1a4"] user=> (size t) 7 88
  • 89.
    ## Elixir iex(1)> defmoduleLeaf do # 構造体の定義 ...(1)> defstruct [:value] ...(1)> end {:module, Leaf, ...} iex(2)> defmodule Branch do ...(2)> defstruct [:left, :right] ...(2)> end {:module, Branch, ...} iex(3)> t = %Branch{left: %Branch{left: %Leaf{value: 1}, right: %Branch{left: %Leaf{value: 2}, right: %Leaf{value: 3}}}, right: %Leaf{value: 4}} %Branch{...} iex(4)> t.left %Branch{left: %Leaf{value: 1}, right: %Branch{left: %Leaf{ value: 2}, right: %Leaf{value: 3}}} iex(5)> t.right %Leaf{value: 4} 89
  • 90.
    ## Elixir iex(6)> defmoduleTree do ...(6)> def size(%Leaf{}), do: 1 # 関数の引数でのパターンマッチ ...(6)> def size(%Branch{left: left, right: right}) do ...(6)> 1 + size(left) + size(right) ...(6)> end ...(6)> end {:module, Tree, ...} iex(7)> Tree.size(t) 7 90
  • 91.
  • 92.
    パターン (patterns) 再帰 (recursion) 遅延評価 (lazy evaluation) メモ化 (memoization) 並⾏プログラミング (concurrent programming) アクターモデル (actormodel) STM (software transactional memory) CSP (communicating sequential processes) 継続 (continuation) ⾼階関数 (higher-order function) 関数合成 (function composition) カリー化 (currying) 部分適⽤ (partial application) 評価 (evaluation) 制御 (control) 関数 (function) パイプ演算⼦ (pipe operator) メソッドチェーン (method chaining) 代数的データ型 (algebraic data type; ADT) パターンマッチ (pattern matching) クロージャー/関数閉包 (closure) オブジェクト (object) 抽象データ型 (abstract data type) データ (data) 直和型 (sum type) 直積型 (product type) カプセル化 (encapsulation) ポリモーフィズム/多相 (polymorphism) サブタイプ多相 (subtype polymorphism) パラメータ多相 (parametric polymorphism) アドホック多相 (ad hoc polymorphism) 型クラス (type class) マルチメソッド (multimethod) プロトコル (protocol) 変性 (variance) 継承 (inheritance) 分配束縛 (destructuring) 純粋関数型データ構造 (purely functional data structure) 永続データ構造 (persistent data structure) オーバーロード/多重定義 (overloading) pipes & filters goroutines & channels データ指向プログラミング (data-oriented programming) ファンクター (functor) モナド (monad) リスト (list) 遅延リスト/ストリーム (lazy list/stream) ベクター (vector) 全域関数 (total function) 部分関数 (partial function) オブジェクト指向プログラミング (object-oriented programming) アプリカティブ (applicative) トレイト (trait) 必要呼び (call by need) ジェネリクス (generics) 末尾再帰 (tail recursion) 再帰型 (recursive type) GoFデザインパターン (GoF Design Patterns) 92
  • 93.
  • 94.
  • 95.
    評価の制御と遅延コレクション {- Haskell: 言語のデフォルトの評価戦略は遅延(非正格)評価-} -- 関数の定義: call by need λ> f x y = if x > 0 then x else y f :: (Ord a, Num a) => a -> a -> a -- 引数yにアクセスされなければエラーが生じない λ> f 1 (2 `div` 0) 1 it :: Integral a => a λ> f 0 (2 `div` 0) *** Exception: divide by zero -- bang patternを利用した関数の定義: call by value λ> g !x !y = if x > 0 then x else y g :: (Ord a, Num a) => a -> a -> a -- 引数x, yは直ちに評価される λ> g 1 (2 `div` 0) *** Exception: divide by zero 95
  • 96.
    {- Haskell -} --無限に続く整数のリストから先頭3要素を取り出す λ> take 3 [0..] [0,1,2] it :: (Num a, Enum a) => [a] -- 3番目の要素にアクセスしなければエラーが生じない λ> [1, 2, 3 `div` 0] !! 2 *** Exception: divide by zero λ> take 2 [1, 2, 3 `div` 0] [1,2] 96
  • 97.
    {- Haskell -} --データ型の定義 λ> data Pair = Pair { l :: Int, r :: Int } ... -- r にアクセスしなければエラーが生じない λ> l $ Pair 1 (2 `div` 0) 1 it :: Int λ> r $ Pair 1 (2 `div` 0) *** Exception: divide by zero -- 値コンストラクタに正格性フラグを利用したデータ型の定義 λ> data Pair' = Pair' { l :: !Int, r :: !Int } ... -- r にアクセスしなくてもエラーが生じる λ> l $ Pair' 1 (2 `div` 0) *** Exception: divide by zero 97
  • 98.
    /* Scala: 言語の評価戦略は積極(正格)評価*/ // 関数の定義: call by value scala> def f(x: Int, y: Int): Int = | if (x > 0) x else y | def f(x: Int, y: Int): Int // 引数x, yは直ちに評価される scala> f(1, 2 / 0) java.lang.ArithmeticException: / by zero // 名前渡しパラメータを利用した関数の定義: call by name scala> def g(x: => Int, y: => Int): Int = | if (x > 0) x else y // この場合、xは最大2回評価される | // (回避するには評価結果をローカル束縛(メモ化)して再利用) def g(x: => Int, y: => Int): Int // 引数yにアクセスされなければエラーが生じない scala> g(1, 2 / 0) val res1: Int = 1 scala> g(0, 2 / 0) java.lang.ArithmeticException: / by zero 98
  • 99.
    /* Scala */ //無限に続く整数の遅延リストから先頭3要素を取り出す scala> LazyList.from(0).take(3).toList val res3: List[Int] = List(0, 1, 2) // 3番目の要素にアクセスしなければエラーが生じない scala> (1 #:: 2 #:: (3 / 0) #:: LazyList.empty)(2) java.lang.ArithmeticException: / by zero scala> (1 #:: 2 #:: (3 / 0) #:: LazyList.empty).take(2) .toList val res5: List[Int] = List(1, 2) 99
  • 100.
    ;;; Clojure: 言語の評価戦略は積極(正格)評価 ;;関数の定義: call by value user=> (defn f [x y] (if (pos? x) x y)) #'user/f ;; 引数x, yは直ちに評価される user=> (f 1 (/ 2 0)) Execution error (ArithmeticException) at user/eval373 ... Divide by zero ;; マクロの定義: call by name user=> (defmacro g [x y] `(if (pos? ~x) ~x ~y)) ; この場合、xは最大2回評価される #'user/g ; (回避するには評価結果をローカル束縛(メモ化)して再利用) ;; 引数yにアクセスされなければエラーが生じない user=> (g 1 (/ 2 0)) 1 user=> (g 0 (/ 2 0)) Execution error (ArithmeticException) at user/eval382 ... Divide by zero 100
  • 101.
    ;;; Clojure ;; 無限に続く整数の遅延シーケンスから先頭3要素を取り出す user=>(take 3 (range)) (0 1 2) ;; 3番目の要素にアクセスしなければエラーが生じない user=> (nth (lazy-seq (cons 1 (lazy-seq (cons 2 (lazy-seq (cons (/ 3 0) nil)))))) 2) Execution error (ArithmeticException) at user/eval428$fn$f... Divide by zero user=> (take 2 (lazy-seq (cons 1 (lazy-seq (cons 2 (lazy-seq (cons (/ 3 0) nil))))))) (1 2) 101
  • 102.
    ;;; Clojure ;; 最も基本的な高階関数map,filterさえ遅延シーケンスを返す user=> (type (map inc (range 0 (inc 9)))) clojure.lang.LazySeq user=> (type (filter odd? (range 0 (inc 9)))) clojure.lang.LazySeq user=> (require '[clojure.java.io :as io]) nil ;; line-seqも遅延シーケンスを返すため実体化前にリソース解放するとエラー user=> (with-open [r (io/reader "fibonacci.clj")] (->> r line-seq (take 3))) Error printing return value (IOException) at java.io.Buffered Reader/ensureOpen (BufferedReader.java:123). Stream closed user=> (with-open [r (io/reader "fibonacci.clj")] (->> r line-seq (take 3) doall)) ; doallで直ちに実体化 ("(ns fibonacci)" "" "(defn fib [n]") 102
  • 103.
    ## Elixir: 言語の評価戦略は積極(正格)評価 #関数の定義: call by value iex(1)> defmodule Some do ...(1)> def f(x, y) do ...(1)> if x > 0, do: x, else: y ...(1)> end ...(1)> end {:module, Some, ...} # 引数x, yは直ちに評価される iex(2)> Some.f(1, 2 / 0) ** (ArithmeticError) bad argument in arithmetic expression... 103
  • 104.
    ## Elixir # マクロの定義:call by name iex(3)> defmodule Other do ...(3)> defmacro g(x, y) do ...(3)> quote do ...(3)> if unquote(x) > 0, # この場合、xは最大2回評価される ...(3)> do: unquote(x), else: unquote(y) ...(3)> end ...(3)> end # (回避するには評価結果をローカル束縛(メモ化)して再利用) ...(3)> end {:module, Other, ...} iex(4)> require Other Other # 引数yにアクセスされなければエラーが生じない iex(5)> Other.g(1, 2 / 0) 1 iex(6)> Other.g(0, 2 / 0) ** (ArithmeticError) bad argument in arithmetic expression... 104
  • 105.
    ## Elixir # 無限に続く整数のストリームから先頭3要素を取り出す iex(7)>Enum.take(Stream.from_index, 3) [0, 1, 2] # 3番目の要素にアクセスしなければエラーが生じない iex(8)> 1..3 |> Stream.map(fn n -> if n < 3, do: n, else: n / 0 end) |> Enum.at(2) ** (ArithmeticError) bad argument in arithmetic expression... iex(9)> 1..3 |> Stream.map(fn n -> if n < 3, do: n, else: n / 0 end) |> Enum.take(2) [1, 2] 105
  • 106.
    BONUS: (評価の制御といえば)マクロ i.e. ASTレベルでのコンパイル時メタプログラミング 式(oparg0 arg1 ...) の個々の引数arg0, arg1, ...がオペレータop に適用される前に関数 action-fn の呼び出しを差し込めるマクロ ;;; Clojure (defmacro apply-after [action-fn op & args] ; マクロの定義 (let [args (->> args (map (juxt identity pr-str)) (map-indexed (fn [i [expr s]] `(doto ~expr (~action-fn ~i ~s)))))] `(~op ~@args))) 106
  • 107.
    ;;; Clojure user=> (defninspect [v i s] (println (str "arg" i ":") s "=>" v)) #'user/inspect user=> (apply-after inspect + (- 1 2 3) (* 4 5) (/ 6 7)) arg0: (- 1 2 3) => -4 arg1: (* 4 5) => 20 arg2: (/ 6 7) => 6/7 118/7 ; (+ (- 1 2 3) (* 4 5) (/ 6 7)) と等価 user=> (apply-after inspect if (odd? 2) (println :odd) (println :even)) arg0: (odd? 2) => false :even ; (println :even) による標準出力 arg2: (println :even) => nil nil ; (if (odd? 2) (println :odd) (println :even)) と等価 107
  • 108.
    ;;; Clojure user=> (macroexpand-1; マクロ展開(1段階) '(apply-after inspect if (odd? 2) (println :odd) (println :even))) (if (clojure.core/doto (odd? 2) (inspect 0 "(odd? 2)")) (clojure.core/doto (println :odd) (inspect 1 "(println :odd)")) (clojure.core/doto (println :even) (inspect 2 "(println :even)"))) nil 108
  • 109.
  • 110.
    パターン (patterns) 再帰 (recursion) 遅延評価 (lazy evaluation) メモ化 (memoization) 並⾏プログラミング (concurrent programming) アクターモデル (actormodel) STM (software transactional memory) CSP (communicating sequential processes) 継続 (continuation) ⾼階関数 (higher-order function) 関数合成 (function composition) カリー化 (currying) 部分適⽤ (partial application) 評価 (evaluation) 制御 (control) 関数 (function) パイプ演算⼦ (pipe operator) メソッドチェーン (method chaining) 代数的データ型 (algebraic data type; ADT) パターンマッチ (pattern matching) クロージャー/関数閉包 (closure) オブジェクト (object) 抽象データ型 (abstract data type) データ (data) 直和型 (sum type) 直積型 (product type) カプセル化 (encapsulation) ポリモーフィズム/多相 (polymorphism) サブタイプ多相 (subtype polymorphism) パラメータ多相 (parametric polymorphism) アドホック多相 (ad hoc polymorphism) 型クラス (type class) マルチメソッド (multimethod) プロトコル (protocol) 変性 (variance) 継承 (inheritance) 分配束縛 (destructuring) 純粋関数型データ構造 (purely functional data structure) 永続データ構造 (persistent data structure) オーバーロード/多重定義 (overloading) pipes & filters goroutines & channels データ指向プログラミング (data-oriented programming) ファンクター (functor) モナド (monad) リスト (list) 遅延リスト/ストリーム (lazy list/stream) ベクター (vector) 全域関数 (total function) 部分関数 (partial function) オブジェクト指向プログラミング (object-oriented programming) アプリカティブ (applicative) トレイト (trait) 必要呼び (call by need) ジェネリクス (generics) 末尾再帰 (tail recursion) 再帰型 (recursive type) GoFデザインパターン (GoF Design Patterns) 110
  • 111.
    ポリモーフィズム/多相 (polymorphism) サブタイプ多相 (subtype polymorphism) パラメータ多相 (parametric polymorphism) アドホック多相 (adhoc polymorphism) 型クラス (type class) マルチメソッド (multimethod) プロトコル (protocol) 変性 (variance) 継承 (inheritance) オーバーロード/多重定義 (overloading) オブジェクト指向プログラミング (object-oriented programming) トレイト (trait) ジェネリクス (generics) 111
  • 112.
  • 113.
    サブタイプ多相に関する仕組み /* Scala */ scala>trait Solid: // トレイトの定義 | def surfaceArea: Double | def volume: Double | // defined trait Solid scala> case class Cuboid( // トレイトSolidを実装したクラスの定義 | a: Double, | b: Double, | c: Double, | ) extends Solid: | def surfaceArea: Double = | 2.0 * (a * b + b * c + c * a) | def volume: Double = | a * b * c 113
  • 114.
    /* Scala */ scala>case class Sphere(r: Double) extends Solid: | def surfaceArea: Double = | 4.0 * Math.PI * Math.pow(r, 2) | def volume: Double = | 4.0 / 3.0 * Math.PI * Math.pow(r, 3) scala> def solidProps(x: Solid): Map[String, Double] = | Map( | "surface area" -> x.surfaceArea, | "volume" -> x.volume, | ) | def solidProps(x: Solid): Map[String, Double] scala> Seq(Cuboid(2, 3, 4), Sphere(2)).map(solidProps) val res0: Seq[Map[String, Double]] = List(Map(surface area -> 52.0, volume -> 24.0), Map(surface area -> 50.26548245743669, volume -> 33.510321638291124)) 114
  • 115.
    アドホック多相に関する仕組み {- Haskell -} λ>:{ λ| class Solid a where -- 型クラスの定義 λ| surfaceArea :: a -> Double λ| volume :: a -> Double λ| :} type Solid :: Constraint λ> :{ λ| data Cuboid = Cuboid λ| { a :: !Double λ| , b :: !Double λ| , c :: !Double } λ| λ| data Sphere = Sphere { r :: !Double } λ| :} type Cuboid :: * ... 115
  • 116.
    {- Haskell -} λ>:{ λ| instance Solid Cuboid where -- 型クラスのインスタンスの定義 λ| surfaceArea (Cuboid a b c) = λ| 2 * (a * b + b * c + c * a) λ| volume (Cuboid a b c) = λ| a * b * c λ| λ| instance Solid Sphere where λ| surfaceArea (Sphere r) = λ| 4 * pi * r ^ 2 λ| volume (Sphere r) = λ| 4 / 3 * pi * r ^ 3 λ| :} 116
  • 117.
    {- Haskell -} λ>import qualified Data.Map as M λ> :{ λ| solidProps :: Solid a => a -> M.Map String Double λ| solidProps x = M.fromList λ| [("surface area", surfaceArea x), ("volume", volume x)] λ| :} solidProps :: Solid a => a -> M.Map String Double λ> solidProps $ Cuboid 2 3 4 fromList [("surface area",52.0),("volume",24.0)] it :: M.Map String Double λ> solidProps $ Sphere 2 fromList [("surface area",50.26548245743669),("volume", 33.510321638291124)] it :: M.Map String Double 117
  • 118.
    /* Scala */ scala>trait Solid2[A]: // 型クラスの定義 | extension (a: A) | def surfaceArea: Double | def volume: Double | // defined trait Solid2 scala> case class Cuboid2( | a: Double, | b: Double, | c: Double, | ) // defined case class Cuboid2 scala> case class Sphere2(r: Double) // defined case class Sphere2 118
  • 119.
    /* Scala */ scala>given Solid2[Cuboid2] with // 型クラスのインスタンスの定義 | extension (x: Cuboid2) | def surfaceArea: Double = | 2.0 * (x.a * x.b + x.b * x.c + x.c * x.a) | def volume: Double = | x.a * x.b * x.c | given Solid2[Sphere2] with | extension (x: Sphere2) | def surfaceArea: Double = | 4.0 * Math.PI * Math.pow(x.r, 2) | def volume: Double = | 4.0 / 3.0 * Math.PI * Math.pow(x.r, 3) // defined object given_Solid2_Cuboid2 // defined object given_Solid2_Sphere2 119
  • 120.
    /* Scala */ scala>def solidProps[A: Solid2](x: A): Map[String, Double] = | Map( | "surface area" -> x.surfaceArea, | "volume" -> x.volume, | ) | def solidProps[A](x: A)(using evidence$1: Solid2[A]): Map[String, Double] scala> solidProps(Cuboid2(2, 3, 4)) val res0: Map[String, Double] = Map(surface area -> 52.0, volume -> 24.0) scala> solidProps(Sphere2(2)) val res1: Map[String, Double] = Map(surface area -> 50.26548245743669, volume -> 33.510321638291124) 120
  • 121.
    ;;; Clojure user=> (defprotocolSolid ; プロトコルの定義 (surface-area [this]) (volume [this])) Solid user=> (defrecord Cuboid [a b c]) user.Cuboid user=> (defrecord Sphere [r]) user.Sphere 121
  • 122.
    ;;; Clojure user=> (extend-protocolSolid ; プロトコルの実装 Cuboid (surface-area [{:keys [a b c]}] (* 2 (+ (* a b) (* b c) (* c a)))) (volume [{:keys [a b c]}] (* a b c)) Sphere (surface-area [{:keys [r]}] (* 4 Math/PI (Math/pow r 2))) (volume [{:keys [r]}] (* 4/3 Math/PI (Math/pow r 3)))) nil 122
  • 123.
    ;;; Clojure user=> (defnsolid-props [x] {:surface-area (surface-area x) :volume (volume x)}) #'user/solid-props user=> (solid-props (->Cuboid 2 3 4)) {:surface-area 52, :volume 24} user=> (solid-props (->Sphere 2)) {:surface-area 50.26548245743669, :volume 33.51032163829112} 123
  • 124.
    ## Elixir iex(1)> defprotocolSolid do # プロトコルの定義 ...(1)> def surface_area(x) ...(1)> def volume(x) ...(1)> end {:module, Solid, ...} iex(2)> defmodule Cuboid do ...(2)> defstruct [:a, :b, :c] ...(2)> end {:module, Cuboid, ...} iex(3)> defmodule Sphere do ...(3)> defstruct [:r] ...(3)> end {:module, Sphere, ...} 124
  • 125.
    ## Elixir iex(4)> defimplSolid, for: Cuboid do # プロトコルの実装 ...(4)> def surface_area(%Cuboid{a: a, b: b, c: c}) do ...(4)> 2 * (a * b + b * c + c * a) ...(4)> end ...(4)> def volume(%Cuboid{a: a, b: b, c: c}) do ...(4)> a * b * c ...(4)> end ...(4)> end {:module, Solid.Cuboid, ...} iex(5)> defimpl Solid, for: Sphere do ...(5)> def surface_area(%Sphere{r: r}) do ...(5)> 4 * :math.pi() * r ** 2 ...(5)> end ...(5)> def volume(%Sphere{r: r}) do ...(5)> 4 / 3 * :math.pi() * r ** 3 ...(5)> end ...(5)> end {:module, Solid.Sphere, ...} 125
  • 126.
    ## Elixir iex(6)> solid_props= fn x -> ...(6)> %{ ...(6)> surface_area: Solid.surface_area(x), ...(6)> volume: Solid.volume(x) ...(6)> } ...(6)> end #Function<42.81571850/1 in :erl_eval.expr/6> iex(7)> solid_props.(%Cuboid{a: 2, b: 3, c: 4}) %{surface_area: 52, volume: 24} iex(8)> solid_props.(%Sphere{r: 2}) %{ surface_area: 50.26548245743669, volume: 33.510321638291124 } 126
  • 127.
    パラメータ多相に関する仕組み {- Haskell -} λ>:t id -- 関数idの型(type)は? id :: a -> a -- 型a λ> id 42 -- idを数値に適用したとき 42 it :: Num a => a -- 型クラスNumの制約付きの型a λ> :t Just -- 1引数の値コンストラクタJust Just :: a -> Maybe a λ> :t Just 42 -- Justを数値に適用したとき Just 42 :: Num a => Maybe a λ> :t Nothing -- 0引数の値コンストラクタNothing Nothing :: Maybe a -- cf. 型Maybe aの定義(抜粋) data Maybe a = Nothing | Just a 127
  • 128.
    {- Haskell -} λ>:k Maybe -- 型コンストラクタMaybeのカインド(kind)は? Maybe :: * -> * -- 型レベルでの関数に相当 λ> :k Num a => Maybe a -- Maybeを型aに適用したとき Num a => Maybe a :: * -- 型レベルでの値に相当 λ> :k Functor -- 型クラスFunctor Functor :: (* -> *) -> Constraint -- 型レベルでの高階関数に相当 λ> :k Functor Maybe -- FunctorをMaybeに適用したとき Functor Maybe :: Constraint -- cf. 型クラスFunctorの定義(抜粋) class Functor f where fmap :: (a -> b) -> f a -> f b 128
  • 129.
    型(type) カインド(kind) 値(value): a 型(type): * 関数(function)/値コンスト ラクタ(value constructor): a-> b 型コンストラクタ (type constructor): * -> * 高階関数(higher-order function): (a -> b) -> f a -> f b 高カインド型(higher- kinded type): (* -> *) -> * 129
  • 130.
    /* Scala */ scala>[A] => (x: A) => identity(x) val res0: [A] => (x: A) => A = Lambda/0x00002000015018d8@3... scala> identity(42) val res1: Int = 42 scala> [A] => (x: A) => Some(x) val res2: [A] => (x: A) => Some[A] = Lambda/0x000020000150... scala> val x: Option[Int] = Some(42) // SomeはOptionの派生型 val x: Option[Int] = Some(42) scala> val y: Option[Int] = None // NoneはOptionの派生型 val y: Option[Int] = None scala> val z: Option[Any] = x // 型パラメータAは共変(covariant) val z: Option[Any] = Some(42) // cf. 関数identityの定義(抜粋) def identity[A](x: A): A = x // cf. 型Option[+A]の定義(抜粋) sealed abstract class Option[+A] extends IterableOnce[A] with Product with Serializable final case class Some[+A](value: A) extends Option[A] case object None extends Option[Nothing] 130
  • 131.
    /* Scala */ //型クラスFunctorの定義 scala> trait Functor[F[_]]: | extension [A](x: F[A]) | def fmap[B](f: A => B): F[B] | // defined trait Functor // Optionに対するインスタンスの定義 scala> given Functor[Option] with | extension [A](x: Option[A]) | def fmap[B](f: A => B): Option[B] = | x.map(f) | // defined object given_Functor_Option scala> (x, y) val res3: (Option[Int], Option[Int]) = (Some(42),None) scala> x.fmap(_ + 1) val res4: Option[Int] = Some(43) scala> y.fmap(_ + 1) val res5: Option[Int] = None 131
  • 132.
  • 133.
    重視する もの (values) 式指向 (expression-oriented) 不変性 (immutability) 宣⾔型プログラミング (declarative programming) (型)安全性 ((type) safety) 合成可能性 (composability) 永続性 (persistence) 純粋性 (purity) 参照透過性 (referentialtransparency) 命令型プログラミング (imperative programming) ⽂指向 (statement-oriented) 副作⽤ (side effect) 破壊的更新 (mutation) 可変性 (mutability) ⼊出⼒ (I/O) 133
  • 134.
  • 135.
    おわりに Haskell, Scala, Clojure,Elixirを例に、関数型言語で 楽しめる基本的な旨みについて探ってみた 関数型プログラミングの本質、そこから導かれる不 可欠な要素について考えるきっかけになれば幸い 関数型プログラミング(言語)と を 引き続きぜひご賞味ください🍷 「関数型まつり」 135
  • 136.
    Further Reading 言語横断 : Scala,Erlang, Clojure, Haskell 続編 : Elm, Elixir, Idris (通称: SICP, 魔術師本): サンプルコードはScheme (通称: TAPL): サンプルコードはOCaml (通称: PFDS): サンプルコ ードはStandard ML, Haskell 『7つの言語7つの世界』 Seven More Languages in Seven Weeks 『計算機プログラムの構造と解釈第2版』 『型システム入門プログラミング言語と型の理論』 『純粋関数型データ構造』 136
  • 137.
  • 138.
  • 139.
    Clojure (通称: 孔雀本) 最新版 『プログラミングClojure 第2版』 ProgrammingClojure, Third Edition Getting Clojure Clojure Applied 『関数型デザイン原則、パターン、実践』 139
  • 140.
    Elixir & Erlang (通称:飛行機本) (通称: すごいE 本) 『プログラミングElixir(第2版) 』 『Elixir実践入門』 『プログラミングErlang』 『すごいErlangゆかいに学ぼう!』 140