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

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

July 20, 2022

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

July 20, 2022
Tweet

Other Decks in Programming

Transcript

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

    - Chaining Result-Generating Functional • レールに乗らない失敗 /成功はType Errorになる • 失敗したら成功のレールには戻らな い • いつ失敗しても失敗のレールに乗る • 最後まで成功すると成功になる • 失敗の種類が決まっている 4
  2. 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
  3. シグネチャに失敗の値の型を定義する 失敗したらLeft[str, Any]を返す return Left[str, Any] 成功したらRight[str, Any]を返す return Right[str,

    Any] ※ TypeAliasから型変数を指定できる EitherDo: TypeAlias = Generator[Either[L, Any], None, R] 13
  4. 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
  5. 失敗を早期リターンする ↓ =󰢄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
  6. 途中の処理結果を束縛する ↓ 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
  7. 成功をリターンする 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
  8. 失敗/成功のどちらかだけである可能性を定義する SubType: TypeAlias = Left[L, R] | Right[L, R] @property

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

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

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

    ExceptionB from exception_a except Exception: … raise else: … とか書かない ※ どこからどのようなExceptionがraiseされているか  静的解析できない 例外とは何か例外的なもの(ある意味、あきらめ)を表現するものであるとした場合、失敗は想定できる結 果であるため、その失敗を例外としてモデリングすることは意味的におかしいことになってしまいます。 セキュア・バイ・デザイン - 安全性を考えた処理失敗時の対策 21
  12. 文でなく式にする 文: 処理 式: 結果 = 処理 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
  13. 参考文献 • python-category • Railway Oriented Programing • Domain Modeling

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