プログラミング言語 2 関数型プログラミング...
TRANSCRIPT
プログラミング言語 2
関数型プログラミング (OCaml)
Programming Languages 2
Functional Programming (OCaml)
田浦
1 / 132
目次
1 関数型言語とは / What are Functional Languages?
2 OCaml 早見表 / OCaml Cheatsheet
3 関数型言語について / Functional Languages
4 関数型言語の理想と現実 / The Ideal and the Reality ofFunctional Languages
2 / 132
Contents
1 関数型言語とは / What are Functional Languages?
2 OCaml 早見表 / OCaml Cheatsheet
3 関数型言語について / Functional Languages
4 関数型言語の理想と現実 / The Ideal and the Reality ofFunctional Languages
3 / 132
関数型言語とは
. . .というゴタクから入るよりも,まずは使って見るのがその良さを理解するには一番だろう
そこで関数型言語の中でも人気の高いOCamlを実習する
OCamlについて最低限の情報,自習するためのポインタを与え,ドリルを行う
4 / 132
関数型言語とは
. . .というゴタクから入るよりも,まずは使って見るのがその良さを理解するには一番だろう
そこで関数型言語の中でも人気の高いOCamlを実習する
OCamlについて最低限の情報,自習するためのポインタを与え,ドリルを行う
4 / 132
関数型言語とは
. . .というゴタクから入るよりも,まずは使って見るのがその良さを理解するには一番だろう
そこで関数型言語の中でも人気の高いOCamlを実習する
OCamlについて最低限の情報,自習するためのポインタを与え,ドリルを行う
4 / 132
What are Functional Languages?
. . . can be best answered by trying one
learn OCaml, one of the most popular functional languages
exercise OCaml with a minimum information and a pointerto teach yourself
5 / 132
What are Functional Languages?
. . . can be best answered by trying one
learn OCaml, one of the most popular functional languages
exercise OCaml with a minimum information and a pointerto teach yourself
5 / 132
What are Functional Languages?
. . . can be best answered by trying one
learn OCaml, one of the most popular functional languages
exercise OCaml with a minimum information and a pointerto teach yourself
5 / 132
Contents
1 関数型言語とは / What are Functional Languages?
2 OCaml 早見表 / OCaml Cheatsheet
3 関数型言語について / Functional Languages
4 関数型言語の理想と現実 / The Ideal and the Reality ofFunctional Languages
6 / 132
早見表 — トップレベル定義
変数定義�1 # let x = 1 + 2 ;;
2 val x : int = 3
関数定義�1 # let f x = x + 3 ;;
2 val f : int -> int = <fun>
再帰関数定義�1 # let rec fact n = if n = 0 then 1 else n * (fact (n - 1)) ;;
2 val fact : int -> int = <fun>
注:
実は;;は文法の一部ではないocamlインタプリタにプログラムを放り込む区切りJupyter環境 (SHIFT + ENTERで入力)では不要ファイルにプログラムを書く際も不要
7 / 132
Cheatsheet — toplevel definition
variable definition�1 # let x = 1 + 2 ;;
2 val x : int = 3
function definition�1 # let f x = x + 3 ;;
2 val f : int -> int = <fun>
recursive function definition�1 # let rec fact n = if n = 0 then 1 else n * (fact (n - 1)) ;;
2 val fact : int -> int = <fun>
Remarks:
;; is not part of the syntax
it is a delimiter that marks the end of an input
it is not necessary in Jupyter (SHIFT + ENTER marks it)
it is not necessary either when you write programs in a file8 / 132
早見表 — 式について少し
関数定義や適用時の引数はカッコもカンマもなし (f x y ...)
if 式 then 式 else 式
let 変数 = 式 in 式�1 let f x0 y0 x1 y0 =
2 let dx = x0 - x1 in (* 局所定義 *)
3 let dy = y0 - y1 in
4 dx * dx + dy * dy ;;
9 / 132
Cheatsheet — basic expressions
function applications (f x y ...)▶ note: no parens nor commas
if E then E else E
let var = E in E▶ do not be confused with toplevel definition
ex.�1 let f x0 y0 x1 y0 =
2 let dx = x0 - x1 in (* local definition *)
3 let dy = y0 - y1 in
4 dx * dx + dy * dy ;;
10 / 132
早見表 — タプル (組)
タプルリテラル�1 # let trip = 3,4.5,"hello";;
2 val trip : int * float * string = (3, 4.5, "hello")
複数の値を組み合わせたひとつの値を作る簡便な手段
その型は要素の型の組み合わせ (type * type * ...)
11 / 132
Cheatsheet — Tuple
tuple literals�1 # let trip = 3,4.5,"hello";;
2 val trip : int * float * string = (3, 4.5, "hello")
it is a convenient means to combine multiple values into one
the type of a tuple expression is the combination ofcomponent types (type * type * ...)
12 / 132
早見表 — リスト
空リスト:�1 []
リテラル:�1 [ 10; 20; 30; 40 ]}
:: は要素 (x)とリスト (l)を取り, lの先頭に xが追加されたリストを作る�
1 10 :: [ 20; 30; 40 ]�1 10 :: 20 :: 30 :: 40 :: [] ;;
@ で 2つのリストの連結�1 [ 10; 20 ] @ [ 30; 40 ]
13 / 132
Cheatsheet — List
empty list:�1 []
list literals:�1 [ 10; 20; 30; 40 ]
:: takes an element (x) and a list (l) and returns a list thatadds x in front of l�
1 10 :: [ 20; 30; 40 ]�1 10 :: 20 :: 30 :: 40 :: [] ;;
@ concatenates two lists�1 [ 10; 20 ] @ [ 30; 40 ]
14 / 132
早見表 — match
リストに対するマッチの基本形�1 let rec list_length l =
2 match l with
3 [] -> 0 (* 空の場合 *)
4 | a :: r -> 1 + list_length r (* 空じゃない場合 *) ;;
実はもっと柔軟�1 match 式 with
2 [] -> 式 (* 空 *)
3 | [a] -> 式 (* 一要素 *)
4 | a :: b :: r -> 式 (* 二要素以上 *)
などタプルにも使える�
1 match 式 with
2 (x,y,z) -> 式
15 / 132
Cheatsheet — match
the basic form for lists�1 let rec list_length l =
2 match l with
3 [] -> 0 (* empty case *)
4 | a :: r -> 1 + list_length r (* non-empty case *) ;;
it is in fact more flexible; e.g.,�1 match E with
2 [] -> E (* empty *)
3 | [a] -> E (* singleton *)
4 | a :: b :: r -> E (* two or more elements *)
works for tuples too�1 match E with
2 (x,y,z) -> E
16 / 132
早見表 — 式一覧
�1 式 ::= 変数2 | リテラル (* 整数, 浮動小数点数, ’字’, "文字列", true, false, () *)
3 | 式 式 ... (* 関数適用 *)
4 | [ 式; 式; ... ] (* リスト *)
5 | 式, 式, ... (* タプル *)
6 | if 式 then 式 else 式7 | let [rec] 定義 and 定義 and ... in 式8 | match 式 with
9 パターン -> 式10 | パターン -> 式11 | ...
12 | ( 式 )
13
14 定義 ::= 変数 変数 ... = 式
17 / 132
Cheatsheet — Summary of Expressions
�1 E ::= identifier
2 | literals (* integers, floats, ’c’, "string", true, false, () *)
3 | E E ... (* function application *)
4 | [ E; E; ... ] (* list *)
5 | E, E, ... (* tuple *)
6 | if E then E else E7 | let [rec] Def and Def and ... in E8 | match E with
9 Pattern -> E10 | Pattern -> E11 | ...
12 | ( E )
13
14 Def ::= identifier identifier ... = E
18 / 132
早見表 — ライブラリの利用
ライブラリ関数は,モジュール名.関数 で参照. モジュール名は,http://caml.inria.fr/pub/docs/manual-ocaml-4.
01/index.html Part IVを参照.�1 Random.int 30
一部のライブラリは ocaml起動時,コンパイル時にライブラリのファイルを指定する必要あり.�
1 $ ocaml unix.cma
2 Objective Caml version 3.12.1
3
4 # Unix.gettimeofday ();;
5 - : float = 1396802697.034235
Jupyter環境では,最初から組み込まれている以外のものを使うのは難しい (多分)
19 / 132
Cheatsheet — Using Libraries
use “module.function” to reference a function in a module;see Part IV of http://caml.inria.fr/pub/docs/manual-ocaml-4.01/index.html to find available modules�
1 Random.int 30
when using ocaml inerpreter, some libraries require modulefile name(s) in the command line�
1 $ ocaml unix.cma
2 Objective Caml version 3.12.1
3
4 # Unix.gettimeofday ();;
5 - : float = 1396802697.034235
when using Jupyter, it seems difficult to use non builtinfunctions
20 / 132
早見表 — データ型 (1)
要素をとる型�1 type rgb_color = Rgb of (int * int * int) ;;
▶ → Rgb (1,2,3)という式が rgb colorという型を持つようになる
▶ ここで定義された Rgbを (rgb color型の) 「構築子(constructor)」と呼ぶ
▶ 関数のようなものだが関数ではない (“Rgb”単独ではエラー)
複数の構築子を持つ型 (バリアント):�1 type rgb_color = Rgb of (int * int * int)
2 | Rgba of (int * int * int * int)
3 | Black | White | Blue ;;
21 / 132
Cheatsheet — data types (1)
a type can be built from an existing type�1 type rgb_color = Rgb of (int * int * int) ;;
▶ → an expression Rgb (1,2,3) will have a type rgb color▶ Rgb is called a constructor (of rgb color type)
⋆ a constructor name must begin with a capital letter⋆ a variable/type name mustn’t
▶ a constructor looks like a function but it is not (a standalone“Rgb” is not a valid expression)
a type can have multiple constructors (variant):�1 type rgb_color = Rgb of (int * int * int)
2 | Rgba of (int * int * int * int)
3 | Black | White | Blue ;;
22 / 132
早見表 — データ型 (2)
型は型パラメータをとれる:�1 type ’a cell = Cell of ’a ;;
▶ 型パラメータは引用符 (’)で始まる
型は再帰的でも良い�1 type hoge = ... | ... of hoge ...
すべてを組み合わせ, 以下で 2分木が定義できる�1 type ’a bintree = Empty | Node of (’a bintree * ’a bintree);;
一般には,�1 type (型)変数名 (型)変数名 ... 型名 =
2 構築子名 ( of T )?3 | 構築子名 ( of T )?4 | ...
▶ T : 型式 (type expression)23 / 132
Cheatsheet — data types (2)
a type can take type parameters:�1 type ’a cell = Cell of ’a ;;
▶ a type parameter must begin with a quote (’)
a type can be recursive�1 type hoge = ... | ... of hoge ...
all combined, a binary tree type can be defined as follows:�1 type ’a bintree = Empty | Node of (’a bintree * ’a bintree);;
general syntax�1 type identifier identifier ... identifier =
2 constructor ( of T )?3 | constructor ( of T )?4 | ...
▶ T : type expression24 / 132
早見表 — match
matchは type で定義された型にも使える�1 let rec tree_sz t =
2 match t with
3 Empty -> 0
4 | Node (l,r) -> 1 + tree_sz l + tree_sz r ;;
25 / 132
Cheatsheet — match
match can be used for user-defined types. e.g.,�1 let rec tree_sz t =
2 match t with
3 Empty -> 0
4 | Node (l,r) -> 1 + tree_sz l + tree_sz r ;;
26 / 132
Contents
1 関数型言語とは / What are Functional Languages?
2 OCaml 早見表 / OCaml Cheatsheet
3 関数型言語について / Functional Languages
4 関数型言語の理想と現実 / The Ideal and the Reality ofFunctional Languages
27 / 132
関数型言語
ML族▶ Standard ML of New Jersey (SML)▶ SML# (東北大). MLを「普通の言語に」
⋆ 他の言語の相互運用,データベース,スレッド,etc.
▶ Caml Light, Objective Caml (OCaml)▶ F# (Microsoft. OCamlをもとに設計)
Lisp, Scheme (動的型)
Haskell, Miranda (「純粋」関数型.非正則)
28 / 132
Functional Languages
ML family▶ Standard ML of New Jersey (SML)▶ SML# (Tohoku University). making ML “ordinary
languages”⋆ interoperability with other languages, databases,
multithreading, etc.
▶ Caml Light, Objective Caml (OCaml)▶ F# (Microsoft; based on OCaml)
Lisp, Scheme (dynamically typed)
Haskell, Miranda (“purely” functional. non-strict evaluation)
29 / 132
関数型言語,特にMLの特徴・ポイント
「関数型」なるもの:
型宣言なしで再構築
静的な型検査
パラメトリックな多相型
第一級の関数
データ構造の簡潔な定義,生成,パターンマッチ
⇒ 関数の「宣言的」な (≈定義そのものに近い)記述.それによる「間違いにくく,正しさを理解・証明しやすい」プログラム
30 / 132
Salient features of functional languages and esp.
ML family
“Functional” things:
type reconstruction without type declarations
static type checking
parametric polymorphism
first-class functions
concise definition, creation and pattern matching of datastructures
⇒ “declarative” (≈ similar to math) description of functions;programs less prone to errors and easier to reason/prove thecorrectness of
31 / 132
宣言的 vs. 手続き的
例: 以下の数列の第 anを計算する関数を書け{a0 = 1,an = an−1 + n (n = 1, 2, · · · )
宣言的: OCaml:�1 let rec a n =
2 if n = 0 then 1
3 else a (n - 1) + n
もちろんCでも:�1 int a(int n) {
2 if (n == 0) return 1;
3 else return a(n - 1) + n;
4 }
手続き的:�1 int a(int n) {
2 int t = 1;
3 for (int i = 0; i < n; i++) {
4 t = t + (i + 1);
5 }
6 return t;
7 }
32 / 132
Declarative vs. procedural/imperative
Ex: write a function that computes an of the following series{a0 = 1,an = an−1 + n (n = 1, 2, · · · )
declarative: OCaml:�1 let rec a n =
2 if n = 0 then 1
3 else a (n - 1) + n
you can do it in C as well,to be sure:�
1 int a(int n) {
2 if (n == 0) return 1;
3 else return a(n - 1) + n;
4 }
procedural:�1 int a(int n) {
2 int t = 1;
3 for (int i = 0; i < n; i++) {
4 t = t + (i + 1);
5 }
6 return t;
7 }
33 / 132
型宣言なしで再構築 (Type Reconstruction)
入力を含め,プログラマが型を書かなくて良い�1 # let f x = x + 2;;
2 val f : int -> int = <fun>
a -> b は aを受け取り bを返す関数の型(大雑把な)推論過程:
1 x + 2から,(+は intをとるので),xは int2 その場合 x + 2も整数だから,fは int -> int
cf. Cの場合�1 int f(int x) { return x + 2; }
34 / 132
Type reconstruction without type declaration
the programmer does not have to declare types of variables,including those of input parameters�
1 # let f x = x + 2;;
2 val f : int -> int = <fun>
a -> b represents a type of functions taking a and returning b
a (rough) sketch of how it is inferred:1 look at x + 2; as + takes ints, x should be an int2 then x + 2 should be an int, so f is int -> int
cf. in C:�1 int f(int x) { return x + 2; }
35 / 132
用語: 型の再構築・型推論
型の再構築を「型推論 (type inference)」と呼ぶこともある
ただし「型推論」はどんな言語でも行われている,式の型の導出を意味することもある�
1 float f(float * a, int i) { return a[i]; }
▶ aが float*, iが int ⇒ a[i]は float
言葉はさておき,MLの型推論 (型の再構築)は,「関数の入力の型」が型宣言なしで推論されるところが特徴
36 / 132
Terminology: type reconstruction/inference
the above type reconstruction is commonly called “typeinference”
technically, however, it sometimes means a more ordinaryprocess that derives types of compound expressions fromcomponent types�
1 float f(float * a, int i) { return a[i]; }
▶ a is float*, i is int ⇒ a[i] is float
regardless of terminologies, the salient feature of typeinference (reconstruction) in ML is that it infers function’sinput types without type declaration
37 / 132
静的なエラー検査
型エラー,未定義の変数の利用を始めとした様々な間違いは「実行前に」検査されている (そこはC言語と同じ)�
1 # let f x = print_int x ; x + true ;;
2 Characters 28-32:
3 let f x = print_int x ; x + true ;;
4 ^^^^
5 Error: This expression has type bool but an expression was expected of
type
6 int
fが呼ばれてから, x + trueを実行する瞬間にエラーになるのではない (cf. Python)
38 / 132
Static type checking
various errors, such as type errors and use of undefinedvariables, are checked prior to execution (like C languages)�
1 # let f x = x + true ;;
2 Characters 14-18:
3 let f x = x + true ;;
4 ^^^^
5 Error: This expression has type bool but an expression was expected of
type
6 int
note that the error is raised for the definition of f; not forthe execution of x + true (cf. Python)
39 / 132
どのような型があるか (1)
いくつかの「基本型」
int (整数), float (浮動小数点数), bool (真偽), 文字列 (string),文字 (char), unit�
1 # 3;;
2 - : int = 3
3 # 3.8 ;;
4 - : float = 3.8
5 # true;;
6 - : bool = true
7 # "hello";;
8 - : string = "hello"
9 # ’h’;;
10 - : char = ’h’
11 # () ;;
12 - : unit = ()
40 / 132
types (1)
a few primitive types
int (integers), float (floating point numbers), bool(boolean), string, char (characters), unit�
1 # 3;;
2 - : int = 3
3 # 3.8 ;;
4 - : float = 3.8
5 # true;;
6 - : bool = true
7 # "hello";;
8 - : string = "hello"
9 # ’h’;;
10 - : char = ’h’
11 # () ;;
12 - : unit = ()
41 / 132
どのような型があるか (2)
関数型�1 # let f x = x + 1;; (* 関数の定義 *)
2 val f : int -> int = <fun>
3 # fun x -> x + 1 ;; (* 無名関数 *)
4 - : int -> int = <fun>
注: +, -, *, /は整数のみ受け取る.
浮動小数点数は.をつける (+., -., *., /.)�1 # fun x -> x +. 2.3;;
2 - : float -> float = <fun>
3 # fun x -> x + 2.3;;
4 Characters 13-16:
5 fun x -> x + 2.3;;
6 ^^^
7 Error: This expression has type float but an expression was expected of
type
8 int
42 / 132
types (2)
function types�1 # let f x = x + 1;; (* function definition *)
2 val f : int -> int = <fun>
3 # fun x -> x + 1 ;; (* anonymous function *)
4 - : int -> int = <fun>
remark: +, -, *, / takes only integers
operators on floating point numbers have . (+., -., *.,
/.)�1 # fun x -> x +. 2.3;;
2 - : float -> float = <fun>
3 # fun x -> x + 2.3;;
4 Characters 13-16:
5 fun x -> x + 2.3;;
6 ^^^
7 Error: This expression has type float but an expression was expected of
type
8 int
43 / 132
どのような型があるか (3)
* (タプル; 複数の値の組)�1 # 3,4.5,"hello" ;;
2 - : int * float * string = (3, 4.5, "hello")
list (リスト)�1 # [ 1; 2; 3 ];;
2 - : int list = [1; 2; 3]
3 # [ "this"; "is"; "a"; "pen" ];;
4 - : string list = ["this"; "is"; "a"; "pen"]
注: リストの要素は ; で区切る.以下はやりがちな間違い�1 # [ 1, 2, 3 ];;
2 - : (int * int * int) list = [(1, 2, 3)]
44 / 132
types (3)
* (tuple; combination of multiple values)�1 # 3,4.5,"hello" ;;
2 - : int * float * string = (3, 4.5, "hello")
list (list)�1 # [ 1; 2; 3 ];;
2 - : int list = [1; 2; 3]
3 # [ "this"; "is"; "a"; "pen" ];;
4 - : string list = ["this"; "is"; "a"; "pen"]
Remark: delimit elements by ’;’ the following is a commonmistake (still type correct).�
1 # [ 1, 2, 3 ];;
2 - : (int * int * int) list = [(1, 2, 3)]
45 / 132
型エラー (型付けできない式)の例
関数と引数の型の不一致�1 let f x = x + 1
2 in f "hello";;
thenと elseで型が違う if文�1 if 1 < 2 then 10 else "hello"
違う型の値が混ざったリスト�1 [ 10 ; "hello" ]
46 / 132
Expressions that are not typed (type errors)
a function application whose function and argument hasincompatible types�
1 let f x = x + 1
2 in f "hello";;
an if experssion whose then and else has incompatibletypes�
1 if 1 < 2 then 10 else "hello"
a list expression that mixes incompatible types�1 [ 10 ; "hello" ]
47 / 132
多相関数
「色々な値に対して適用できる関数 (多相的な関数;polymorphic function)」を表現する型がある
しかも関数の定義から,関数が多相型を持つことを推論する�1 # let f x = x;;
2 val f : ’a -> ’a = <fun>
3 # let rec len lst = match lst with [] -> 0 | h::r -> 1 + len r ;;
4 val len : ’a list -> int = <fun>
’aは「型パラメータ」と呼ぶ
直感的には「すべての値」ということ
48 / 132
Polymorphic functions
there is a type that represents functions that “apppy tovarious (any) types” (polymorphic functions)
such types are also automatically reconstructed from functiondefinitions�
1 # let f x = x;;
2 val f : ’a -> ’a = <fun>
3 # let rec len lst = match lst with [] -> 0 | h::r -> 1 + len r ;;
4 val len : ’a list -> int = <fun>
’a is called a “type parameter”
intuitively, it represents a type of “any value”
49 / 132
よく使うリスト関係の多相関数
map f l : lの各要素に f を適用したリスト�1 # List.map ;;
2 - : (’a -> ’b) -> ’a list -> ’b list = <fun>
filter p l : l中で pを満たす要素だけのリスト�1 # List.filter ;;
2 - : (’a -> bool) -> ’a list -> ’a list = <fun>
50 / 132
Frequently used polymorphic functions on lists
map f l : apply f to each element of l returns the list of them�1 # List.map ;;
2 - : (’a -> ’b) -> ’a list -> ’b list = <fun>
filter p l : returns the list of elements in l satisfying p�1 # List.filter ;;
2 - : (’a -> bool) -> ’a list -> ’a list = <fun>
see List module for more functions
51 / 132
第一級の関数
関数型言語では,「関数」と「その他の値」を区別せず,同様に扱う
▶ 関数を他の関数に渡せる▶ 関数を関数の返り値にできる▶ 関数をデータ中に入れられる
もっともこれだけではCの関数ポインタでも同じ.本質的な違いは,
▶ どこでも関数が定義できる (生成できる)
52 / 132
First class functions
functional languages generally treat functions and othervalues similarly without distinctions; namely, a function:
▶ can be passed to another function,▶ can be returned from a function,▶ can be put in a data structure, etc.
it is actually the case in C.the essential difference is▶ you can define (create) a function anywhere
53 / 132
どこでも関数が作れる
= トップレベルだけでなく,関数の中でまた関数を定義できる�
1 let f x =
2 let g y = ... (* 関数の中でまた関数を定義 *)
3 in ... ;;
例�1 # let make adder x = (* x 足す関数を返す *)
2 let g y = y + x
3 in g ;;
4 val make_adder : int -> int -> int = <fun>
5 # let a3 = make adder 3 ;; (* 3を足す関数 *)
6 val a3 : int -> int = <fun>
7 # let a4 = make adder 4 ;; (* 4を足す関数 *)
8 val a4 : int -> int = <fun>
9 # a3 10 ;;
10 - : int = 13
11 # a4 10 ;;
12 - : int = 14
54 / 132
Functions can be defined anywhere
= you can define a function not only in the toplevel but alsowithin a function�
1 let f x =
2 let g y = ... (* define a function in another function *)
3 in ... ;;
ex.�1 # let make adder x = (* create a function that adds x *)
2 let g y = y + x
3 in g ;;
4 val make_adder : int -> int -> int = <fun>
5 # let a3 = make adder 3 ;; (* a function that adds 3 *)
6 val a3 : int -> int = <fun>
7 # let a4 = make adder 4 ;; (* a function that adds 4 *)
8 val a4 : int -> int = <fun>
9 # a3 10 ;;
10 - : int = 13
11 # a4 10 ;;
12 - : int = 14
55 / 132
どこでも関数が作れる
あえてC言語風に書けば以下のようなことができるということ�
1 int (*)(int) make_adder(int x) {
2 int g(int y) { return y + x; } /* 関数の中で関数定義 */
3 return g;
4 }
どこでも関数定義が「書ける」だけなら文法をそうすればいいだけのこと
真に大事なのは,中の関数 (g)が,外側の変数 (自由変数; x)を参照でき,しかも,「自分が作られた時の値」をいつまでも覚えているということ
関数=文面+関数定義時の自由変数の値 (closure)
56 / 132
Functions can be defined anywhere
in a hypothetical C-like syntax, it is like you can write thefollowing�
1 int (*)(int) make_adder(int x) {
2 int g(int y) { return y + x; } /* a function definition in a function */
3 return g;
4 }
just allowing you to “write” this function is merely a matterof extending the syntax
what really matters is that the inner function (g) canreference a variable outside of it (free variable; x) and itremembers its value when it is defined
a function = program text + values of free variables at thetime of its definition (closure)
57 / 132
注: 関数の引数は「常に」ひとつ
�1 let f x y = E
は,�1 let f = (fun x -> (fun y -> E))
の略記に過ぎない (「カリー化された関数」). Eの中で xが参照できることの重要性に注意.
実際,f xのように一つだけ引数を与えても良い (部分適用)�1 # let f x y = x + y;;
2 val f : int -> int -> int = <fun>
3 # f 3 ;;
4 - : int -> int = <fun>
58 / 132
Note: a function’s arity is always one
what looks like a function taking two-parameters (x and y):�1 let f x y = E
actually takes a parameter (x) and returns a function thattakes a parametr (y), as if:�
1 let f = (fun x -> (fun y -> E))
note that this is legitimate only when E can reference x (freevariable)
as a matter of fact f x is a valid application (partialapplication)�
1 # let f x y = x + y;;
2 val f : int -> int -> int = <fun>
3 # f 3 ;;
4 - : int -> int = <fun>
the concept is called currying59 / 132
演算子も実は関数
組み込み演算 (a+b, a<bなど)も,実は関数適用
演算記号 (+, <など)をカッコでくくると,演算子の「関数」を取り出せる�
1 # (+);;
2 - : int -> int -> int = <fun>
3 # (=);;
4 - : ’a -> ’a -> bool = <fun>
5 # (<);;
6 - : ’a -> ’a -> bool = <fun>
7 # (<) 3 5 ;; (* 3 < 5 と同じ意味 *)
8 - : bool = true
それらももちろん部分適用できる関数. 例えば,(<) 3は,3 < yを判定する「関数」�
1 # List.filter ((<) 3) [ 1; 2; 3; 4; 5 ];;
2 - : int list = [4; 5]
60 / 132
Operators are in fact functions
builtin operations (a+b, a<b etc.) are in fact functionapplications
put an operator symbol (+, <, etc.) inside parens and you getthe function corresponding to the operator�
1 # (+);;
2 - : int -> int -> int = <fun>
3 # (=);;
4 - : ’a -> ’a -> bool = <fun>
5 # (<);;
6 - : ’a -> ’a -> bool = <fun>
7 # (<) 3 5 ;; (* means 3 < 5 *)
8 - : bool = true
they are functions so are partially applicable. e.g., (<) 3 is afunction that judges if 3 < y�
1 # List.filter ((<) 3) [ 1; 2; 3; 4; 5 ];;
2 - : int list = [4; 5]
61 / 132
データ構造の簡潔な定義 (バリアント)
Cの struct相当�1 # type student = Student of (int * string * float);;
2 type student = Student of (int * string * float)
3 # Student(31489678, "Masakazu Mimura", 169.8);;
4 - : student = Student (31489678, "Masakazu Mimura", 169.8)
Cの union相当 (型は一つ.その作り方が複数)�1 # type staff = Student of (int * string * float)
2 | Teacher of (string * float) ;;
Cの enum相当 (上記の特別な場合)�1 # type color = Blue | Red | Orange | Black | ... ;;
62 / 132
A concise definition of data structure (variant)
A single type construct serves what are distinct constructs in C
like struct in C�1 # type student = Student of (int * string * float);;
2 type student = Student of (int * string * float)
3 # Student(31489678, "Masakazu Mimura", 169.8);;
4 - : student = Student (31489678, "Masakazu Mimura", 169.8)
like union in C (multiple constituent types for a single type)�1 # type staff = Student of (int * string * float)
2 | Teacher of (string * float) ;;
like enum in C (special case of the above)�1 # type color = Blue | Red | Orange | Black | ... ;;
63 / 132
安全なパターンマッチ
Unionのような取り違えの起き得ない文法 (場合分けとフィールドの取り出しを「セットで」行う文法)�
1 # let height x = match x with
2 Student(id, name, h) -> h
3 | Teacher(name, h) -> h;;
4 val height : staff -> float = <fun>
パターンの書き忘れも警告してくれる�1 # let height x = match x with Student(id, name, h) -> h ;;
2 Characters 15-53:
3 let height x = match x with Student(id, name, h) -> h ;;
4 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5 Warning 8: this pattern-matching is not exhaustive.
6 Here is an example of a value that is not matched:
7 Teacher _
8 val height : staff -> float = <fun>
64 / 132
Safe pattern matches
a syntax that safely separates cases and extracts componentfields (you’ll never access wrong fields)�
1 # let height x = match x with
2 Student(id, name, h) -> h
3 | Teacher(name, h) -> h;;
4 val height : staff -> float = <fun>
a warning against missing cases�1 # let height x = match x with Student(id, name, h) -> h ;;
2 Characters 15-53:
3 let height x = match x with Student(id, name, h) -> h ;;
4 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5 Warning 8: this pattern-matching is not exhaustive.
6 Here is an example of a value that is not matched:
7 Teacher _
8 val height : staff -> float = <fun>
65 / 132
matchの略記
�1 let f x = match x with Foo(y) -> ...
で,matchの選択肢がひとつしかない (|がない)場合,上記を�1 let f (Foo(y)) = ...
と書ける
タプルのパターンマッチに使うととりわけ自然�1 let f (x,y) = x + y ;;
実は以下の略記�1 let f t = match t with (x,y) -> x + y ;;
66 / 132
Abbreviations of match expressions
if there is only a single case for a match expression, like:�1 let f x = match x with Foo(y) -> ...
you can write the above as follows�1 let f (Foo(y)) = ...
particularly convenient/natural for pattern-matching tuples.�1 let f (x,y) = x + y ;;
is an abbreviation of:�1 let f t = match t with (x,y) -> x + y ;;
67 / 132
型定義も多相的にできる
自分で明示的に型パラメータ (’a)を書く�1 # type ’a tree = Empty
2 | Node of (’a * ’a tree * ’a tree) ;;
3 type ’a tree = Empty | Node of (’a * ’a tree * ’a tree)
組み込みのリストは,以下のような型だと理解すれば良い (以下は実際には文法エラー)�
1 # type ’a list = [] | :: of (’a * ’a list) ;;
2 # 3 :: 4 :: 5 :: [] ;;
他によく使う型 option は以下のようなもの�1 # type ’a option = None | Some of ’a ;;�1 # None ;;
2 - : ’a option = None
3 # Some 5 ;;
4 - : int option = Some 5
68 / 132
Types can be made polymorphic
specify type parameters (’a)�1 # type ’a tree = Empty
2 | Node of (’a * ’a tree * ’a tree) ;;
3 type ’a tree = Empty | Node of (’a * ’a tree * ’a tree)
the builtin list type can be understood as if it were definedfollows (note: it is not syntactically valid)�
1 # type ’a list = [] | :: of (’a * ’a list) ;;
2 # 3 :: 4 :: 5 :: [] ;;
a frequently used type option is defined as:�1 # type ’a option = None | Some of ’a ;;�1 # None ;;
2 - : ’a option = None
3 # Some 5 ;;
4 - : int option = Some 5
69 / 132
クイックソート
以上をまとめて,クイックソートがこれだけで書けることは嬉しいと思うのだがどうでしょうか?�
1 let rec qs l =
2 match l with
3 [] -> []
4 | h::r ->
5 let smaller = List.filter ((>) h) r in
6 let larger = List.filter ((<=) h) r in
7 (qs smaller) @ h :: (qs larger) ;;
正しいことが理解・証明しやすい (帰納法)
変数の間違いや型エラーをはじいてくれるので,型検査を通れば結構な確率であっさりと動くどんなリストにでも動作するただし実はまだ問題あり(スタックオーバーフロー.また後日)
70 / 132
Quicksort
Isn’t it nice to be able to write a quicksort as concisely as this?�1 let rec qs l =
2 match l with
3 [] -> []
4 | h::r ->
5 let smaller = List.filter ((>) h) r in
6 let larger = List.filter ((<=) h) r in
7 (qs smaller) @ (h :: (qs larger)) ;;
easy to reason about its correctness (induction)
typos and type errors are checked at compile time, so oncetype-checked, it tends to work in the first run
it can work on any list type
disclosure: this version has an issue with large lists (stackoverflow; I’ll get to it later)
71 / 132
Contents
1 関数型言語とは / What are Functional Languages?
2 OCaml 早見表 / OCaml Cheatsheet
3 関数型言語について / Functional Languages
4 関数型言語の理想と現実 / The Ideal and the Reality ofFunctional Languages
72 / 132
関数型言語の理想
OCaml (関数型言語)の利点 (理想)は,再帰呼出しを使って物事を,簡潔に (≈ 定義そのものみたいに) 書けること
例: リストの長さ�1 let rec len lst =
2 match lst with
3 [] -> 0 (* 空リストの長さは 0 *)
4 | h::r -> 1 + (len r) (* [h;...] の長さは ...の長さ+1 *)
例: [a, b)に含まれる整数の和�1 let rec sum a b =
2 if a >= b then
3 0 (* 空区間の和は 0 *)
4 else
5 a + (sum (a+1) b) (* [a,b)の和は a + [a+1,b)の和 *)
73 / 132
Functional languages: the ideal and the reality
The advantage (“ideal”) of OCaml (functional languages) isto be able to write things concisely (≈ as if you are merelywriting definitions)
ex: length of a list�1 let rec len lst =
2 match lst with
3 [] -> 0 (* length of an empty list is 0 *)
4 | h::r -> 1 + (len r) (* length of [h;...] is (length of ...) + 1 *)
ex: sum of integers in [a, b)�1 let rec sum a b =
2 if a >= b then
3 0 (* sum of empty interval: 0 *)
4 else
5 a + (sum (a+1) b) (* sum of [a,b) is a + (sum of [a+1,b)) *)
74 / 132
関数型言語であまり使わないもの
関数型言語の「理想」においては,あまり使わなくていいはずのもの
ループ
変数の更新
データ構造の更新
75 / 132
Not often used in functional languages
Things you should not have to use often in the “ideal” functionalprogramming
loops
(destructive) updates of variables
(destructive) updates of data structures
76 / 132
ループ
for,while文はある�1 for i = a to b do
2 Printf.printf "hello %d\n" i done ;;
しかし再帰を使えばあまり「ループ」したいと思う必要はなかった
▶ sum, range, qs, bs tree, ...
なお一般には,forと同じことが以下のようにしてできる�1 for i = a to b do
2 E3 done
=
�1 let rec loop i =
2 if i <= b then
3 E ; loop (i + 1)
4 in loop a
77 / 132
loops
OCaml does have for and while expressions, to be sure�1 for i = a to b do
2 Printf.printf "hello %d\n" i done ;;
but remember, you already used recursions where you mighthave used loops in other languages. did you look for loops forthem?
▶ sum, range, qs, bs tree, ...
in general, for loops in OCaml can be done with recursionsas follows:�
1 for i = a to b do
2 E3 done
=
�1 let rec loop i =
2 if i <= b then
3 E ; loop (i + 1)
4 in loop a
78 / 132
変数の更新
let x = ... で定義した変数はあとから「更新」できない(immutable).
例えば以下はNG�1 let s = 0 in
2 for i = a to b do
3 s に s + i を代入4 done ;;
for文の有用性がそれほどない理由でもある
更新できる (mutableな)変数もあります (後日)
が,ループ同様,「再帰を使えばいらない」ことが多い
79 / 132
Updating variables
variables defined by let x = ... cannot be updated later(they are immutable).
in particular, you cannot do the following:�1 let s = 0 in
2 for i = a to b do
3 update s with s + i
4 done ;;
one reason why for expression is not that useful
there are updatable (mutable) variables, to be sure (we willget to it later)
the point: you don’t want to use it often once you masterrecursions
80 / 132
Immutableな変数とつきあう
[a, b)の和を求める「ループ」�1 let s = 0 in
2 for i = a to b do
3 s に s + i を代入4 done ;;
. . .を再帰に直したもの�1 let rec loop i s =
2 if i <= b then
3 s
4 else
5 loop (i + 1) (s + i)
6 in loop a 0 ;;
81 / 132
Working with immutable variables
a “loop” that sums up integers in [a, b)�1 let s = 0 in
2 for i = a to b do
3 update s with s + i
4 done ;;
. . . transformed into a recursion:�1 let rec loop i s =
2 if i <= b then
3 s
4 else
5 loop (i + 1) (s + i)
6 in loop a 0 ;;
82 / 132
Immutableな変数とつきあう
変数を更新しながら進行するループは多くの場合,ループ内で変更される関数を引数にとるような,再帰関数に形式的に書き換えられる�
1 x = x0; y = y0; z = z0;
2 for (i = a; i <= b; i++) {
3 E;
4 x = A;
5 y = B;
6 z = C;
7 }
8 return R
≈
�1 let rec loop i x y z =
2 if i = b then R else
3 (E ; loop (i + 1) A B C)
4 in loop a x0 y0 z0 ;;
これを引き出しに入れておくのは良いが,そもそも最初から「再帰」で考えれば,そんな「技法」が必要だとすら感じない場合が多い
▶ 「aから bまでの和」⇒ a+ 「(a+ 1)から bまでの和」
83 / 132
Working with immutable variables
a loop that updates variables can often be transformed into arecursive function that takes these variables as parameters�
1 x = x0; y = y0; z = z0;
2 for (i = a; i <= b; i++) {
3 E;
4 x = A;
5 y = B;
6 z = C;
7 }
8 return R
≈
�1 let rec loop i x y z =
2 if i = b then R else
3 (E ; loop (i + 1) A B C)
4 in loop a x0 y0 z0 ;;
you may remember this as a formula, but more important isyou do not have to master such a technique, if you think withrecursions in the first place. e.g.,
▶ sum of [a, b) ⇒ a + sum of [a+ 1, b)
84 / 132
データ構造の更新
これまで紹介したデータ構造 (リスト, バリアント)もimmutable = 破壊的更新 (destructive update); その場の更新(in-place update)不可能
例えばリストに要素を「追加」することもできない
以下のような感じで,range a b関数を書くのは無理�1 let l = [] in
2 for i = a to b do
3 add i to l
4 done ; l
cf. C++の vector�1 vector<int> l;
2 for (i = a; i < b; i++) {
3 l.push_back(i);
4 }
85 / 132
Updating data structures
data structures we have seen so far (list, tuple, variants) areimmutable too = “destructive” or “in-place” updates are notallowed
you cannot “add” an element to a list; you instead createanother list that has an additional element (with ::)
you cannot write range a b along:�1 let l = [] in
2 for i = a to b do
3 add i to l
4 done ; l
cf. with C++ vector:�1 vector<int> l;
2 for (i = a; i < b; i++) {
3 l.push_back(i);
4 }
86 / 132
破壊的更新なしでのプログラミング
すでにあるデータを変更するではなく,得たいデータを「作る」という考え方が基本
▶ aから bまでのリスト = a :: ((a+ 1)から bまでのリスト)▶ 空リストを作り,それに要素を「追加している」と考える必要がない
データを少しずつ「変更」(例: 木構造へデータを挿入)したい場合,データを文字通り変更する代わりに「変更後のデータを新たに作る」のが基本 (関数的更新; functional update)
87 / 132
Working with immutable data structures
think how to “express” the final data you want, not how tobuild it from scratch
▶ list of [a, b) = a :: (list of [a+ 1, b))▶ you do not think: “create an empty list first; then add [a, b)
to it”
when you slightly “modify” an existing data structure (e.g.,insert an element to a tree), you create a data structure afterthe operation (functional update)
88 / 132
関数的更新: 2分木の例
データ構造�1 type ’a bs_tree =
2 Empty | Node of (’a * ’a bs_tree * ’a bs_tree) ;;
挿入する関数は「挿入後の木を作って返す」関数�1 let rec bs_tree_insert x t =
2 match t with
3 Empty -> Node (x, Empty, Empty)
4 | Node (y, l, r) ->
5 if x < y then
6 Node (y, bs_tree_insert x l, r)
7 else
8 Node (y, l, bs_tree_insert x r);;
y
t
l r
y
t
l r
l'
yt'
以下の bs tree insert x t計算後も tはあくまで元の木�1 let t’ = bs_tree_insert x t
一部分 (上記の青下線部分)は再利用されており,毎回全てが作り直されるわけではないことに注意
89 / 132
Functional updates: a binary search tree
data definition�1 type ’a bs_tree =
2 Empty | Node of (’a * ’a bs_tree * ’a bs_tree) ;;
a function that “inserts” an element creates another tree�1 let rec bs_tree_insert x t =
2 match t with
3 Empty -> Node (x, Empty, Empty)
4 | Node (y, l, r) ->
5 if x < y then
6 Node (y, bs_tree_insert x l, r)
7 else
8 Node (y, l, bs_tree_insert x r)
y
t
l r
y
t
l r
l'
yt'
the variable t below still refers to the original tree afterbs tree insert x t�
1 let t’ = bs_tree_insert x t
note that a part of data structure (the underlined with blueof the above) is reused; the whole data structure is notrebuilt every time 90 / 132
Immutableのどこがいい? (1)
何もかもが immutable ⇒ 同じスコープで同じ式は「同じもの」(参照透明性; referential transparency)
人間が紙の上で計算をする時もそのようにしているはず
コンピュータを習いたての頃,x = x + 1 が意味不明だったという人には,嬉しい性質のはず
x = x + 1の意味をあえて「等式」で理解すると,「次の瞬間の x」と「今の x」+ 1 が等しいということ
参照透明性があれば「さっきの x」「今の x」「明日の x」などという区別は不要
91 / 132
What are good about immutability? (1)
everything is immutable ⇒ the same variable (the sameexpression in the same scope) always refers to the “samething (value)” (referential transparency)
calculations on papers are doing the same
if you were confused by a statement like: x = x + 1 whenyou first learned a procedural language, you will like it
to understand the meaning of x = x + 1 as an “equation”, itis: “x at the next moment” equals to “x at the currentmoment” + 1
with referential transparency, there are no such things as “xas of yesterday”, “x as of today” and “x as of tomorrow”
92 / 132
Immutableのどこがいい? (2)
参照透明性は,ある種の最適化を簡単にする.
たとえば共通部分式は「無条件」で最適化できる.�1 f x + f x = 2 * (f x)
▶ cf. Cでは,(f x)に「副作用 (データの更新や I/O)」が含まれていないかを気にする必要がある
自動的なメモ化 (入力→出力の結果を覚えておく)�1 let rec fib n =
2 if n < 2 then
3 1
4 else
5 fib (n - 1) + fib (n - 2)
数学的な式変形の多くが,プログラム上でも正しくなる
93 / 132
What are good about immutability? (1)
it makes certain optimizatins straightforward
e.g., redundant expressions can be “unconditionally”optimized away�
1 f x + f x = 2 * (f x)
▶ cf. in C, you need to know (f x) does not have a “side effect(data structure updates or I/O)”
automatic memoization (record and reuse “input → output”)relationships)�
1 let rec fib n =
2 if n < 2 then
3 1
4 else
5 fib (n - 1) + fib (n - 2)
≈ mathematical transformations become valid for programstoo
94 / 132
Immutableのどこがいい? (3)
データの更新がないと,並列化可能性 (=並列実行によって計算結果が変わらないこと) の判定が自明になる
以下の (f x)と (g x)は並列に実行可能,(f x)と (h y)はそうではない,etc.�
1 let y = f x in
2 let z = g x in
3 let w = h y in
4 y + z + w
C言語などでこの判定が困難な理由:(f x) の中で,(g x)
に影響のある計算が行われないことを判定するのが困難
95 / 132
What are good about immutability? (1)
immutability makes judging parallelizability (= parallel executionhas an equivalent result to the sequential execution)straightforward
e.g., (f x) and (g x) below can be executed in parallel; (fx) and (h y) cannot ((h y) requires the value of (f x)), etc.�
1 let y = f x in
2 let z = g x in
3 let w = h y in
4 y + z + w
in languages supporting mutable data structures (e.g., C), itis necessary (and difficult) to judge if execution of (f x)
never affects (g x) (e.g., (f x) never updates data structureused by (g x))
96 / 132
Immutableのどこが悪い? (1)
不自然なこともある.問題によってはまさしく「同じ変数や式でもその値が変わること (内部状態の変化)」が表現したいことである (世の中は常に変化している)
▶ 現在時刻▶ 銀行残高▶ 身長,体重▶ etc.
「同じ式は同じ値になる」を守るには,日々刻々変化するものはことごとく,「異なる式」にならなくてはならない�
1 let t0,s1 = gettimeofday s0 in
2 let t1,s2 = gettimeofday s1 in
3 ...
97 / 132
What are bad about immutability? (1)
sometimes it is just unnatural/awkward; some problemsexactly want to have the same variable/expression havevarying values over time (the world is changing)
▶ current time▶ the balance in bank accounts▶ height, weight, etc. of a human▶ etc.
to maintain referential transparency, different values must beexpressed by different expressions�
1 let t0,s1 = gettimeofday s0 in
2 let t1,s2 = gettimeofday s1 in
3 ...
98 / 132
Immutableのどこが悪い? (2)
効率が悪い:「データを少し書き換える」だけの操作が,結構な量のメモリ割り当てになりがち
14
12 88
47 93
46 70
72
78
99
tinsert 68 t 14
12 88
47 93
46 70
72
78
99
68
tinsert 68 t
14
12 88
47 93
46 70
72
78
9970
68
47
88
14
tt'=insert 68 t
元の木 ⇒ 破壊的更新 関数的更新
→ メモリの割り当て・解放を激しく行う.その性能に強く依存する
99 / 132
What are bad about immutability? (2)
it is inefficient; a “slight modification to data” tends toallocate a fair amount of memory
14
12 88
47 93
46 70
72
78
99
tinsert 68 t 14
12 88
47 93
46 70
72
78
99
68
tinsert 68 t
14
12 88
47 93
46 70
72
78
9970
68
47
88
14
tt'=insert 68 t
the original tree ⇒ in-place update functional update
→ performs intensive memory allocation/deallocation andrelies on their performance
100 / 132
関数的更新:様々なケースリストの先頭に追加 リストの連結(let v’ = n::v) (let c = u @ v)
a ::
b ::
c ::
d ::
e ::
n::
v
v' = n :: v
a ::
b ::
c ::
d ::
e ::
p::
q::
a ::
b ::
c ::
d ::
e ::
u v
c=u @ v
配列の一要素更新 (どうしてもというなら. . . )
let a’ = update a i x
aa'=update a i x
101 / 132
Functional updates: vaious cases
add an element to the head concatenate two lists(let v’ = n::v) (let c = u @ v)
a ::
b ::
c ::
d ::
e ::
n::
v
v' = n :: v
a ::
b ::
c ::
d ::
e ::
p::
q::
a ::
b ::
c ::
d ::
e ::
u v
c=u @ v
update an element ofan array (if you insist . . . )
let a’ = update a i x
aa'=update a i x
102 / 132
関数的更新:様々なケースのコスト
各セルは,「読み出しコスト/書き込み (+メモリ割り当て)コスト」
d : 木 (t)の深さ
n : 配列の大きさ
破壊的 関数的木へ挿入 insert x t O(d)/O(1) O(d)/O(d)リストの先頭に追加 n::v O(1)/O(1) O(1)/O(1)リストの連結 u@v O(|u|)/O(1) O(|u|)/O(|u|)配列の要素更新 [i] O(1)/O(1) O(n)/O(n)
103 / 132
Functional updates: cost
each cell represents the cost of read / write (+ allocation)
d : depth of a tree (t)
n : size of an array
in-place functional
insert to tree insert x t O(d)/O(1) O(d)/O(d)add to the head of list n::v O(1)/O(1) O(1)/O(1)concat two lists u@v O(|u|)/O(1) O(|u|)/O(|u|)update an array element [i] O(1)/O(1) O(n)/O(n)
104 / 132
Immutableのどこが悪い? (3)
並列性の判定をしやすいからといって,役に立つ並列性を抽出しやすいとは限らない
例:木構造への並列挿入�1 let t1 = insert x0 t0 in
2 let t2 = insert x1 t1 in
3 let t3 = insert x2 t2 in
4 ...
▶ 単純には全部逐次にやるしかない (t0 → t1 → t2 → · · · という依存関係)
▶ 直接更新する場合,木の異なる部分を同時に更新して良い
105 / 132
What are bad about immutability? (3)
being able to judge parallelizability easily does not meanbeing able to parallelize easily
e.g., insert many elements to tree�1 let t1 = insert x0 t0 in
2 let t2 = insert x1 t1 in
3 let t3 = insert x2 t2 in
4 ...
▶ dependencies: t0 → t1 → t2 → · · · precludes parallelizingthese insertions
▶ insertions to different parts of a tree could actually be donein parallel
106 / 132
というわけで. . .
Imperative
はじめました
ごく一部の「純粋」関数型言語 (Haskell, Mirandaなど)を除くと,状態の更新をともなう操作を許している
▶ array : 配列.更新可能.▶ ref : ≈ 一要素の配列.後から変更できる変数の代わり▶ record : フィールドに「更新可能 (mutable)」という属性を指定可能
「参照の透明性」よりも,型推論,静的型検査,パターンマッチなどの,実利を目指す
107 / 132
After all . . .
Imperative
はじめました
functional languages except few “purely functional”languages (Haskell, Miranda, etc.) allow updating states
▶ arrays are mutable▶ ref : ≈ a singleton array. can be used as a mutable variable▶ record : you can label a field with mutable attribute
abandon “referential transparency” but retain practicalbenefits from declarative programming, type inferance, statictype checking, pattern matching, etc.
108 / 132
もうひとつの冷める現実
大きな区間の和を求めてみると. . .�1 # sum 0 (1000 * 100);;
2 - : int = 4999950000
3 # sum 0 (1000 * 1000);;
4 Stack overflow during evaluation (looping recursion?).
理由はなんとなく想像できるでしょう
�1 let rec sum a b =
2 if a >= b then
3 0
4 else
5 a + (sum (a+1) b)
sum 0 1000000
109 / 132
もうひとつの冷める現実
大きな区間の和を求めてみると. . .�1 # sum 0 (1000 * 100);;
2 - : int = 4999950000
3 # sum 0 (1000 * 1000);;
4 Stack overflow during evaluation (looping recursion?).
理由はなんとなく想像できるでしょう
�1 let rec sum a b =
2 if a >= b then
3 0
4 else
5 a + (sum (a+1) b)
sum 0 1000000
sum 1 1000000
109 / 132
もうひとつの冷める現実
大きな区間の和を求めてみると. . .�1 # sum 0 (1000 * 100);;
2 - : int = 4999950000
3 # sum 0 (1000 * 1000);;
4 Stack overflow during evaluation (looping recursion?).
理由はなんとなく想像できるでしょう
�1 let rec sum a b =
2 if a >= b then
3 0
4 else
5 a + (sum (a+1) b)
sum 0 1000000
sum 1 1000000
sum 2 1000000
109 / 132
もうひとつの冷める現実
大きな区間の和を求めてみると. . .�1 # sum 0 (1000 * 100);;
2 - : int = 4999950000
3 # sum 0 (1000 * 1000);;
4 Stack overflow during evaluation (looping recursion?).
理由はなんとなく想像できるでしょう
�1 let rec sum a b =
2 if a >= b then
3 0
4 else
5 a + (sum (a+1) b)
sum 0 1000000
sum 1 1000000
sum 2 1000000
sum 999999 1000000
sum 1000000 1000000
...
109 / 132
One more “inconvenient” truth
let’s find the sum of very large interval . . .�1 # sum 0 (1000 * 100);;
2 - : int = 4999950000
3 # sum 0 (1000 * 1000);;
4 Stack overflow during evaluation (looping recursion?).
you can imagine why it happens . . .
�1 let rec sum a b =
2 if a >= b then
3 0
4 else
5 a + (sum (a+1) b)
sum 0 1000000
110 / 132
One more “inconvenient” truth
let’s find the sum of very large interval . . .�1 # sum 0 (1000 * 100);;
2 - : int = 4999950000
3 # sum 0 (1000 * 1000);;
4 Stack overflow during evaluation (looping recursion?).
you can imagine why it happens . . .
�1 let rec sum a b =
2 if a >= b then
3 0
4 else
5 a + (sum (a+1) b)
sum 0 1000000
sum 1 1000000
110 / 132
One more “inconvenient” truth
let’s find the sum of very large interval . . .�1 # sum 0 (1000 * 100);;
2 - : int = 4999950000
3 # sum 0 (1000 * 1000);;
4 Stack overflow during evaluation (looping recursion?).
you can imagine why it happens . . .
�1 let rec sum a b =
2 if a >= b then
3 0
4 else
5 a + (sum (a+1) b)
sum 0 1000000
sum 1 1000000
sum 2 1000000
110 / 132
One more “inconvenient” truth
let’s find the sum of very large interval . . .�1 # sum 0 (1000 * 100);;
2 - : int = 4999950000
3 # sum 0 (1000 * 1000);;
4 Stack overflow during evaluation (looping recursion?).
you can imagine why it happens . . .
�1 let rec sum a b =
2 if a >= b then
3 0
4 else
5 a + (sum (a+1) b)
sum 0 1000000
sum 1 1000000
sum 2 1000000
sum 999999 1000000
sum 1000000 1000000
...
110 / 132
スタックオーバーフロー
関数呼び出しをしようとすると,その関数実行の (変数の値とかを保持する)ための領域が必要になる
その領域は通常「スタック」と呼ばれる固定サイズの領域から割り当てられている
→ 深い再帰はスタックオーバーフローしかし,何でもかんでも再帰で書く関数型言語にとっては致命的
Cで sum(a, b)を再帰で書く人は (あまり)いない�
1 int sum(int a, int b) {
2 int s = 0;
3 for (i = a; i < b; i++) s += i;
4 return s; }
sum 0 1000000
sum 1 1000000
sum 2 1000000
sum 999999 1000000
sum 1000000 1000000
...
111 / 132
Stack overflow
a functionl call requires a space (forholding values of variables orexpressions)
the space is obtained from a regioncalled “stack”, which is normally offixed size and cannot grow indefinitely(even if memory is otherwiseabundant)
→ deep recursions cause a stackoverflow
critical for functional languages,which encourage recursions
less critical for C, in whichprogrammers do not want to writesomething like sum(a, b) withrecursions anyway�
1 int sum(int a, int b) {
2 int s = 0;
3 for (i = a; i < b; i++) s += i;
4 return s; }
sum 0 1000000
sum 1 1000000
sum 2 1000000
sum 999999 1000000
sum 1000000 1000000
...
112 / 132
スタックオーバーフローに関する色々な言語・実装のスタンス
スタックオーバーフローは本来,「不可避」というような問題ではない多くの言語の実装で,関数呼び出しのためのメモリを特別な固定長の領域 (スタック)から取るために生ずる,「実装上の」問題
▶ ほとんどの言語: 深い再帰をやる君がいけない.ループを使え▶ 一部の関数言語実装 (例: SML/NJ): 関数呼び出しのメモリをヒープからとり,メモリが溢れない限りいくらでも深い呼び出しができる.「スタック」オーバーフローと無縁
▶ OCaml: 関数型だが,スタックオーバーフローする.といってループは不便.さてどうする?
113 / 132
Positions of various languages/implementations
against stack overflows
the problem is not inevitable problem
it is a ramification of an implementation (memorymanagement) strategy, which obtains memory for functioncalls from the stack (of a fixed size)
▶ most languages: you should avoid deep recursions; use loopsinstead
▶ a few functional languages (e.g., SML/NJ): abandon stacksaltogether; get memory for function calls from heap. there isno such thing as stack overflow. “overflow” occurs only whenyou run out of the heap (≈ whole memory)
▶ OCaml: it is a functional language, yet prone to stackoverflow (somewhat irresponsible implementation).
▶ what should OCaml programmers do?
114 / 132
末尾呼び出しという「特別な」呼び出し
OCamlでのプログラム上の工夫として,「末尾呼び出し」という特別な関数呼び出しがある
試しに sum 関数の a + をなくしてみる�1 let rec sum a b =
2 if a >= b then
3 0
4 else
5 a + (sum (a+1) b)
→
�1 let rec sum’ a b =
2 if a >= b then
3 0
4 else
5 sum’ (a+1) b
するとどうでしょう�1 # sum’ 0 1000000;;
2 - : int = 0
もちろんこれは正しくないが,ともかくスタックオーバーフローはしない
115 / 132
Tail calls
some function calls are called “tail calls” and do not requireextra space
for example, let’s see what happens if we remove a + in thesum function�
1 let rec sum a b =
2 if a >= b then
3 0
4 else
5 a + (sum (a+1) b)
→
�1 let rec sum’ a b =
2 if a >= b then
3 0
4 else
5 sum’ (a+1) b
then . . .�1 # sum’ 0 1000000;;
2 - : int = 0
it’s not a correct program, of course, but at least does notcause a stack overflow
116 / 132
末尾呼び出し (tail call)
f が gを呼び出す時 gの結果が「そのまま」f の結果になるような位置にある関数呼び出しを,「末尾呼び出し」という以下の青は末尾呼び出し,赤は違う�
1 let f x =
2 if ... then
3 g (x - 1)
4 else if ... then
5 1 + g (x - 2)
6 else
7 let y = g (x - 3) in
8 g (y - 4)
特に,末尾呼び出しであるような再帰呼出しを,「末尾再帰呼び出し (tail recursive call, tail recursion)」という�
1 let rec sum a b =
2 if a >= b then
3 0
4 else
5 a + (sum (a+1) b)
�1 let rec sum’ a b =
2 if a >= b then
3 0
4 else
5 sum’ (a+1) b117 / 132
Tail calls
when f calls g at such a position that the result of g becomesthe result of f , it is called a “tail call”below, function calls colored blue are tail calls; red are not�
1 let f x =
2 if ... then
3 g (x - 1)
4 else if ... then
5 1 + g (x - 2)
6 else
7 let y = g (x - 3) in
8 g (y - 4)
in particular, a tail call that is also a recursive call is called“a tail recursive call” or simply “a tail recursion”�
1 let rec sum a b =
2 if a >= b then
3 0
4 else
5 a + (sum (a+1) b)
�1 let rec sum’ a b =
2 if a >= b then
3 0
4 else
5 sum’ (a+1) b
118 / 132
sumがオーバーフローする「深い」理由
関数呼出し時,実際にメモリに記録しておく必要があるのは「その呼び出しの返り値を受け取ったあと何をするか」という指示
sum の場合,「a+ 返り値」という「計算」
「呼び出しの返り値」をそのまま返すだけ (つまり末尾呼び出し)だったら,メモリはいらない�
1 let rec sum’ a b =
2 if a >= b then
3 0
4 else
5 sum’ (a+1) b
sum 0 1000000 0 + <返り値>
sum 1 1000000 1 + <返り値>
sum 2 1000000 2 + <返り値>
sum 999999 1000000 999999 + <返り値>
sum 1000000 1000000
...
sum
119 / 132
sumがオーバーフローする「深い」理由
関数呼出し時,実際にメモリに記録しておく必要があるのは「その呼び出しの返り値を受け取ったあと何をするか」という指示
sum の場合,「a+ 返り値」という「計算」
「呼び出しの返り値」をそのまま返すだけ (つまり末尾呼び出し)だったら,メモリはいらない�
1 let rec sum’ a b =
2 if a >= b then
3 0
4 else
5 sum’ (a+1) b
sum 0 1000000 <返り値>
sum'
119 / 132
sumがオーバーフローする「深い」理由
関数呼出し時,実際にメモリに記録しておく必要があるのは「その呼び出しの返り値を受け取ったあと何をするか」という指示
sum の場合,「a+ 返り値」という「計算」
「呼び出しの返り値」をそのまま返すだけ (つまり末尾呼び出し)だったら,メモリはいらない�
1 let rec sum’ a b =
2 if a >= b then
3 0
4 else
5 sum’ (a+1) b
sum 1 1000000 <返り値>
sum'
119 / 132
sumがオーバーフローする「深い」理由
関数呼出し時,実際にメモリに記録しておく必要があるのは「その呼び出しの返り値を受け取ったあと何をするか」という指示
sum の場合,「a+ 返り値」という「計算」
「呼び出しの返り値」をそのまま返すだけ (つまり末尾呼び出し)だったら,メモリはいらない�
1 let rec sum’ a b =
2 if a >= b then
3 0
4 else
5 sum’ (a+1) b
sum 2 1000000 <返り値>
sum'
119 / 132
sumがオーバーフローする「深い」理由
関数呼出し時,実際にメモリに記録しておく必要があるのは「その呼び出しの返り値を受け取ったあと何をするか」という指示
sum の場合,「a+ 返り値」という「計算」
「呼び出しの返り値」をそのまま返すだけ (つまり末尾呼び出し)だったら,メモリはいらない�
1 let rec sum’ a b =
2 if a >= b then
3 0
4 else
5 sum’ (a+1) b
sum 999999 1000000 <返り値>
sum'
119 / 132
sumがオーバーフローする「深い」理由
関数呼出し時,実際にメモリに記録しておく必要があるのは「その呼び出しの返り値を受け取ったあと何をするか」という指示
sum の場合,「a+ 返り値」という「計算」
「呼び出しの返り値」をそのまま返すだけ (つまり末尾呼び出し)だったら,メモリはいらない�
1 let rec sum’ a b =
2 if a >= b then
3 0
4 else
5 sum’ (a+1) b
sum 1000000 1000000 <返り値>
sum'
119 / 132
A “deeper” reason that deep recursions of sum
overflow
when you call a function, what trulyneeds to be recorded in memory isinformation about “what to do with thereturn value”
in the case of sum, it is “a+ (the returnvalue)”
if the return value of the call becomes thereturn value of the caller (i.e., it is a tailcall), you don’t need memory�
1 let rec sum’ a b =
2 if a >= b then
3 0
4 else
5 sum’ (a+1) b
sum 0 1000000 0 + <返り値>
sum 1 1000000 1 + <返り値>
sum 2 1000000 2 + <返り値>
sum 999999 1000000 999999 + <返り値>
sum 1000000 1000000
...
sum
120 / 132
A “deeper” reason that deep recursions of sum
overflow
when you call a function, what trulyneeds to be recorded in memory isinformation about “what to do with thereturn value”
in the case of sum, it is “a+ (the returnvalue)”
if the return value of the call becomes thereturn value of the caller (i.e., it is a tailcall), you don’t need memory�
1 let rec sum’ a b =
2 if a >= b then
3 0
4 else
5 sum’ (a+1) b
sum 0 1000000 <返り値>
sum'
120 / 132
A “deeper” reason that deep recursions of sum
overflow
when you call a function, what trulyneeds to be recorded in memory isinformation about “what to do with thereturn value”
in the case of sum, it is “a+ (the returnvalue)”
if the return value of the call becomes thereturn value of the caller (i.e., it is a tailcall), you don’t need memory�
1 let rec sum’ a b =
2 if a >= b then
3 0
4 else
5 sum’ (a+1) b
sum 1 1000000 <返り値>
sum'
120 / 132
A “deeper” reason that deep recursions of sum
overflow
when you call a function, what trulyneeds to be recorded in memory isinformation about “what to do with thereturn value”
in the case of sum, it is “a+ (the returnvalue)”
if the return value of the call becomes thereturn value of the caller (i.e., it is a tailcall), you don’t need memory�
1 let rec sum’ a b =
2 if a >= b then
3 0
4 else
5 sum’ (a+1) b
sum 2 1000000 <返り値>
sum'
120 / 132
A “deeper” reason that deep recursions of sum
overflow
when you call a function, what trulyneeds to be recorded in memory isinformation about “what to do with thereturn value”
in the case of sum, it is “a+ (the returnvalue)”
if the return value of the call becomes thereturn value of the caller (i.e., it is a tailcall), you don’t need memory�
1 let rec sum’ a b =
2 if a >= b then
3 0
4 else
5 sum’ (a+1) b
sum 999999 1000000 <返り値>
sum'
120 / 132
A “deeper” reason that deep recursions of sum
overflow
when you call a function, what trulyneeds to be recorded in memory isinformation about “what to do with thereturn value”
in the case of sum, it is “a+ (the returnvalue)”
if the return value of the call becomes thereturn value of the caller (i.e., it is a tailcall), you don’t need memory�
1 let rec sum’ a b =
2 if a >= b then
3 0
4 else
5 sum’ (a+1) b
sum 1000000 1000000 <返り値>
sum'
120 / 132
スタックオーバーフローを避けるには
深い再帰は「末尾再帰で」
sum a bの場合:�1 let sum a b =
2 (* sum tail a b s = s + [a,b) の和 *)
3 let rec sum_tail a b s =
4 if a >= b then
5 s
6 else
7 sum_tail a b (s + a)
8 in sum tail a b 0
万能変換公式は (実はあるのだが)汚くなるのであまり期待しない方が良い.例で慣れた方が良い
役立つ経験則: 余分な引数 (上記の s)を受け取る関数を作るとうまく行くことが多い
121 / 132
To avoid stack overflows . . .
deep recursions must be tail recursions
for sum a b . . .�1 let sum a b =
2 (* sum tail a b s = s + [a,b) の和 *)
3 let rec sum_tail a b s =
4 if a >= b then
5 s
6 else
7 sum_tail a b (s + a)
8 in sum tail a b 0
there is a formula to get rid of all non-tail calls, but you willlive without it (learn from examples)
a good heuristic: consider adding extra parameter (s above)to the function
122 / 132
末尾再帰への書き換え練習 — リストを作る (1)
random ints�1 let rec random_ints a n =
2 if n <= 0 then
3 []
4 else
5 (Random.int a) :: (random_ints (n - 1))
→�1 let random ints a n =
2 (* random ints tail a n l = n 要素の乱数 @ l *)
3 let rec random_ints_tail a n l =
4 if n <= 0 then
5 l
6 else
7 random_ints_tail a (n - 1) ((Random.int a)::l)
8 in random ints tail a n []
注: 結果の並び方が逆になる (が,乱数だから気にしていない)
123 / 132
Practicing tail recursions — make a list (1)
random ints (random integers)�1 let rec random_ints a n =
2 if n <= 0 then
3 []
4 else
5 (Random.int a) :: (random ints (n - 1))
→�1 let random ints a n =
2 (* random ints tail a n l = n 要素の乱数 @ l *)
3 let rec random_ints_tail a n l =
4 if n <= 0 then
5 l
6 else
7 random_ints_tail a (n - 1) ((Random.int a)::l)
8 in random ints tail a n []
Remark: the latter stores the result in the opposite order(but we ignore that as they are random numbers anyway)
124 / 132
末尾再帰への書き換え練習 — リストを作る (2)
range�1 let rec range a b =
2 if a >= b then
3 []
4 else
5 a :: (range (a + 1) b)
→�1 let range a b =
2 (* range tail a b l = [a,b) のリスト @ l *)
3 let rec range_tail a b l =
4 if a >= b then
5 l
6 else
7 range_tail a (b - 1) ((b-1)::l)
8 in range tail a b []
今度は結果の順番を意識して,aを増やすのではなく,bを減らす再帰に切り替えた
125 / 132
Practicing tail recursions — make a list (2)
range�1 let rec range a b =
2 if a >= b then
3 []
4 else
5 a :: (range (a + 1) b)
→�1 let range a b =
2 (* range tail a b l = [a,b) のリスト @ l *)
3 let rec range_tail a b l =
4 if a >= b then
5 l
6 else
7 range_tail a (b - 1) ((b-1)::l)
8 in range tail a b []
the order matters this time, so the tail recursive versiondecrements b rather than incrementing a
126 / 132
末尾再帰への書き換え練習 — リストからリストを作る
map�1 let rec map f lst =
2 match lst with
3 [] -> []
4 | x::xs -> (f x) :: (map f xs)
これをそのまま末尾呼び出しにするのは難しい.こんなことができればいいのだが . . .�
1 let rec map_tail f lst l =
2 match lst with
3 [] -> []
4 | all but last @ [last]
5 -> map_tail f all_but_last ((f last)::m)
入力は「前から」しか見れない.だが結果は「後ろから」計算したい. . .頻出パターン: 一旦逆順に計算して最後にひっくり返す
127 / 132
Practicing tail recursions — a list from a list
map�1 let rec map f lst =
2 match lst with
3 [] -> []
4 | x::xs -> (f x) :: (map f xs)
it is difficult to make it tail recursive directly. we wish wecould do something like this . . .�
1 let rec map_tail f lst l =
2 match lst with
3 [] -> []
4 | all but last @ [last]
5 -> map_tail f all_but_last ((f last)::m)
we can only process the input from “the head to the end”,but the output should be computed from “the end to thehead” . . .frequent pattern: get the list in the opposite order and reverseit
128 / 132
末尾再帰への書き換え練習 — リストからリストを作る
一旦逆順に計算して最後にひっくり返す�1 let map f lst =
2 (* map rev f x l = [ f xn−1; · · · ; f x0 ] @ l *)
3 let rec map_rev f lst l =
4 match lst with
5 [] -> []
6 | x::xs -> map_rev f xs ((f x)::l)
7 in rev (map rev f lst [])
revは末尾再帰だけで書ける? → 書けます�1 let rev lst =
2 (* rev tail x l = [ xn−1; · · · ; x0 ] @ l *)
3 let rec rev_tail lst l =
4 match lst with
5 [] -> l
6 | x::xs -> rev_tail xs (x::l)
7 in rev tail lst []
129 / 132
Practicing tail recursions — a list from a list
compute the list in the opposite order and reverse it�1 let map f lst =
2 (* map rev f x l = [ f xn−1; · · · ; f x0 ] @ l *)
3 let rec map_rev f lst l =
4 match lst with
5 [] -> []
6 | x::xs -> map_rev f xs ((f x)::l)
7 in rev (map rev f lst [])
can we write rev with a tail recursion? → yes�1 let rev lst =
2 (* rev tail x l = [ xn−1; · · · ; x0 ] @ l *)
3 let rec rev_tail lst l =
4 match lst with
5 [] -> l
6 | x::xs -> rev_tail xs (x::l)
7 in rev tail lst []
130 / 132
関数型なもののまとめ
それが自然である場合は,再帰関数・functional update で,アルゴリズムを「宣言的に」記述
クロージャ(自由変数を含んだ関数を作れる)
map, filterなど多相的な関数で,アルゴリズムを簡潔に記述
静的型検査,型推論,多相型
実は「関数型」と呼ばれない言語にも取り入れられている. 例えばC++
ラムダ式
Standard Template Library
template
131 / 132
Summary : functional programming
write algorithms declaratively where it is natural to do so,with recursions and functional updates
closure (functions with free variables)
write algorithms concisely with generic functions such as mapand filter
static type checking, type reconstructions and polymorphism
they are incorporated into non functional languages. e.g., C++
lambdas
Standard Template Library
template
132 / 132