Upload
nnwww
View
2.089
Download
0
Embed Size (px)
Citation preview
関数型 プログラミング入門
的な
20min-talk Nth TUTLT 18/12/2015
105 Pages!
誰だ俺は
• じょんどろ @_Nnwww
• Linux班/Tech Lab班
• 計算機科学を学びたいと思っていたらOCamlに入門していた
• サンタさん知能くれ2
そもそも アンケート
3
関数型〇〇 聞いたことある人 ?
4
使って(いる|いた)人?
5
アンケートで分岐
• 少ない
→人間でもわかる関数型プログラミング入門
• 多い
→幽霊型と一般化代数的データ型入門(アドリヴ)
6
Caution!
7
間違っていたら ツッコミ下さい
8
関数型 プログラミング入門
人間でもわかる
20min-talk Nth TUTLT 18/12/2015
39 Pages!
たまに聞こえる『関数型プログラミング怖い』
10
「とりあえず怖いと言っとけ」
イクナイ (・△・) !
11
考え方に共感できれば 分かりやすいし実際便利
12
間違って理解しないために 大事なこと
13
積極的に信頼できる 専門家の言葉を借りよう !
14
引用について
[「関数型言語」に関するFAQ形式の一般的説明 ]
より引用(以降、[ ]はリンクです)
15
以降ダブルクォートは引用
16
関数型言語とは ?
17
関数型言語とは
“厳密には関数型プログラミング言語(functional programming language)の略で、
「関数型プログラミングを推奨・支援する
プログラミング言語」のこと”
• 関数型言語の厳密な境界があるわけではない !
18
関数型プログラミングとは ?
19
関数型プログラミング(FP)とは
“副作用をできるだけ (あるいは全く) 用いず、
式や関数で計算を表すプログラミングスタイル”
“副作用をまったく用いない関数型プログラム
ないし関数型言語を純粋(pure)、
そうでないものを非純粋(impure)と言います”
20
関数型プログラミング(FP)とは
ある式eを関数fに適用しf(e)という式を作る
それを更に関数gに適用しg(f(e))という式へ…
小さな組み合わせから始まり
目的のプログラムを構築するプログラミング
21
副作用とは ?
22
副作用とは
“「式の値を計算して求める」(評価(evaluation)と言います) 以外の動作”
e.g.) 標準出力の書き込み 大域変数への再代入
・ 副作用が増えるほど不透明なプログラムに23
参照透明性
“副作用がない式は、必然的に、評価する (=値を計算する) と常に同じ結果になります。
これを参照透明性(referential transparency)
と言います”
• 参照透明な関数の場合、引数に依って結果が一意に定まる
24
関数型プログラミング のメリットは ?
25
副作用が少ないと…
• 各式間の独立性が高まる
▶組み合わせによる再利用性と柔軟性向上
▶副作用が多い場合と比較してテストや検証が行い易い
26
参照透明であれば…
• 式内の変数に対する破壊的代入が存在しない
▶実行時に変更された変数が想定と異なって発生するエラーの抑制
▶副作用が無いため状況の再現も容易
27
オブジェクト指向 プログラミング(OOP)と対立するの ?
28
補足: OOPの潮流
• アラン・ケイがSmalltalkで提唱したOOP
(基本の処理単位でもメッセージ式が登場)
“動的なクラス・オブジェクト”
• ストラウストラップがC++で提唱したOOP
“静的な抽象データ型としてのクラス”
黒曜さんのAdC[オブジェクト指向プログラミングとは結局なんなのか]より29
今回のOOPは処理・データをまとめる単位のレイヤを指す
30
FPとOOPは対立しない
“メジャーなオブジェクト指向言語が命令型言語ベースのため、オブジェクト指向=命令型という誤解もよくありますが、データとそれに対する操作 (メソッド) のまとまり (オブジェクト) を基本にシステムを構築するオブジェクト指向と、「副作用を用いない」関数型プログラミングとは、直交・独立な概念です”
31
FPとOOPは対立しない
• (関数型|手続き型)プログラミングはプログラムをどのように記述するかが焦点
• ここでのオブジェクト指向プログラミングはデータと操作をどのように構造化するかが焦点
• 注目している構成単位が異なる(言い換えれば議論しているレイヤが異なる)
32
FPとOOPは対立しない
• FP上でOOPを実現している例は幾つもある
• FPではオブジェクト以外の(構造化|抽象化)の手法も存在
▶比較するならそこ
▶今回はそこまで踏み込まない33
その他諸々 誤解編
34
すごいHの影響
• “遅延評価と純粋・非純粋は直交(独立)な概念であることに注意してください”
• モナドは主に副作用を扱いやすくする等の
目的で使われる事があるが、
「モナドを使うこと→FP」ではない
35
こういう話をする時は それ言語-specificな話じゃね
という姿勢も大事
36
衒学的、ダメ絶対
• 関数型言語は哲学や宗教と関係がありますか?“ありません。いやひょっとしたらいつか何らかの関係が見出されるかもしれませんが、これまでのところ、まっとうな言説は寡聞にして知りません。”
37
そういう手法も有るよという話
• 関数型プログラミングで数学の概念を~
▶オブジェクト以外の抽象化方法として数学を背景に持つ物もある
▶ FPに数学の知識が不可欠という事では無い
知は力であるが門戸を狭める物では無い
38
かつて人々と関数型ヴォルデモートとの戦いがあった…
39
衒学的な記事を真に受けない
40
何かよく分からんけど難しそうなこと言ってる
↓ 怖い、凄いはダメ絶対
41
以上言語に依らない説明
42
抽象的過ぎるので 言語を導入しよう
43
具体的な話はTUT Advent Calendarで!
44
From here,
Second half of Toyohashi University Too
Long Talk
In TUT Advent Calendar
関数型 プログラミング入門
OCamlの
more 20min-talk 2nd TUT Advent Calendar 22/12/2015
58 Pages!
OCamlをざっくり雰囲気入門
49
Caution!!
50
ここからは幾つかの言語にも共通する特徴の説明
51
広く知られているものを中心に紹介していきますが一般的では無いことに注意
52
あくまでOCamlのプログラミング
53
OCamlとは
• 強い静的型付け、非純粋、正格評価
• フランス国立の計算機科学、数学の応用に
関する研究所INRIAが実装
• 関数型プログラミングを中心に
手続き型や構造的部分型付けのOOPをサポート
55
OCamlの関数型プログラミング
• 自動的な型推論 (Automatic type inference)
• パラメトリック多相 (Parametric polymorphism)
• 第一級関数 (First-class functions)
• 代数的データ型 (Algebraic data types)
• パターンマッチング (Pattern matching)
56
OCamlの関数型プログラミング
• 自動的な型推論 (Automatic type inference)
• パラメトリック多相 (Parametric polymorphism)
• 第一級関数 (First-class functions)
• 代数的データ型 (Algebraic data types)
• パターンマッチング (Pattern matching)
57
自動的な型推論
1 (* This is a comment. *) 2 (* let <var_id> = <val> *) 3 let txt = "hello" 4 (* val txt : bytes = "hello" *) 5 6 (* let <fun_id> <args...> = <body> *) 7 let longer_len ls len = String.length ls > len 8 (* val longer_len : bytes -> int -> bool = <fun> *) 9 10 longer_len "hello" 411 (* - : bool = true *)
val~は実際REPLで評価した際に推論、表示される58
自動的な型推論
1 (* This is a comment. *) 2 (* let <var_id> = <val> *) 3 let txt = "hello" 4 (* val txt : bytes = "hello" *) 5 6 (* let <fun_id> <args...> = <body> *) 7 let longer_len ls len = String.length ls > len 8 (* val longer_len : bytes -> int -> bool = <fun> *) 9 10 longer_len "hello" 411 (* - : bool = true *)
->は関数のシグネチャを表し、最後のboolが返値59
自動的な型推論
1 (* This is a comment. *) 2 (* let <var_id> = <val> *) 3 let txt = "hello" 4 (* val txt : bytes = "hello" *) 5 6 (* let <fun_id> <args...> = <body> *) 7 let longer_len ls len = String.length ls > len 8 (* val longer_len : bytes -> int -> bool = <fun> *) 9 10 longer_len "hello" 411 (* - : bool = true *)
10行目、関数適用は並べるだけ60
その他のデータ型
1 let l = ['T'; 'U'; 'T'] 2 (* val l : char list *) 3 4 (* Option type has a value that may be invalid *) 5 let o = Some 100 (* or None *) 6 (* val o : int option *) 7 8 (* Tuple aka product type *) 9 let t = (longer_len, l, o)10 (* val t : (int -> bytes -> bool) * char list * int option *)
61
複合的な型も同様に推論
自動的な型推論
• 基本的に特別なアノテーションは必要なし
• FPでは細かな関数の組み合わせが非常に多い
▶後述する多相の推論の有無は可読性に大きな影響
• 強制したければ明示的な指定も可能
• mliファイルにソースの推論結果を自動生成可62
OCamlの関数型プログラミング
• 自動的な型推論 (Automatic type inference)
• パラメトリック多相 (Parametric polymorphism)
• 第一級関数 (First-class functions)
• 代数的データ型 (Algebraic data types)
• パターンマッチング (Pattern matching)
63
パラメトリック多相/第一級関数
• パラメトリック多相
▶型の一部をパラメータとした時の多相
• 第一級関数
▶第一級オブジェクトとして扱える関数
▶関数を引数や返り値にとる関数が高階関数64
補足: 部分適用
一部の引数を束縛した新しい関数の値を返す操作longer_lenの引数lenを7に束縛した関数の値
をlonger_7に束縛している65
1 let longer_len len ls = String.length ls > len2 let longer_7 = longer_len 73 4 longer_7 "Aie" (* false *)5 longer_7 "Aieeeeeeeee" (* true *)
多相で高階の関数
1 (* Infix operator *)2 (* Normally, you should use the one3 in the Pervasives module(standard library). *)4 let ( |> ) x f = f x5 (* val ( |> ) : 'a -> ('a -> 'b) -> 'b = <fun> *)6 7 List.filter_map8 (* 'a list -> f:('a -> 'b option) -> 'b list = <fun> *)
66
'a 'b が型変数
関数適用時にそれぞれが1つに定まれば良い
多相で高階の関数
1 (* Infix operator *)2 (* Normally, you should use the one3 in the Pervasives module(standard library). *)4 let ( |> ) x f = f x5 (* val ( |> ) : 'a -> ('a -> 'b) -> 'b = <fun> *)6 7 List.filter_map8 (* 'a list -> f:('a -> 'b option) -> 'b list = <fun> *)
67
xが'aの時、fが'aをとり'bを返す関数fにxを適用し結果として’b型の値を返す
多相で高階の関数
1 (* Infix operator *)2 (* Normally, you should use the one3 in the Pervasives module(standard library). *)4 let ( |> ) x f = f x5 (* val ( |> ) : 'a -> ('a -> 'b) -> 'b = <fun> *)6 7 List.filter_map8 (* 'a list -> f:('a -> 'b option) -> 'b list = <fun> *)
68
filter_mapは'aのリストとその各要素に
適用する関数fをとり適用した結果のリストを返す
多相で高階の関数
1 (* Infix operator *)2 (* Normally, you should use the one3 in the Pervasives module(standard library). *)4 let ( |> ) x f = f x5 (* val ( |> ) : 'a -> ('a -> 'b) -> 'b = <fun> *)6 7 List.filter_map8 (* 'a list -> f:('a -> 'b option) -> 'b list = <fun> *)
69
更にfの返値で引数にとった要素の
取捨選択(Some or None)が可能
1 let count_groups l = String.Table.group 2 | (fun record -> List.nth_exn record 6) 3 | (fun x -> 1) 4 | (fun x y -> x + 1) l 5 6 let () = 7 In_channel.read_lines "car.csv" 8 |> List.filter_map ~f:(fun l -> 9 | | let l' = String.split ~on:',' l in10 | | if eval l' then Some l' else None)11 |> count_groups12 |> Hashtbl.iter ~f:(fun ~key ~data -> Printf.printf "%s = %d, " key data)
具体例として拙コードよりcsv処理の抜粋
(弊学同系には”ジャバ,car.csv”で伝わって)70
1 let count_groups l = String.Table.group 2 | (fun record -> List.nth_exn record 6) 3 | (fun x -> 1) 4 | (fun x y -> x + 1) l 5 6 let () = 7 In_channel.read_lines "car.csv" 8 |> List.filter_map ~f:(fun l -> 9 | | let l' = String.split ~on:',' l in10 | | if eval l' then Some l' else None)11 |> count_groups12 |> Hashtbl.iter ~f:(fun ~key ~data -> Printf.printf "%s = %d, " key data)
let()= はエントリポイントと見做して下さい
精読は必要ありませんが、流れは次の通り71
1 let count_groups l = String.Table.group 2 | (fun record -> List.nth_exn record 6) 3 | (fun x -> 1) 4 | (fun x y -> x + 1) l 5 6 let () = 7 In_channel.read_lines "car.csv" 8 |> List.filter_map ~f:(fun l -> 9 | | let l' = String.split ~on:',' l in10 | | if eval l' then Some l' else None)11 |> count_groups12 |> Hashtbl.iter ~f:(fun ~key ~data -> Printf.printf "%s = %d, " key data)
csv読み込み|>条件に合うデータ抽出|>幾つかのグループに分類して数を集計
|>結果の列挙72
1 let count_groups l = String.Table.group 2 | (fun record -> List.nth_exn record 6) 3 | (fun x -> 1) 4 | (fun x y -> x + 1) l 5 6 let () = 7 In_channel.read_lines "car.csv" 8 |> List.filter_map ~f:(fun l -> 9 | | let l' = String.split ~on:',' l in10 | | if eval l' then Some l' else None)11 |> count_groups12 |> Hashtbl.iter ~f:(fun ~key ~data -> Printf.printf "%s = %d, " key data)
名前付き引数~f:に要素毎の処理を部分適用group関数は「分類方法,分類の初期値,畳み込み」の
3つの関数をとる73
多相と高階のメリット• ロジックの分離(汎用化)
▶全体の流れ (let (|>) x f = f x etc…)
▶渡されるデータ構造に対する処理の方針(filter_map,group,iter etc…)
▶各要素に対する処理 (無名関数 fun etc…)
• それぞれ独立に実装、検証、組み合わせ可能74
つまりウォーズマン理論1. 両手の無名関数で100万+100万の200万パワー !!
2. いつもの2倍の畳み込みが加わり、200万×2の400万パワー !!
3. そして、いつもの3倍のPipe Operatorを加えれば、 400万×3のLINQ to Objectsマン !
お前をうわまわる1200万パワーだーっ!!
4. (※OCamlは正格評価なので
省メモリ化はLazyか読込み元での畳み込みが必要)
75
OCamlの関数型プログラミング
• 自動的な型推論 (Automatic type inference)
• パラメトリック多相 (Parametric polymorphism)
• 第一級関数 (First-class functions)
• 代数的データ型 (Algebraic data types)
• パターンマッチング (Pattern matching)
76
ヴァリアント型(代数的データ型)
• OCamlではヴァリアント型と呼ばれる、以下構文
• 「直積型の総和」…「直和型」… とも
• これを用いて問題や構造を型に変換していく77
type <variant> = | <Tag> [ of <type> [* <type>]... ] | <Tag> [ of <type> [* <type>]... ] | ...
分かりにくいので実例
78
e.g.) リストの再現
1 (* You can declare ["aaa"; "bbb"] usually *)2 let original_list = "aaa" :: "bbb" :: []
3 (* Variants aka Algebraic data types *)4 type 'a eq_list =5 | Nil6 | Cons of 'a * 'a eq_list
7 (* Equivalent representation *)8 let eq_l = Cons("aaa", Cons("bbb", Nil))9 (* val eq_l : string eq_list *)
79
2行目は空のリストへ"bbb"と"aaa"を加えた物
e.g.) リストの再現
1 (* You can declare ["aaa"; "bbb"] usually *)2 let original_list = "aaa" :: "bbb" :: []
3 (* Variants aka Algebraic data types *)4 type 'a eq_list =5 | Nil6 | Cons of 'a * 'a eq_list
7 (* Equivalent representation *)8 let eq_l = Cons("aaa", Cons("bbb", Nil))9 (* val eq_l : string eq_list *)
80
type <型変数> <型名> = <定義>
e.g.) リストの再現
1 (* You can declare ["aaa"; "bbb"] usually *)2 let original_list = "aaa" :: "bbb" :: []
3 (* Variants aka Algebraic data types *)4 type 'a eq_list =5 | Nil6 | Cons of 'a * 'a eq_list
7 (* Equivalent representation *)8 let eq_l = Cons("aaa", Cons("bbb", Nil))9 (* val eq_l : string eq_list *)
81
Nil Consはコンストラクタと呼ばれる
e.g.) リストの再現
1 (* You can declare ["aaa"; "bbb"] usually *)2 let original_list = "aaa" :: "bbb" :: []
3 (* Variants aka Algebraic data types *)4 type 'a eq_list =5 | Nil6 | Cons of 'a * 'a eq_list
7 (* Equivalent representation *)8 let eq_l = Cons("aaa", Cons("bbb", Nil))9 (* val eq_l : string eq_list *)
82
Consはof以降の型の値を引数にとる
e.g.) リストの再現
1 (* You can declare ["aaa"; "bbb"] usually *)2 let original_list = "aaa" :: "bbb" :: []
3 (* Variants aka Algebraic data types *)4 type 'a eq_list =5 | Nil6 | Cons of 'a * 'a eq_list
7 (* Equivalent representation *)8 let eq_l = Cons("aaa", Cons("bbb", Nil))9 (* val eq_l : string eq_list *)
83
*はタプル(直積型)
e.g.) リストの再現
1 (* You can declare ["aaa"; "bbb"] usually *)2 let original_list = "aaa" :: "bbb" :: []
3 (* Variants aka Algebraic data types *)4 type 'a eq_list =5 | Nil6 | Cons of 'a * 'a eq_list
7 (* Equivalent representation *)8 let eq_l = Cons("aaa", Cons("bbb", Nil))9 (* val eq_l : string eq_list *)
84
*'a eq_listとあるように、再帰的に定義可能
e.g.) リストの再現
1 (* You can declare ["aaa"; "bbb"] usually *)2 let original_list = "aaa" :: "bbb" :: []
3 (* Variants aka Algebraic data types *)4 type 'a eq_list =5 | Nil6 | Cons of 'a * 'a eq_list
7 (* Equivalent representation *)8 let eq_l = Cons("aaa", Cons("bbb", Nil))9 (* val eq_l : string eq_list *)
85
これらNil Consで構成される値がeq_list型となる
e.g.) リストの再現
1 (* You can declare ["aaa"; "bbb"] usually *)2 let original_list = "aaa" :: "bbb" :: []
3 (* Variants aka Algebraic data types *)4 type 'a eq_list =5 | Nil6 | Cons of 'a * 'a eq_list
7 (* Equivalent representation *)8 let eq_l = Cons("aaa", Cons("bbb", Nil))9 (* val eq_l : string eq_list *)
86
original_listとeq_lは等価な表現
e.g.) Option, 赤黒木, Trie 1 type 'a option = 2 | None 3 | Some of 'a 4 5 type 'a rb_tree = 6 | Empty 7 | Red of 'a rb_tree * key * 'a * 'a rb_tree 8 | Black of 'a rb_tree * key * 'a * 'a rb_tree 9 10 type trie = Trie of int option * char_to_children11 and char_to_children = (char * trie) list
87
keyは適当に定義したということで…
andは相互再帰できるtype
e.g.) 簡単な式(雰囲気) 1 type expr = 2 | Var of name 3 | Int of int 4 | Bool of bool 5 | Times of expr * expr 6 | Plus of expr * expr 7 | Minus of expr * expr 8 | Equal of expr * expr 9 | Less of expr * expr10 | If of expr * expr * expr11 | Fun of name * name * ty * ty * expr12 | Apply of expr * expr
88
[ミニ言語の実装]等を見ると使われ方が分かります
値が作れるのは分かったけど操作はどうするの
89
OCamlの関数型プログラミング
• 自動的な型推論 (Automatic type inference)
• パラメトリック多相 (Parametric polymorphism)
• 第一級関数 (First-class functions)
• 代数的データ型 (Algebraic data types)
• パターンマッチング (Pattern matching)
90
パターンマッチング
• match式で[パターン] に応じた分岐と 変数割当てが可能
• パターンを網羅できているか、冗長でないかを コンパイル時にチェックし何か有れば警告してくれる
91
match <value> with| <pattern> -> <result>| <pattern> -> <result> ...
e.g.) FizzBuzz
1 let fizzbuzz i = match i mod 3, i mod 5 with2 | 0, 0 -> "FizzBuzz"3 | 0, _ -> "Fizz"4 | _, 0 -> "Buzz"5 | _ -> string_of_int i6 7 let () =8 for i = 1 to 100 do print_endline @@ fizzbuzz i done
92
modは剰余の中置演算子string_of_int はintからstringへのキャスト関数
@@は|>の逆向きの関数適用
e.g.) Without a side effect
1 let (--) i j = (* i -- j makes a list from i to j *)2 let rec aux n acc =3 | if n < i then acc else aux (n - 1) (n :: acc)4 in5 aux j []6 7 let () =8 List.iter (fun x -> x |> fizzbuzz |> print_endline) (1 -- 100)
93
recはこの関数が再帰する事を示すlet…inは宣言後にinに続く式を評価するという意味
リストを畳み込む関数foldl を起点にfilter_mapを作ってみる
94
リストの畳込み関数foldl 1 let rec foldl acc ls f = match ls with 2 | [] -> acc 3 | hd :: tl -> foldl (f acc hd) tl f 4 5 let rev ls = foldl [] ls (fun acc x -> x :: acc) 6 7 let filter_map ls f = 8 rev @@ foldl [] ls 9 | (fun acc elm -> match f elm with10 | | || None -> acc11 | | || Some x -> x :: acc)
95
例えばfoldl 0 [x1;x2;x3] (+)と適用した際に(((0 + x1) + x2) + x3)となるのが畳み込み(fold-left)
リストのパターン( :: ) 1 let rec foldl acc ls f = match ls with 2 | [] -> acc 3 | hd :: tl -> foldl (f acc hd) tl f 4 5 let rev ls = foldl [] ls (fun acc x -> x :: acc) 6 7 let filter_map ls f = 8 rev @@ foldl [] ls 9 | (fun acc elm -> match f elm with10 | | || None -> acc11 | | || Some x -> x :: acc)
96
| [] -> リストが空であるならば| hd :: tl -> 先頭をhd,残りをtlと割当て可能ならば
ヴァリアントのパターン 1 let rec foldl acc ls f = match ls with 2 | [] -> acc 3 | hd :: tl -> foldl (f acc hd) tl f 4 5 let rev ls = foldl [] ls (fun acc x -> x :: acc) 6 7 let filter_map ls f = 8 rev @@ foldl [] ls 9 | (fun acc elm -> match f elm with10 | | || None -> acc11 | | || Some x -> x :: acc)
97
filter_mapはSomeに包んだ返値のみのリストを構築するOptionに限らずヴァリアントはコンストラクタでマッチ
ヴァリアントとパターンマッチング• ヴァリアントは異なる複数の型を
コンストラクタで型付け1つの型として表現できる
• 関数側でパターンを網羅することでヴァリアント型の値を扱う事ができる
• 型の積(タプル,レコード)と和(ヴァリアント系)で様々なデータ構造を表す事ができる
98
その他紹介できなかったもの
99
OCamlな機能のみなさま
• 多相ヴァリアント、 一般化代数的データ型
▶ ヴァリアントのゆかい(強力)な仲間たち
• 命令型プログラミング(副作用akaダークパワー)
▶ 非純粋だからって疎かにしている訳ではない !
[手続き型言語OCaml]
[ハスケロウ、破壊的代入はいいぞ!!] 100
OCamlな機能の諸兄
• モジュール、ファンクタ、第一級モジュール
▶ 前述したオブジェクト以外の(構造化|抽象化)
▶ 最もOCaml(というかml族)な機能の1つかも?
• 構造的部分型付けのオブジェクト
▶ 強力だが[過ぎる側面] も…
101
今回は幾つかの言語にも共通する特徴の説明
102
つまりOCamlらしい特徴はここから…
103
結論104