Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Pythonで始めてみよう関数型プログラミング

 Pythonで始めてみよう関数型プログラミング

Satoshi Terajima

September 17, 2019
Tweet

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  5. 5

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  14. 関数型プログラミングの例
    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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  20. 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

    View Slide

  21. 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

    View Slide

  22. 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

    View Slide

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

    View Slide

  24. ⽅法4. がんばって3rdパーティパッケージとして実現
    パッケージをインストールしてimportすればそのままPythonコード
    で使える

    fn.py / Pyrsistent / Pampy / typesafe-monad / etc.
    この発表で取り上げるのはこの⽅法
    24

    View Slide

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

    View Slide

  26. 関数合成 (Function synthesis)

    View Slide

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

    View Slide

  28. 関数を組み合わせて…
    例題:複数の割引関数を適⽤した後の価格が⾼額か少額か判定したい
    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

    View Slide

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

    View Slide

  30. [解決に使う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

    View Slide

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

    View Slide

  32. 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

    View Slide

  33. [解決] 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

    View Slide

  34. 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

    View Slide

  35. 関数のカリー化 (Curried function)

    View Slide

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

    View Slide

  37. 関数の引数が増えたら…
    例題: 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

    View Slide

  38. 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

    View Slide

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

    View Slide

  40. カリー化
    カリー化 (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

    View Slide

  41. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  45. 標準モジュールの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

    View Slide

  46. [おまけ] 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

    View Slide

  47. ここでちょっと休憩
    47

    View Slide

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

    View Slide

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

    View Slide

  50. 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

    View Slide

  51. [解決に使⽤する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

    View Slide

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

    View Slide

  53. 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

    View Slide

  54. [解決] 永続データ構造に置き換える
    例題のリスト操作関数を永続データ構造に置き換えたコード
    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

    View Slide

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

    55

    View Slide

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

    View Slide

  57. パターンマッチ (Pattern matching)

    View Slide

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

    View Slide

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

    View Slide

  60. 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

    View Slide

  61. [解決に使⽤する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

    View Slide

  62. 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

    View Slide

  63. 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

    View Slide

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

    View Slide

  65. 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

    View Slide

  66. モナド (Monad)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  70. 70

    View Slide

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

    View Slide

  72. 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

    View Slide

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

    View Slide

  74. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  78. モナドの例 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

    View Slide

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

    View Slide

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

    View Slide

  81. 戻り値が存在しない場合がある関数の組み合わせ
    例題: 存在しない可能性のある値を取得する関数の組み合わせ
    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

    View Slide

  82. 普通に組み合わせた場合の問点
    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

    View Slide

  83. [解決] 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  87. まとめ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide