Slide 1

Slide 1 text

Pythonで始めてみよう関数型プログラミング Terajima Satoshi (@meganehouser) PyCon JP 2019 | 17 Sep. 2019

Slide 2

Slide 2 text

お前誰よ Terajima Satoshi @meganehouser 所属 株式会社SQUEEZE Python Django / Django REST framework AngularJS / Angular Meguro.LYAHFGG主催 (すごいHaskell本を原書で読む会) 2

Slide 3

Slide 3 text

私はなぜ関数型プログラミングを愛するよう になったか それは、10年以上前の話 私は前職のSIerでアプリケーション開発をしていた。 動作環境は .NET Framework ⾔語はC#を使っていた 3

Slide 4

Slide 4 text

そこで、素敵なプログラミング⾔語に出会ってしまった 4

Slide 5

Slide 5 text

5

Slide 6

Slide 6 text

F# OCamlの影響を⾊濃く受けた、.NET Framework向けの⾔語 ⼿続型、オブジェクト指向、関数型に対応した強い静的型付けのマ ルチパラダイムプログラミング⾔語 REPLがある コンパイルもスクリプト実⾏も可能 6

Slide 7

Slide 7 text

F#で関数型プログラミングの魅⼒に⽬覚めた 7

Slide 8

Slide 8 text

この発表のゴール 便利な関数型⾔語の機能を知る Pythonを使って関数型⾔語スタイルでプログラミングする⽅法の概 要を知る Pythonで関数型⾔語を実現するパッケージの使い⽅・実現⽅法の概 要を知る 関数型プログラミングを試してみたくなる 8

Slide 9

Slide 9 text

⽬次 関数型プログラミングとは Pythonでの関数型プログラミング実現⽅法 関数型プログラミング機能の紹介 関数合成 関数のカリー化 不変/永続データ構造 パターンマッチ モナド まとめ 9

Slide 10

Slide 10 text

注意 この発表では関数型プログラミングの機能解説のためにPython以外 のソースが豊富に出てきますが... 雰囲気で理解してください 10

Slide 11

Slide 11 text

関数型プログラミングとは

Slide 12

Slide 12 text

関数型プログラミングとは プログラミングパラダイムの⼀つ ⼿続き型プログラミング 命令実⾏の列としてプログラムを記述していくプログラミングスタ イル 関数型プログラミング 複数の式を、関数の適⽤によって組み合わせていくプログラミング スタイル 12

Slide 13

Slide 13 text

関数型⾔語の特徴 データに複数の関数を適⽤するプログラミングスタイル そのため、以下の機能が豊富 特定の⽬的のためのデータ型をユーザが定義する 関数を組み合わせて新たな関数を作る 13

Slide 14

Slide 14 text

関数型プログラミングの例 F#のパイプライン演算⼦ let add x y = x + y // add : int -> int -> int let minus x y = x - y // minus : int -> int -> int let display = printfn "number is %d" // display : int -> unit // パイプライン演算⼦を使⽤しない通常の書き⽅ display(minus 20 (add 5 10)) // パイプライン演算⼦を使⽤した書き⽅ 10 |> add 5 |> minus 20 |> display // => "number is 5" 14

Slide 15

Slide 15 text

Pythonでの関数型プログラミング実現⽅法

Slide 16

Slide 16 text

発表の進捗 関数型プログラミングとは Pythonでの関数型プログラミング実現⽅法 <- Now 関数型プログラミング機能の紹介 関数合成 関数のカリー化 不変/永続データ構造 パターンマッチ モナド まとめ

Slide 17

Slide 17 text

Pythonにおける関数型プログラミング機能の 実現⽅法 1. 標準機能・標準パッケージで実現 2. Pythonコードへのコンパイラによって実現 3. AST変換で実現 4. がんばって3rdパーティパッケージとして実現 17

Slide 18

Slide 18 text

⽅法1. 標準機能・標準パッケージで実現 公式ドキュメントに解説が掲載されている 関数型プログラミング HOWTO — Python 3.7.4 ドキュメント 標準機能・パッケージで関数型スタイルのプログラミングをす る⽅法を解説 iterator, generator itertools functools 18

Slide 19

Slide 19 text

⽅法2. Pythonを⽣成するコンパイラで実現 Pythonの実⾏までの⽣成物 Pythonの⽣成物にコンパイルされる⾔語の例 Pythonコードにコンパイルする⾔語 Coconut PythonのASTにコンパイルする⾔語 Hylang Pythonのbytecodeにコンパイルする⾔語 dg 19

Slide 20

Slide 20 text

Pythonコードにコンパイルするプログラミング⾔語 Coconut Programming Language Pythonと互換性があり、関数型⾔語の便利な機能を追加している # パイプライン演算⼦, 部分適⽤ range(10) |> map$(pow$(?, 2)) |> list # 代数的データ型 data Empty() data Leaf(n) data Node(l, r) # データ型によって適⽤される関数を切り替える機能 def size(Empty()) = 0 addpattern def size(Leaf(n)) = 1 addpattern def size(Node(l, r)) = size(l) + size(r) 出典: http://coconut-lang.org/ 20

Slide 21

Slide 21 text

PythonのASTにコンパイルするプログラミング⾔語 hylang/hy: A dialect of Lisp that's embedded in Python ⽂法はLisp マクロもある defn simple-conversation [] (print "Hello! I'd like to get to know you. Tell me about yourself!") (setv name (input "What is your name? ")) (setv age (input "What is your age? ")) (print (+ "Hello " name "! I see you are " age " years old."))) (simple-conversation) 出典: Tutorial — hy 0.17.0 documentation 21

Slide 22

Slide 22 text

CPython bytecodeにコンパイルするプログラミング⾔語 dg — it's a Python! No, it's a Haskell! Haskellのような⾒た⽬の動的⾔語 Slow. Stupid. Absolutely adorable. import '/asyncio' main = async $ loop -> task = "Hello, {}!".format whatever where await asyncio.sleep 1 whatever = "World" await task loop = asyncio.get_event_loop! loop.run_until_complete $ main loop 出典: https://pyos.github.io/dg/ 22

Slide 23

Slide 23 text

⽅法3. AST変換で実現する MacroPy3 lihaoyi/macropy モジュールのインポートフックでAST変換を⾏うマクロ インポート時に変換が⾛るのでマクロを使⽤したコードは __main__モジュールでは使⽤不可 参照: 30,000ft Overview — MacroPy3 1.1.0 documentation マクロの例として代数的データ型、パターンマッチ等が提供されて いる 23

Slide 24

Slide 24 text

⽅法4. がんばって3rdパーティパッケージとして実現 パッケージをインストールしてimportすればそのままPythonコード で使える 例 fn.py / Pyrsistent / Pampy / typesafe-monad / etc. この発表で取り上げるのはこの⽅法 24

Slide 25

Slide 25 text

関数型プログラミング機能の紹介

Slide 26

Slide 26 text

関数合成 (Function synthesis)

Slide 27

Slide 27 text

発表の進捗 関数型プログラミングとは Pythonでの関数型プログラミング実現⽅法 関数型プログラミング機能の紹介 関数合成 <- Now 関数のカリー化 不変/永続データ構造 パターンマッチ モナド

Slide 28

Slide 28 text

関数を組み合わせて… 例題:複数の割引関数を適⽤した後の価格が⾼額か少額か判定したい apple = ('apple', 300) def discount_by_day(fruit): # 曜⽇によって割引 price = fruit[1] * 0.9 if datetime.now().weekday() in (5, 6) else fruit[1] return fruit[0], price def discount_by_time(fruit): # 時間帯によって割引 price = fruit[1] * 0.9 if datetime.now().hour > 21 else fruit[1] return fruit[0], price def get_price_rank(fruit): # 価格によって価格区分を返す return 'High'if fruit[1] >= 300 else 'Low' 28

Slide 29

Slide 29 text

単純に関数を適⽤する場合の問題点 price_rank = is_high_price(late_discount(week_of_day_discount(apple))) # => 'Low' 関数の呼び出しが⼊れ⼦になって可読性が低い 実⾏の順序と、関数の並び順が逆になっている 29

Slide 30

Slide 30 text

[解決に使うFP機能] 関数合成 2個の関数を合成して新しい1個の関数を作り出す機能 専⽤の演算⼦を使⽤して関数合成を⾏う 処理する順序で関数を並べて定義できるので可読性が⾼い F# での関数合成の例 let add50 x = x + 50 // add50 : int -> int let add100 x = x + 100 // add100 : int -> int let minus100 x = x - 100 // minus100 : int -> int // newFunc : int -> int let newFunc = add50 >> add100 >> minus100 newFunc 10 // => 10 30

Slide 31

Slide 31 text

F#における関数合成の実装 F#などの関数型⾔語では、記号を組み合わせた新たな演算⼦が定義 可能 関数合成の演算⼦も⾔語仕様内で定義することができる let (>>) f g x = g(f x) 31

Slide 32

Slide 32 text

Pythonで関数合成を提供するパッケージ fn.py fnpy/fn.py: Missing features of fp in Python -- active fork of kachayev/fn.py pip3 install fn.py Scalaスタイルのラムダ式 永続データ構造 ストリームと無限シーケンス 関数のカリー化 etc. 32

Slide 33

Slide 33 text

[解決] fn.pyのFクラスで関数合成 fn.F 関数合成と部分適⽤を簡単に使えるようになる関数ラッパークラス 例題を関数合成で書き換えたコード 関数呼び出しの⼊れ⼦がなくなり、関数が呼び出される順序で記載でき る from fn import F apple = ('apple', 300) # 各関数定義は省略 new_func = (F() >> discount_by_day >> discount_by_time >> get_price_rank) print(new_func(apple)) # => 'Low' 33

Slide 34

Slide 34 text

fn.Fの関数合成の実装⽅法 Pythonでは演算⼦は特殊メソッドのオーバーロードによって実現 新たな記号を使⽤した演算⼦を増やすことはできない Fクラスの__rshift__をオーバーロードして >> 演算⼦の機能を上書 きしている class F(object): @classmethod def __compose(cls, f, g): return cls(lambda *args, **kwargs: f(g(*args, **kwargs))) def __ensure_callable(self, f): return self.__class__(*f) if isinstance(f, tuple) else f def __rshift__(self, g): return self.__class__.__compose(self.__ensure_callable(g), self.f) ソース出典: fn.py/func.py at master · fnpy/fn.py 34

Slide 35

Slide 35 text

関数のカリー化 (Curried function)

Slide 36

Slide 36 text

発表の進捗 関数型プログラミングとは Pythonでの関数型プログラミング実現⽅法 関数型プログラミング機能の紹介 関数のカリー化 <- Now 不変/永続データ構造 パターンマッチ モナド

Slide 37

Slide 37 text

関数の引数が増えたら… 例題: rateを引数で指定できるようにする def discount_by_day(rate, fruit): price = fruit[1] * rate if datetime.now().weekday() in (5,6) else fruit[1] return fruit[0], price def discount_by_time(rate, fruit): price = fruit[1] * rate if datetime.now().hour >= 21 else fruit[1] return fruit[0], price def get_price_rank:(fruit): return 'High'if fruit[1] > 300 else 'Low' 37

Slide 38

Slide 38 text

lambda式で対応する場合の問題点 lambda式で、引数が1個の関数にする 簡単な定型的な動作のために追加する⽂字数が多い lambdaって何回もタイプしたくない new_func = (F() >> (lambda fruit: discount_by_day(0.8, fruit)) >> (lambda fruit: discount_by_time(0.8, fruit)) >> get_price_rank) print(new_func(apple)) # => 'Low' 38

Slide 39

Slide 39 text

[解決に使うFP機能] カリー化された関数 F# の関数は、標準でカリー化された関数になっており引数の部分 適⽤ができる 部分適⽤ 複数の引数を必要とする関数に対して、⼀部のみ引数を適⽤するこ とができる 部分適⽤した関数は、残りの引数を必要とする関数になる F#の関数の部分適⽤の例 let add x y = x + y let addFive = add 5 addFive 3 // => 8 39

Slide 40

Slide 40 text

カリー化 カリー化 (currying, カリー化された=curried) とは、複数の引数を とる関数を、引数が「もとの関数の最初の引数」で戻り値が「もと の関数の残りの引数を取り結果を返す関数」であるような関数にす ること(あるいはその関数のこと)である。 [出典] カリー化 - Wikipedia // 3 引数の関数は // (int, int, int) -> int だと普通は考えるが... let add x y z = x + y + z // add: int -> int -> int -> int let add' = add 1 // add': (1) int -> int -> int let add'' = add' 2 // add'': (1) (2) int -> int let result = add'' 3 // result: (1) (2) (3) int // => 6 40

Slide 41

Slide 41 text

Pythonで関数をカリー化を提供するパッケージ fn.pyのcurriedデコレータで関数をカリー化できる >>> from fn.func import curried >>> @curried ... def sum5(a, b, c, d, e): ... return a + b + c + d + e ... >>> sum5(1)(2)(3)(4)(5) 15 >>> sum5(1, 2, 3)(4, 5) 15 出典: https://github.com/fnpy/fn.py#function-currying 41

Slide 42

Slide 42 text

[解決] 割引関数にcurriedデコレータを追加 例題の割引関数をカリー化したコード from fn.func import curried @curried def discount_by_day(rate, fruit): ... @curried def discount_by_time(rate, fruit): ... 42

Slide 43

Slide 43 text

[解決] 割引関数に引数を部分適⽤する 例題の関数合成する関数の引数(rate)を部分適⽤したコード new_func = (F() >> discount_by_day(0.8) >> discount_by_time(0.8) >> get_price_rank) print(new_func(apple)) # => 'Low' lambda式を書く必要がなくなり可読性が上がった 43

Slide 44

Slide 44 text

fn.pyのcurriedの実装⽅法 /fn/func.py#L61-L102 実際はカリー化は⾏なっていない 標準モジュール(functools.partial)を使って引数を部分適⽤している 引数が全て揃った場合は、引数を元の関数に与えて戻り値を returnする 引数が揃っていない場合は、引数を部分適⽤した関数をさらに curriedでラップしてreturnする 44

Slide 45

Slide 45 text

標準モジュールのfunctools.partial 関数に引数の部分適⽤したCallableオブジェクトを返す >>> from functools import partial >>> basetwo = partial(int, base=2) >>> basetwo.__doc__ = 'Convert base 2 string to an int.' >>> basetwo('10010') 18 出典: functools --- ⾼階関数と呼び出し可能オブジェクトの操作 — Python 3.7.4 ドキュメント 45

Slide 46

Slide 46 text

[おまけ] fn.Fクラスの部分適⽤機能 fn.Fは関数合成と部分適⽤を簡単に使えるようになる関数ラッパー クラス new_func = (F() >> (discount_by_day, 0.8) >> (discount_by_time, 0.8) >> get_price_rank) print(new_func(apple)) # => 'Low' 46

Slide 47

Slide 47 text

ここでちょっと休憩 47

Slide 48

Slide 48 text

不変/永続データ構造 (Immutable/Persistent data structure)

Slide 49

Slide 49 text

発表の進捗 関数型プログラミングとは Pythonでの関数型プログラミング実現⽅法 関数型プログラミング機能の紹介 関数のカリー化 不変/永続データ構造 <- Now パターンマッチ モナド まとめ

Slide 50

Slide 50 text

listを操作する関数の問題点 例題: 関数でlistを操作して結果を⽐較する def add_mango(fs: List[str]) -> List[str]: fs.append('mango') return fs def change_from_apple_to_banana(fs: List[str]) -> List[str]: fs[fs.index('apple')] = 'banana' return fs fruits = ['apple', 'melon'] fruits1 = add_mango(fruits) fruits2 = change_from_apple_to_banana(fruits) assert fruits1 != fruits2 # AssertionError !! Pythonの関数は参照渡しのため引数で渡したリストも変更される 関数呼び出しごとにリストをコピーする必要がある 毎回listをコピーする必要があるので⾮効率 50

Slide 51

Slide 51 text

[解決に使⽤するFP機能] 永続データ構造 変更される際に変更前のバージョンを保持するデータ構造 追加・更新・削除操作ごとに新たなオブジェクトが返る データが不変となることで、関数の参照透過性が上がる Clojureでのリスト(ベクター)操作の例 user=> (def foo [0 1 2 3]) #'user/foo user=> (conj foo 7) [0 1 2 3 7] user=> (assoc foo 2 9) [0 1 9 3] user=> (println foo) [0 1 2 3] nil F#も基本的にはデータは不変だが 永続データ的な操作を提供して いない 51

Slide 52

Slide 52 text

参照透過性 ある式が参照透過であるとは、その式をその式の値に置き換えても プログラムの振る舞いが変わらないことを⾔う。 (出典: 参照透過性 - Wikipedia) 純粋関数 引数を受け取って、戻り値を返す以外の副作⽤を持たない参照透過 な関数のことを純粋関数という 純粋関数は、テストしやすい 純粋関数を組み合わせて書かれたロジックは、解釈しやすい 52

Slide 53

Slide 53 text

Pythonで不変・永続データ構造を提供するパッケージ tobgu/pyrsistent Python標準モジュールのlist,tuple,dict,classなどに似せた不変/永 続/関数型のデータ型を提供するパッケージ >>> from pyrsistent import v, pvector >>> v1 = v(1, 2, 3) >>> v2 = v1.append(4) >>> v3 = v2.set(1, 5) >>> v1 pvector([1, 2, 3]) >>> v2 pvector([1, 2, 3, 4]) >>> v3 pvector([1, 5, 3, 4]) 出典: https://pyrsistent.readthedocs.io/en/latest/intro.html#pvector 53

Slide 54

Slide 54 text

[解決] 永続データ構造に置き換える 例題のリスト操作関数を永続データ構造に置き換えたコード from pyrsistent import v, PVector def add_mango(fs: PVector[str]) -> PVector[str]: return fs.append('mango') def change_from_apple_to_banana(fs: PVector[str]) -> PVector[str]: return fs.set(fs.index('apple'), 'banana') fruits = ['apple', 'melon'] fruits1 = add_mango(fruits) fruits2 = change_from_apple_to_banana(fruits) assert fruits1 != fruits2 引数に渡したlistが変更されないことで純粋関数となり使いやすくなった 54

Slide 55

Slide 55 text

Pyrsistentの実装 データの操作時に新しく作成したオブジェクトを返すが、なるべく パフォーマンスがPython標準組み込みのデータ型に⾒劣りしないよ うに気をつけて実装されている C⾔語拡張版も⼊っており、使⽤可能な環境では⾃動的に使⽤され る 55

Slide 56

Slide 56 text

Pyrsistentの実装 (PVectorの例) 32個の値のlistをnodeとする⽊構造を共有している ⼤量データでもデータ操作時、対象データに関連するノードの 再作成のみであとは共有できるため pyrsistent/_pvector.py at master · tobgu/pyrsistent 56

Slide 57

Slide 57 text

パターンマッチ (Pattern matching)

Slide 58

Slide 58 text

発表の進捗 関数型プログラミングとは Pythonでの関数型プログラミング実現⽅法 関数型プログラミング機能の紹介 関数のカリー化 不変/永続データ構造 パターンマッチ <- Now モナド まとめ

Slide 59

Slide 59 text

dictの形によって動作を変えたい… 例題: 予約希望の⼈数構成によって予約割り当てする部屋を変えたい # ⼤⼈と⼦供で予約したい request1 = {'date': '2019-08-10', 'numbers': {'adults': 2, 'children': 2}} # ⼤⼈のみで予約したい request2 = {'date': '2019-08-10', 'numbers': {'adults': 1}} 59

Slide 60

Slide 60 text

dictのkeyの有無の判定で条件分岐する場合の問題点 def reserve(request): num_of_children = request['numbers'].get('children') date_ = request['date'] num_of_adults = request['numbers']['adults'] if num_of_children is None: return reserve_general_room(date, num_of_adults) else: return reserve_familly_room(date, num_of_adults, num_of_children) 関数の実装から、想定されているdictの形式が掴みづらい 60

Slide 61

Slide 61 text

[解決に使⽤するFP機能] パターンマッチ 構造を持つデータを分解し、構成要素を取り出す 構造または分解・取得したデータによって条件分岐を⾏う F#でのパターンマッチの例 type Person = {Name: string; Gender: int} // 1: male, 2: female let genderName person = match person with | { Name=n; Gender=1 } -> n + " is male" | { Name=n; Gender=2 } -> n + " is female" | _ -> "undeterminded" genderName {Name="Taro"; Gender=1} 61

Slide 62

Slide 62 text

Pythonでパターンマッチを提供するパッケージ santinic/pampy: Pampy: The Pattern Matching for Python you always dreamed of. Pythonでは制御構⽂は追加できないので、引数で callback関数を指定する ⽂字列, 数値, tuple, list, dict, dataclass等、様々なデー タ形式でマッチングが可能 from pampy import match, _ input = [1, 2, 3] pattern = [1, 2, _] action = lambda x: f"it's {x}" match(input, pattern, action) 出典: https://github.com/santinic/pampy 62

Slide 63

Slide 63 text

Pampyで複数のパターンを使う場合 match([1,2,3], [1, 2, _], lambda x: f"it's {x}", [9, 9, 9], lambda x: "All nine", _, lambda x: "unmatch", ) pattern, actionのpairを複数書く _ 単体のpatternはどんな値にもmatchするため、デフォルトの actionの定義できる 63

Slide 64

Slide 64 text

[解決] 条件分岐をパターンマッチで書き換える 例題の条件分岐をパターンマッチで書き換えたコード from pampy import match, _ def reserve(request): return match(request, {'date': _, 'numbers': {'adults': _, 'children': _}}, reserve_familly_room, {'date': _, 'numbers': {'adults': _}}, reserve_general_room, ) 想定されるデータ形式が把握しやすい 想定されるデータ形式と、アクションが連続しているので可読性が ⾼い 64

Slide 65

Slide 65 text

Pampyの実装 match_value patternの型を調べる 型ごとにmatching⽤の関数が⽤意されており呼び出す 例えばdictの場合(match_dict) valueがdictではない場合はunmatch key同⼠とvalue同⼠がmatchするか⽐較(⽐較には match_valueを再度呼び出す) patternのdictのkeyかvalueが_の場合は、それぞれ key_extracted, value_extractedとして保持する pampy/pampy.py at master · santinic/pampy 65

Slide 66

Slide 66 text

モナド (Monad)

Slide 67

Slide 67 text

発表の進捗 関数型プログラミングとは Pythonでの関数型プログラミング実現⽅法 関数型プログラミング機能の紹介 関数のカリー化 不変/永続データ構造 パターンマッチ モナド <- Now まとめ

Slide 68

Slide 68 text

Haskell ⾮正格な評価を特徴とする純粋関数型プログラミング⾔語 Haskell の美しさを 知っている⼈は、 ⼈⽣に絶望することはない。 Haskell で世界を変えたい。 「Haskell 教養としての関数型プログラミング」の帯より引⽤ モナドはHaskellで標準ライブラリとして提供され広まった 68

Slide 69

Slide 69 text

モナドとは Haskellを設計したメンバーのひとり、フィリップ・ワドラー⽒いわ く…… 「モナドは単なる⾃⼰関⼿の圏におけるモノイド対象だ よ。何か問題でも?」 69

Slide 70

Slide 70 text

70

Slide 71

Slide 71 text

モナドとは型クラス 型クラスとは データ型をカテゴライズする役割を持つ機能 データ型が実装すべき関数のシグネチャを定義 イメージ的にはオブジェクト指向プログラミングにおけるインター フェース Monadの型クラスの継承関係 この順序で説明するのがメジャーな説明⽅法 71

Slide 72

Slide 72 text

Functor Functorの定義 class Functor f where fmap :: (a -> b) -> f a -> f b Functor則 Functorを実装する際に満たすべき規則 # id でファンクター値を写した場合、ファンクター値が変化してはいけない fmap id = id # 、すべてのファンクター値x に対して以下の等式が成り⽴つ fmap (f . g) x = fmap f (fmap g x) 72

Slide 73

Slide 73 text

Applicative Functor Applicative Functorの定義 class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b 73

Slide 74

Slide 74 text

Monad Monadの定義 class Applicative m => Monad m where return :: a -> m a (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b x >> y = x >>= _ -> y fail :: String -> m a fail :: msg = error msg 74

Slide 75

Slide 75 text

モナド則 モナドを実装するときに満たすべき規則 左恒等性 return x >>= f == f x 右恒等性 m >>= return == m 結合性 (m >>= f) >>= g == m >>= (\x -> f x >>= g) 75

Slide 76

Slide 76 text

モナドの詳しい説明は時間が⾜りないので省 略します 今回はモナドの使い⽅に着⽬して説明します 76

Slide 77

Slide 77 text

改めてモナドとは ⽂脈を持った値について、⽂脈を持たない値について想定した関数を適 ⽤したり、関数を組み合わせたりする関数(演算⼦)を定義した型クラス 77

Slide 78

Slide 78 text

モナドの例 1 「Maybeモナド」 Maybeは値が存在する|存在しない⽂脈を持った型 data Maybe a = Just a | Nothing 例としてx + yが100を超える場合はNothingを返す関数を定義 addLimit :: Int -> Int -> Maybe Int addLimit x y | (x + y) <= 100 = Just(x + y) | otherwise = Nothing 78

Slide 79

Slide 79 text

Maybeモナドのバインド関数 バインド関数 (>>=) 左辺の⽂脈付きの値を、右辺の⽂脈なし引数を取り⽂脈付き値を返す関 数に適⽤する演算⼦ Just 10 >>= addLimit 20 >>= addLimit 30 -- Just 60 Just 20 >>= addLimit 90 >>= addLimit 60 -- Nothing 右辺値がNothingの場合は関数をバイパスしてNothingを返すよう に定義されている 79

Slide 80

Slide 80 text

やっとPythonのモナド typesafe-monad correl/typesafe-monads PyMonadをベースに型アノテーションをつけた Maybeモナド, Listモナド, Resultモナド, Futureモナド, Readerモナ ドを実装 80

Slide 81

Slide 81 text

戻り値が存在しない場合がある関数の組み合わせ 例題: 存在しない可能性のある値を取得する関数の組み合わせ def get_user(user_id: int) -> Optional[User]: try: return User.objects.get(pk=user_id) except DoesNotExist: return None def get_user_photo(user: User) -> Optional[UserPhoto]: try: return UserPhoto.objects.get(user=User) except DoesNotExist: return None def get_photo_datetime(user_photo: UserPhoto) -> datetime: return user_photo.datetime 81

Slide 82

Slide 82 text

普通に組み合わせた場合の問点 user_id = 101 user = get_user(user_id) if user: user_photo = get_user_photo(user) if user_photo: return get_photo_datetime(user_photo) if⽂ごとにネストが深くなってしまう 82

Slide 83

Slide 83 text

[解決] Maybeモナド まずは関数を、⽂脈なしの引数をとって⽂脈ありの戻り値を返す関数に 書き直す from monads.maybe import Maybe, Just, Nothing def get_user(user_id: int) -> Maybe[User]: try: return Just(User.objects.get(pk=user_id)) except DoesNotExist: return Nothing() def get_user_photo(user: User) -> Maybe[UserPhoto]: try: return Just(UserPhoto.objects.get(user=User)) except DoesNotExist: return Nothing() def get_photo_datetime(user_photo: UserPhoto) -> Maybe[datetime]: return Just(user_photo.datetime) 83

Slide 84

Slide 84 text

[解決] Maybeモナド 次に関数をbind関数(bind演算⼦)で結合する Just(101) >> get_user >> get_user_photo >> get_photo_datetime Pythonでは新たな記号を⽤いた演算⼦を作成することはできない typesafe-monadではbind関数を >> に割り当てている Pythonは新たな演算⼦は増やせないのでパッケージ間で使⽤ する記号がかぶりがち 84

Slide 85

Slide 85 text

実装 Functor, Applicative, Monadで必要なメソッド(実装は空)を定義 具体的なモナド(ex. Maybe)でbind関数などのメソッドをオーバー ライドして中⾝を実装 コンテナ型(ex. Just, Nothing)は具体的なモナドを継承 85

Slide 86

Slide 86 text

時間の都合でいろいろ説明を省略したので、 次回は「Pythonで⼀から学ぶモナド」を発表 したいです。 86

Slide 87

Slide 87 text

まとめ

Slide 88

Slide 88 text

関数型プログラミングは… いろいろな便利な機能がある Pythonでのプログラミングでもパッケージを使って利⽤することが できる 楽しい! 88

Slide 89

Slide 89 text

もっと関数型プログラミングに詳しくなりたい! 今回、解説できたのは機能やパッケージの使い⽅の⼊り⼝の部分 興味が出てきた⽅はぜひ関数型プログラミング沼に⾶び込み⾒まし ょう Meguro.LYAHFF(すごいHaskell本の原書を読む会) SQUEEZEブースでもお待ちしております 89

Slide 90

Slide 90 text

Let's enjoy Python & Functional Programming !! ご静聴ありがとうございました !!