Share
Embed
エンジニアにとってコードと並んで重要な「データ」のお話 - データが動くとコードが見える:関数型=データフロー入門
2025/11/10(月) Hello LT world『みんなで語る!好きと学びのアウトプット LT Night』
https://findy.connpass.com/event/372581/
登壇時の発表資料です。
Izumu KUSUNOKI
November 09, 2025
Transcript
10 slides
-
ಋೖɿؔʹೖྗˠग़ྗ ϓϩάϥϜΛ ʮσʔλͷܗʯΛʮผͷܗʯʹม͑ΔύΠϓϥΠϯ ͩͱଊ͑Δ ීஈϓϩάϥϜΛυϝΠϯࣝͷखଓ͖ʹैͬͯهड़͢Δ͜ͱ͕ଟ͍ͱࢥ͍·͕͢ɺ ͜ͷΑ͏ʹղऍ͢Δ͜ͱͰϓϩάϥϜ࣮ʹผࢹͰͷݟ௨͠ͷΑ͞Λݟग़͢͜ͱ͕Ͱ͖·͢ɻ ಛʹ࠷ޙʹհ͠·͕͢ɺσʔλΤϯδχΞͷ։ൃ͢ΔʮσʔλύΠϓϥΠϯʯͰͷࢹͩͱݴ͑·͢ɻ ʮ७ਮؔʯͷͭͷଋ ७ਮʢ෭࡞༻ͳ͠ʣ ߹Մೳ
ෆม σʔλϑϩʔਤ *OQVU ᴷᴷGᴷᴷ " ᴷᴷHᴷᴷ B ᴷᴷIᴷᴷ 0VUQVU ֤ؔ७ਮͰɺ෭࡞༻Λ࣋ͨͣɺ߹Մೳ ࠓճؔܕϓϩάϥϛϯάʹ৮ΕͭͭɺσʔλΤϯδχΞೖΛ͢Δͷ͕ૂ͍Ͱ͢ -
ܕͰೖྗͱग़ྗΛ໌֬ʹ͢Δ # Cart(注文) → Quote(見積書) を作成する純粋関数 def to_quote(c: Cart) ->
Quote: lines = tuple( QuoteLine(sku=i.sku, qty=i.qty, subtotal=i.price * i.qty) for i in c.items ) total = sum(l.subtotal for l in lines) return Quote(lines=lines, total=total) # 境界で副作用注入(ID 生成など) def confirm(q: Quote, gen_id: Callable[[], str]) -> Order: return Order(id=gen_id(), total=q.total) ϙΠϯτɿ෭࡞༻ʢ*%ੜͳͲʣҾͰೖͯ͠ڥքʹ͍ग़͢ 純粋関数として実装することで → テストが容易になり、予測可能性が向上する 副作用を引数化することで → 関数の振る舞いを外部から制御可能にする ύΠϓϥΠϯͷྫ ɾ ɾ ֤Ϋϥε!EBUBDMBTT1ZEBOUJDͳͲʹΑΔఆٛͩͱ͠·͢ -
ܕͰೖྗͱग़ྗΛ໌֬ʹ͢Δ # 合成(pipe)によって複雑な純粋関数のロジックを組み立てられる A = TypeVar("A"), B = TypeVar("B"), C
= TypeVar("C") def pipe(f: Callable[[A], B], g: Callable[[B], C]) -> Callable[[A], C]: return lambda a: g(f(a)) def checkout(gen_id: Callable[[], str]) -> Callable[[Cart], Order]: return pipe(to_quote, lambda q: confirm(q, gen_id)) # --- 使い方例(決定的テストが容易) --- if __name__ == "__main__": fixed = lambda: "id" cart = Cart(items=(CartItem("A", price=100, qty=2),)) order = checkout(fixed)(cart) # Order(id='id', total=200) print(order) ύΠϓϥΠϯͷྫ -
ঢ়ଶભҠܕͰද͢ OrderState = Union[Draft, Confirmed] def confirm_state(d: Draft, id: str)
-> Confirmed: return Confirmed(kind="Confirmed", id=id, total=d.total) def render_state(s: OrderState) -> str: match s: case Draft(kind="Draft", total=t): return f"Draft: {t}" case Confirmed(kind="Confirmed", id=i, total=t): return f"Confirmed: {i} / {t}" ύΠϓϥΠϯͷྫ ɾཏੑνΣοΫ͕ޮ͘ʹ౸ୡෆೳঢ়ଶΛܕͰࢭ ɾঢ়ଶભҠ͕໌ࣔతʹͳΓɺίʔυͷҙਤ͕໌֬ʹͳΔ -
σʔλج൫Ͱಉ͡ൃΛৗ༻͍ͯ͠Δ ೖྗεΩʔϚ ˠ७ਮม ˠग़ྗεΩʔϚ ෭࡞༻ʹ*0ڥք σʔλΤϯδχΞϦϯάʹ͓͍ͯؔܕͷߟ͑ํ͕ඇৗʹ༗ޮʜ ͱ͍͏͔ɺ&5-ʢ&YUSBDU 5SBOTGPSN -PBEʣϓϩηεɺ ·͞ʹσʔλͷܗΛม͢ΔύΠϓϥΠϯͦͷͷɻ
͜Ε͕ࠓճհ͔ͨͬͨ͜͠ͱɻ ڞ௨͢Δݪଇ ܾఆతͳม ಉ͡ೖྗ͔Βৗʹಉ͡ग़ྗΛੜ ႈੑ Կ࣮ߦͯ݁͠Ռ͕มΘΒͳ͍ ςελϒϧ ೖྗͱظग़ྗΛఆٛ͢Εɺςετ͕༰қ <ຊ>σʔλΤϯδχΞ࣮ɿؔܕ&5-&-5 εΩʔϚϑΝʔετʹɺႈͳσʔλมͷύʔπΛΈ߹Θ͍ͤͯ͘ͷ͕σʔλύΠϓϥΠϯ։ൃ -
ؔܕσʔλϑϩʔΛମݱ͢Δπʔϧ %BHTUFSʮΞηοτத৺ʯͷࢥߟͰσʔλύΠϓϥΠϯΛߏங @assetdef daily_sales(...) -> DataFrame: # 純粋な変換ロジックのみを記述 # 返り値の型に基づいて、IO
Managerが自動的に永続化 return pd.DataFrame(...) @asset(deps=[daily_sales]) def weekly_sales(daily_sales: DataFrame) -> DataFrame: # daily_salesの読み込みはIO Managerが自動処理 # 変換ロジックのみに集中できる return daily_sales.resample('W').sum() *0ͷΛ%BHTUFSଆ͕ٵऩ Ξηοτϕʔε ؔܕσʔλϑϩʔ ೖྗΞηοτ ˠ७ਮม ˠग़ྗΞηοτ ෭࡞༻ʢ*0ʣڥքʢ*0.BOBHFSʣ %BHTUFS ਪ͠ ͷΞηοτϕʔεΞϓϩʔν ७ਮͳมϩδοΫͷΈهड़ r ར༻ऀؔͷதʢσʔλมʣ͚ͩΛ࣮͢Ε͍͍ *0 .BOBHFS ͕ӬଓԽΛ୲ r ϑΝΠϧॻ͖ࠐΈɺ%#ଓͳͲͷ෭࡞༻%BHTUFS͕ॲཧ ฦΓͷܕʹجͮࣗ͘ಈॲཧ r ܕใ͔ΒదͳӬଓԽํ๏Λࣗಈબ IUUQTEBHTUFSJPCMPHTPGUXBSFEFGJOFEBTTFUT