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

FP in Julia « SIDE: J » for JuliaTokai #21

FP in Julia « SIDE: J » for JuliaTokai #21

Julia による関数型プログラミング (FP) の解説。
JuliaTokai #21 発表資料

GOTOH Shunsuke

March 30, 2025
Tweet

More Decks by GOTOH Shunsuke

Other Decks in Technology

Transcript

  1. お品書き • お前誰よ? • 簡単な Julia の紹介 • 関数型プログラミング (FP)

    • Julia で FP するためのエッセンス • Julia でより FP するために
  2. 自己紹介 • 名前:後藤 俊介 • 所属:有限会社 来栖川電算 • コミュニティ:🌟JuliaTokai, 🌟JuliaLangJa, JuliaTokyo,

    Python東海, 🌟関数型まつり🆕… • 言語:Julia, Python, Ruby, … • SNS等:                  (@antimon2) • SNS等(2):      (@antimon2.jl) • 著書:実践Julia入門
  3. Julia とは?(1) • The Julia Language • 最新 v1.11.4(2025/03/10) 🆕

    ◦ LTS:v1.10.9(2025/03/10) 🆕 ◦ 次期:--- • 科学技術計算に強い! • 動作が速い!(LLVM JIT コンパイル)
  4. Julia とは?(2) • Rのように中身がぐちゃぐちゃでなく、 • Rubyのように遅くなく、 • Lispのように原始的またはエレファントでなく、 • Prologのように変態的なところはなく、

    • Javaのように硬すぎることはなく、 • Haskellのように抽象的すぎない ほどよい言語である 引用元:http://www.slideshare.net/Nikoriks/julia-28059489/8
  5. Julia とは?(3) • C のように高速だけど、 Ruby のようなダイナミズムを併せ持っている • Lisp のような真のマクロを持ちながら、

    MATLAB のような直感的な数式表現もできる • Python のように総合的なプログラミングができて、 R のように統計処理も得意で、 Perl のように文字列処理もできて、 MATLAB のように線形代数もできて、 shell のように複数のプログラムを組み合わせることもできる • 超初心者にも習得は容易でありながら、 ハッカーの満足にも応えられる • インタラクティブな動作環境もあって、コンパイルもできる (Why We Created Julia から抜粋・私訳)
  6. プログラミングパラダイム • パラダイム(プログラミングパラダイム) ◦ プログラミングにおける規範・模範 ◦ 手続き型、オブジェクト指向、関数型、など • 命令型 vs

    宣言型 ◦ 命令型:「どうなすべきか」を実装していくパラダイム ◦ 宣言型:「何をなすべきか」を表現していくパラダイム • 手続き型プログラミング(Procedural Programming) ◦ 主に 文(Statement) の記述で進めるパラダイム(命令型の一種) • オブジェクト指向プログラミング(Object Oriented Programming) ◦ オブジェクト を主体としたパラダイム(命令型の一種) ◦ オブジェクトの生成・管理、その組合せ、振る舞い定義などでプログラミング
  7. 関数型プログラミング (FP) • 関数型プログラミング(Functional Programming / FP) ◦ 宣言型の一種 ◦

    (数学的な意味での) 関数 を主体としたパラダイム ◦ キーワード(※詳細後述): ▪ 参照透過性 ▪ 副作用(がないこと) ▪ 式と評価
  8. コード例 (1): リストのフィルタリングと集計 # 手続き型スタイル function sum_of_squares_procedural(lst) total = 0

    for x in lst if iseven(x) total += x^2 end end return total end # 関数型スタイル(1) function sum_of_squares_functional(lst) filtered = filter(iseven, lst) transformed = map(x -> x^2, filtered) sum(transformed) end # 関数型スタイル(2) sum_of_squares_functional(lst) = sum(x^2 for x in lst if iseven(x)) • 要件:リスト(1次元配列)の偶数を抽 出し、それらを2乗し、合計する ◦ 手続き型:要素を for ループで回しなが ら対象のものを加工し集計する ◦ 関数型:抽出・加工・集計をそれぞれ実現 する式(関数)の組合せを記述する
  9. コード例 (2): フィボナッチ数 # 手続き型スタイル function fib_procedural(n) a, b =

    0, 1 for _ in 1:n a, b = b, a + b end return a end # 関数型スタイル(1)(再帰、O(2ⁿ)) function fib_functional(n) n == 0 && return 0 n == 1 && return 1 fib_functional(n-1) + fib_functional(n-2) end # 関数型スタイル(2)(内部別関数で再帰、O(n)) function fib_functional(n) function _fib_sub(n, a, b) n == 0 && return a _fib_sub(n - 1, b, a + b) end _fib_sub(n, 0, 1) end • 要件:整数 n を与えて、n 番目の フィボナッチ数を計算して返す ◦ 手続き型:for ループで各項を順に計算 ◦ 関数型(1):一般項の定義をそのまま記 述(再帰関数利用) ◦ 関数型(2):ループを再帰で表現すること で関数呼び出しだけで実現
  10. 手続き型スタイル • ⭕コードの流れが直感的 • ⭕アルゴリズムを追いやすい • ⭕効率的(メモリ使用量、速度) • ❌冗長になりやすい •

    ❌抽象性が低くなる • ❌ロジックが状態に依存するため 保守性が低くなる 比較 関数型スタイル • ⭕コードが簡潔になりやすい • ⭕処理の意図が分かりやすい • ⭕関数の再利用性やテストが容易 • ❌効率が良いとは限らない(良くな い) • ❌複雑な処理(長いパイプライン等) だと可読性が落ちる
  11. FP in Julia その他の利点 # double(x) = if isa(x, Number)

    # 2x # else # if isa(x, AbstractString) # x^2 # end # ↑みたいなのを↓のように簡潔かつ可読性高く書ける julia> double(n::Number) = 2n; julia> double(s::AbstractString) = s^2 double (generic function with 2 methods) # さらに↓で要素の型を気にせずに扱える julia> map(double, [1, 2, 3]) #> [2, 4, 6] julia> map(double, ["A", "B", "C"]) #> ["AA", "BB", "CC"] • 多重ディスパッチと実は相性が良 い! ◦ 細かい処理を関数で抽象化 ◦ その組み合わせでプログラム作成 • (Julia の)JIT コンパイルとも相性 が良い! ◦ JIT コンパイルは 関数単位 ◦ 前段の型推論も関数単位(式単位)
  12. 参照透過性 • 参照透過性 ◦ プログラム中の 式 を 式の値 に置き換えてもプログラムの振る舞いが変わらないこと ◦

    言い換えると、「その式が 評価 するたびに値が変わるということがない」こと ◦ 関数で言えば「同じ引数を与えたら必ず同じ戻り値が得られる」こと • 参照透過性が確保されていると… ◦ そのプログラム片(式)は 状態 を持たない(状態を気にする必要がない) ◦ その値がどこに記憶されているか(どこを参照しているか)を気にする必要がない • ※ 式 とか 評価 とか… ◦ 拙著『実践Julia入門』等参照(説明割愛)
  13. 副作用 • 副作用 (Side Effect) ◦ 式の評価による作用(主たる作用)以外の作用を指す言葉 ◦ 関数の場合、「関数値(関数の 戻り値)を得ること」が(主たる)作用、それ以外は副作用:

    ▪ 引数の値(オブジェクト)の内容の変更(破壊的変更) ▪ 引数以外の変数の(参照および)変更 ▪ 入出力(標準入出力、ファイルIO、ネットワーク参照等) • 副作用がないと… ◦ その式・関数は 参照透過 である ◦ 副作用のない参照透過な関数を特に 純粋関数 と呼ぶ
  14. Julia の 文 と 式 • Julia はすべて 式 ◦

    どのようなプログラム片も 値 を持つ(評価すると値が得られる) ◦ 一般的な手続き型言語で 文 と呼ばれるものも Julia では 式: ▪ 代入式(値は右辺の値) ▪ if 式(値は最後に評価した式の値) ▪ try 式(値は try/catch/else 節内で最後に評価した式の値) ▪ for 式、while 式(値は nothing) ◦ 式なので、例えば結果を他の変数に代入ができる
  15. Julia の 関数 # 関数定義例(1): `function` キーワード使用 function add(x, y)

    x + y # ← `return` キーワード不要 end # 関数定義例(2): インライン定義 f(x) = x^2 + 2x - 1 # 関数定義例(3): アロー演算子(無名関数定義) sq = x -> x^2 # (1)または(2)で定義したものは関数名が型名に入る typeof(add) #> typeof(add) (singleton type of function add, subtype of Function) # 関数はすべて `Function` 型のサブタイプ all(f isa Function for f in [add, f, sq]) #> true • Julia の関数は 第一級オブジェクト ◦ 関数をオブジェクトとして(他の値と同様 に)扱える • 関数に 型 がつく ◦ Julia では関数は Function 型の subtype ◦ ※(よくある関数型言語にあるような) A -> B -> C のような型ではない • 定義の書式はいくつかある ◦ 最後に評価した式の値が戻り値(明示的 な return 不用)
  16. Julia の 型システム (1) • Julia は 公称型システム(Nominative Type System)

    ◦ 簡単に言うと「(名前空間+)名前」で型が決まるということ • Julia は 公称的サブタイピング(Nominal Subtyping) ◦ 明示的に宣言されることでサブタイピング(基本型-派生型 の親子関係)が生成され るということ ◦ ※(よくある関数型言語ような) 構造的部分型 の仕組みはない! ◦ ※(関数で言うと) シグニチャ(引数や戻り値の型の組合せ)が同じなら同じ型、とい う仕組みもない!
  17. Julia の 型システム (2) • Any 型 と Union{} 型

    ◦ Any: 所謂「トップ型」、すなわちすべての型の基本型 ▪ 他言語の Object 型(Java, etc) 等に相当 ◦ Union{}: 所謂「ボトム型」、すなわちすべての型の派生型 ▪ 他言語の void (C言語) や ()(Haskell の Unit) 等に相当 ▪ Julia では『(例外が発生するために)何も値を返さない式の型』を表す • Union 型 ◦ TypeScript の Union 型と大体同じ ◦ ※(型理論でいうところの) 直和型 とは厳密には異なる!
  18. Julia の イテレータ struct FibSequence end; Base.iterate(itr::FibSequence) = iterate(itr, (0,

    1)); function Base.iterate(::FibSequence, (a, b)) (a, (b, a + b)) end; collect(Iterators.takewhile(<(100), FibSequence())) #> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] #= # Elixir による類似例 defmodule Fibonacci do def sequence, do: Stream.unfold({0, 1}, fn {a, b} -> {a, {b, a + b}} end) end Stream.take_while(Fibonacci.sequence, &(&1 < 100)) |> Enum.to_list # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] =# • Base.iterate() をメソッド実装 (多重定義)することでイテレータを 定義できる ◦ 所謂 関数型言語 によくある unfold と 同じ仕組みに実はなっている
  19. その他 Julia の FP 関連要素 • 並行・並列プログラミング ◦ 関数型プログラミング(関数ベースの抽象化)は並行・並列コンピューティングと相性 が良い

    ◦ Julia 標準の 並行・並列処理(Task、Thread、MultiProcessing)も関数ベース の抽象化の仕組みを十二分に利用 • メタプログラミング ◦ Expr 型(AST)の評価(eval())は 型なしラムダ計算 に基づく考え(Lisp 由来) ◦ 生成関数(引数の型が確定(実行時)したときにbodyを動的生成する関数) ◦ マクロ(ASTを受け取って加工したASTを返す関数ベースのマクロ)
  20. 関数合成 julia> f(x) = x + 2 f (generic function

    with 1 method) julia> g(x) = 2x g (generic function with 1 method) julia> (f ∘ g)(10) # == `f(g(10))` == `(2 * 10) + 2` 22 julia> (g ∘ f)(10) # == `g(f(10))` == `2 * (10 + 2)` 24 • Julia 標準で 関数合成演算子 ∘ が 存在
  21. 高階関数 julia> map(x -> x ^ 2, 1:10) #> [1,

    4, 9, 16, 25, 36, 49, 64, 81, 100] julia> filter(x -> x > 5, 1:10) #> [6, 7, 8, 9, 10] julia> map(+, 1:7, [3, 1, 4, 1, 5, 9, 2]) #> [4, 3, 7, 5, 10, 15, 9] • 関数を引数に受け取る関数 ◦ 加工・判定などの処理を引数に受け取っ た関数に委任 ◦ 関数は第一級オブジェクトなので普通に 実現可能 ◦ (一部の例外を除く)演算子も関数なので 同様に扱える
  22. 部分適用 julia> filter(>(5), 1:10) #> [6, 7, 8, 9, 10]

    julia> sq = Base.Fix2(^, 2); # == `sq = x -> x ^ 2` julia> map(sq, 1:10) #> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] • 一部の関数(演算子)は簡単に 部分 適用 の形にできる ◦ >(a) は x -> x > a と等価 • Base.Fix2(«op», «val») で明 示的に部分適用を生成することも可 能(x -> «OP»(x, «val») と同等) ◦ Base.Fix1(«op», «val») (x -> «OP»(«val», x) と同等) もあります
  23. パイプライン演算子(パイプ演算子) julia> f(x) = x + 2 f (generic function

    with 1 method) julia> g(x) = 2x g (generic function with 1 method) julia> 10 |> f |> g # == `g(f(10))` 24 julia> 10 |> f ∘ g # == `(f ∘ g)(10)` == `f(g(10))` 22 • «val» |> «fn» で «fn» («val») と同じ意味(関数適用) • Chain できる • 関数合成演算子 ∘ とも相性良い
  24. MLStyle.jl # ↓ README 記載のサンプルまま using MLStyle # 代数的データ型 `Shape`

    の定義 @data Shape begin Rock Paper Scissors end # ジャンケンゲームを パターンマッチング を利用して定義 play(a::Shape, b::Shape) = @match (a, b) begin (Paper, Rock) => "Paper Wins!"; (Rock, Scissors) => "Rock Wins!"; (Scissors, Paper) => "Scissors Wins!"; (a, b) => a == b ? "Tie!" : play(b, a) end • 代数的データ型(ADT および GADT)、アクティブパターン、そして それらを利用した パターンマッチン グ の機能を提供 • 関数型言語(特に ML(Meta Language)系)でよく使われる機 能を Julia に導入したかったという のがモチベーション ◦ 中の人曰く 『もしチャンスがあるなら “FunctionalProgramming.jl” にリ ネームしたい』 らしい
  25. Transducers.jl using Transducers, Primes # このスライドの最初の方で示した例(1)の別解 let lst=[3, 1, 4,

    1, 5, 9, 2, 6, 5, 3] # lst |> Filter(iseven) |> Map(x -> x^2) |> sum # ↑ でも良いがより FP 的には↓(本家のサンプルもこちら) foldl(+, lst |> Filter(iseven) |> Map(x -> x^2)) end #> 56 # 無限リストも扱えることを示すサンプル Iterators.countfrom(1) |> Map(n -> n^2 + 1) |> Filter(isprime) |> Take(100) |> collect #> [2, 5, 17, 37, 101, «中略», 682277, 739601] • Clojure の Transducers の仕 組みを Julia に導入 • シーケンス(イテレータ)を受け取り、 フィルタリング・加工等の ロジックの 連鎖 をしていく仕組み(遅延リスト を生成) ◦ ↑ 無限リストも扱える!ということ • マルチスレッド・マルチプロセスにも 対応
  26. HolyMonads.jl (非公式) # 事前に `]add https://github.com/antimon2/HolyMonads.jl.git` が必要 using HolyMonads using

    HolyMonads.MaybyMonad getmaybe(dic::AbstractDict, key) = haskey(dic, key) ? Some(dic[key]) : nothing d = Dict(:a => 1, :b => 2); Maybe.@do begin a ← getmaybe(d, :a) b ← getmaybe(d, :b) return (a + b) end #> Some(3) Maybe.@do begin c ← getmaybe(d, :c) # ここで中断、以下は処理されない b ← getmaybe(d, :b) return (c + b) end #> nothing • Monad、(Haskell の)do 記法 (F# のコンピュテーション式) を Julia に導入 (拙作) ◦ GitHub で公開、General(パッケージ ディレクトリ)には未登録 • 既存の型も Monad のシステムに 組み込める! ◦ Maybe モナドは Julia 標準の Some と Nothing をそのまま利用
  27. 参考リンク等 (1): Julia 関連 • julialang.org(Julia 本家サイト) ◦ Julia Documentation

    • Wikipedia[ja]: Julia (プログラミング言語) (https://ja.wikipedia.org/wiki/Julia_(プログラミング言 語))
  28. 参考リンク等 (2): パラダイム・用語等 • Wikipedia[ja]: プログラミングパラダイム (https://ja.wikipedia.org/wiki/プログラミングパラダイム) • Wikipedia[ja]: 関数型プログラミング

    (https://ja.wikipedia.org/wiki/関数型プログラミング) ◦ Wikipedia[ja]: 参照透過性 (https://ja.wikipedia.org/wiki/参照透過性) ◦ Wikipedia[ja]: 副作用 (プログラム) (https://ja.wikipedia.org/wiki/副作用_(プログラム))
  29. 参考リンク等 (3): パッケージ等 • MLStyle.jl ◦ GitHub: https://github.com/thautwarm/MLStyle.jl ◦ Documents:

    https://thautwarm.github.io/MLStyle.jl/latest/ • Transducers.jl ◦ GitHub: https://github.com/JuliaFolds/Transducers.jl ◦ Documents: https://juliafolds.github.io/Transducers.jl/dev/ • HolyMonads.jl ◦ GitHub: https://github.com/antimon2/HolyMonads.jl
  30. 関数型まつり • 関数型まつり ( https://2025.fp-matsuri.org ) ◦ 関数型プログラミング(FP)カンファレンス ハッシュタグ #fp_matsuri

    ◦ 2025/06/14(土)~2024/06/15(日) ◦ サイトより紹介文抜粋↓ ◦ 私も発表予定(スタッフ枠)→ Julia という言語について 私たちは様々な背景の方々が関数型プログラミングを通じて新しい知見 を得て、交流ができるような場を提供することを目指しています。普段か ら関数型言語を活用している方や関数型プログラミングに興味がある方 はもちろん、最先端のソフトウェア開発技術に興味がある方もぜひご参加 ください!