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

Pythonで鉄道指向プログラミング

Cc15f7fbb06e96bdb56992e3d42ea8cd?s=47
July 20, 2022

 Pythonで鉄道指向プログラミング

Cc15f7fbb06e96bdb56992e3d42ea8cd?s=128

July 20, 2022
Tweet

Other Decks in Programming

Transcript

  1. Pythonで 鉄道指向プログラミング Haskell, Scala, F#, Rust, … etc のアレ Python

    3.10, Pylance v2022.6.30 2022/07/20 @usabarashi 1
  2. どんな人?こんな人 Python歴 • 〜2019/09: Elm, Scala, TSを書いていた • 2019/10: Pythonを触り始める

    • 2022/06: 未だに「Pythonicって何やねん...」という日々 2
  3. 鉄道指向 プログラミング 3

  4. 失敗/成功の判断を掛け合わせる • Railway Oriented Programing • Domain Modeling Made Functional

    - Chaining Result-Generating Functional • レールに乗らない失敗 /成功はType Errorになる • 失敗したら成功のレールには戻らな い • いつ失敗しても失敗のレールに乗る • 最後まで成功すると成功になる • 失敗の種類が決まっている 4
  5. Haskell: do notation 5

  6. Scala: For Expressions 6

  7. F#: Computation Expressions 7

  8. Rust: Question mark operator 8

  9. Pythonで再現してみる 9

  10. Python仕様 • from typing import Callable, Generic, TypeAlias, TypeVar ◦

    T = TypeVar(“T”) ◦ class List(Generic[T]): ▪ Tは型が束縛される変数なので型変数 ▪ List[T]はT型を処理するList型 ▪ List[int]はint型を処理するList型 ▪ Callable[[A, B], C]はA型, B型を引数にとりC型を返却する関数 • from collections.abc import Generator ◦ variable = yield process() ◦ StopIteration.value ▪ Generator[Y, S, R] <: Iterator <: Iterable ▪ Y: yieldの右側のprocessの結果の型変数 ▪ S: = yieldの左側のvariableに束縛する値の型変数 ▪ R: StopIteration.valueの型変数 10
  11. 失敗/成功の型を定義する 失敗/成功の定義 class Either(Generic[L, R]): Either[L, R] 失敗の定義 class Left(Either[L,

    R): Left[L, R] 成功の定義 class Right(Either[L, R]): Right[L, R] 11
  12. 失敗/成功の型を定義する 失敗/成功のどちらかを返す def either(...) -> Either[L, R]: if …: return

    Left[L, R](...) else: return Right[L, R](...) 12
  13. シグネチャに失敗の値の型を定義する 失敗したらLeft[str, Any]を返す return Left[str, Any] 成功したらRight[str, Any]を返す return Right[str,

    Any] ※ TypeAliasから型変数を指定できる EitherDo: TypeAlias = Generator[Either[L, Any], None, R] 13
  14. EitherDo: TypeAlias = Generator[Either[L, Any], None, R] 上から下へ, 右から左への二次元と考える と理解しやすい

    ↓ =󰢄yield from Left[L, Any] ↓ Any=󰢏yield from Right[L, Any] return R StopIteration.value 一連の処理をGenerator関数で定義する 14
  15. 失敗を早期リターンする ↓ =󰢄yield from Left[L, Any] サブジェネレーターからLeft[L, Any]を貰う class Either(Generic[L,

    R]): @abstractmethod def __iter__(self) -> Generator[Either[L, R], None, R]: 失敗ならLeft[L, Any]をそのままリターンする @staticmethod def do(...) -> Callable[P, Either[L, R]] … case monad.Composability.Immutable: return yield_state …. ※ 失敗パターンはシグネチャに明記され ている 15
  16. 途中の処理結果を束縛する ↓ Any=󰢏yield from Right[L, Any] サブジェネレーターからRight[L, Any]を貰う class Either(Generic[L,

    R]): @abstractmethod def __iter__(self) -> Generator[Either[L, R], None, R]: 成功ならサブジェネレーターのRをsendする @staticmethod def do(...) -> Callable[P, Either[L, R]] … case monad.Composability.Variable: # 何も処理しない …. ※ サブジェネレーターの R: StopIteration.valueで暗黙的に束縛される 16
  17. 成功をリターンする return R StopIteration.value yieldがなくなったらreturnに相当する StopIteration.valueをリターンする @staticmethod def do(...) ->

    Callable[P, Either[L, R]] … except StopIteration as return_: return Right[L, R].lift(return_.value) … ※ 成功のRight[L, R]型へあわせる 17
  18. 失敗/成功のどちらかだけである可能性を定義する SubType: TypeAlias = Left[L, R] | Right[L, R] @property

    def pattern(self) -> SubType[L, R]: return self ※ どこから継承されるか分からないので 可能性を具体的に制限する ※ 動的なメソッドだとtype narrowingが固 定されないのでpropertyで静的な値として 定義する 󰢄 pattern() 󰢏 pattern 18
  19. 失敗/成功を判定する class Left(Either[L, R]): __match_args__ = ("value",) self.value class Right(Either[L,

    R]): __match_args__ = ("value",) self.value ※ パターンを網羅できていない場合は警 告が表示される 19
  20. 同じ構文で記述する: nullかも知れない • if var is None: • if var

    is not None: • if isinstance(var, None): とか書かない ※ if Xxxx is type(var)は  type narrowingされない 20
  21. 同じ構文で記述する: 例外が発生するかも知れない try: … except ExceptionA as exception_a: … raise

    ExceptionB from exception_a except Exception: … raise else: … とか書かない ※ どこからどのようなExceptionがraiseされているか  静的解析できない 例外とは何か例外的なもの(ある意味、あきらめ)を表現するものであるとした場合、失敗は想定できる結 果であるため、その失敗を例外としてモデリングすることは意味的におかしいことになってしまいます。 セキュア・バイ・デザイン - 安全性を考えた処理失敗時の対策 21
  22. 文でなく式にする 文: 処理 式: 結果 = 処理 def method(self: A,

    function: Callable[[A], B]) -> B: … result = sample_a .method(lambda self: SampleB(self.value)) def fold( self, left: Callable[[L], U], right: Callable[[R], U] ) -> U: … json_result = either.fold( left=lambda error: Json変換処理(error), right=lambda result: Json変換処理(result) ) 22
  23. で, 実務で使えるの? 23

  24. ビジネスロジック型パズル 24

  25. ビジネスロジック型パズル 25

  26. ビジネスロジック型パズル 26

  27. ビジネスロジック型パズル 27

  28. エンタープライズの鉄道 • 申請 ◦ 決裁 ◦ 未承認, 却下 • 機密

    ◦ 参照権限あり ◦ 参照権限なし 28
  29. 参考文献 • python-category • Railway Oriented Programing • Domain Modeling

    Made Functional • 明日から使える実践エラーハンドリング • セキュア・バイ・デザイン • 圏論とモナドシリーズ • Scala, Scalaz, Cats 29