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

Julia という言語について (FP in Julia « SIDE: F ») for 関...

Julia という言語について (FP in Julia « SIDE: F ») for 関数型まつり2025

Julia という言語について(FP in Julia « SIDE: F »)。
関数型まつり2025 発表資料

Avatar for GOTOH Shunsuke

GOTOH Shunsuke

June 15, 2025
Tweet

More Decks by GOTOH Shunsuke

Other Decks in Programming

Transcript

  1. Julia という言語について (FP in Julia « SIDE: F ») 2025/06/15

    関数型まつり 2025 antimon2(後藤 俊介)
  2. お品書き • お前誰よ? • 簡単な Julia の紹介 • 関数型プログラミング (FP)

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

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

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

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

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

    宣言型 ◦ 命令型:「どうなすべきか」を実装していくパラダイム ◦ 宣言型:「何をなすべきか」を表現していくパラダイム • 手続き型プログラミング(Procedural Programming) ◦ 主に 文(Statement) の記述で進めるパラダイム(命令型の一種) • オブジェクト指向プログラミング(Object Oriented Programming) ◦ オブジェクト を主体としたパラダイム(命令型の一種) ◦ オブジェクトの生成・管理、その組合せ、振る舞い定義などでプログラミング
  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次元配列)の偶数を抽 出し、それらを2乗し、合計する ◦ 手続き型:要素を for ループで回しなが ら if で条件分岐し加工し集計する
  9. コード例 (1): リストのフィルタリングと集計 # 関数型スタイル function sum_of_squares_functional(lst) filtered = filter(iseven,

    lst) transformed = map(x -> x^2, filtered) sum(transformed) end • 要件:リスト(1次元配列)の偶数を抽 出し、それらを2乗し、合計する ◦ 手続き型:要素を for ループで回しなが ら if で条件分岐し加工し集計する ◦ 関数型:抽出・加工・集計をそれぞれ実現 する式(関数)の組合せを記述する
  10. コード例 (1): リストのフィルタリングと集計 # 関数型スタイル(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 ループで回しなが ら if で条件分岐し加工し集計する ◦ 関数型(1):抽出・加工・集計をそれぞれ 実現する式(関数)の組合せを記述する ◦ 関数型(2):内包表記 (ジェネレータ表 記)を用いて抽出と加工を簡潔かつ直感 的に書くことも可能(しかも高パフォーマ ンス) ◦ function キーワードを伴わない インラ イン関数定義 もあり
  11. コード例 (2): フィボナッチ数 # 手続き型スタイル function fib_procedural(n) a, b =

    0, 1 for _ in 1:n a, b = b, a + b end return a end • 要件:整数 n を与えて、n 番目の フィボナッチ数を計算して返す ◦ 手続き型:for ループで各項を順に計算
  12. コード例 (2): フィボナッチ数 # 関数型スタイル(再帰、O(2ⁿ)) function fib_functional(n) n == 0

    && return 0 n == 1 && return 1 fib_functional(n-1) + fib_functional(n-2) end • 要件:整数 n を与えて、n 番目の フィボナッチ数を計算して返す ◦ 手続き型:for ループで各項を順に計算 ◦ 関数型:一般項の定義をそのまま記述(再 帰関数利用) ▪ ただしパフォーマンスが良いとは限 らない(※要:メモ化等)
  13. コード例 (2): フィボナッチ数 # 関数型スタイル(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):ループを再帰で表現すること で関数呼び出しだけで実現
  14. FP in Julia の利点(概観) # 多重定義 double(n::Number) = 2n #

    数値を2倍する double(s::AbstractString) = s^2 # 文字列を二重化する # 多重ディスパッチ map(double, [1, 2, 3]) #> [2, 4, 6] map(double, ["A", "B", "C"]) #> ["AA", "BB", "CC"] map(double, [1, "B", π]) # `π` は円周率を表す定数 #> [2, "BB", 6.283185307179586] • Julia は関数の 多重定義 ができる ◦ 引数の違いで実装を分けることができる (≒オーバーロード) ◦ if 文を減らし『機能実装』に集中できる • 実行時に引数の型に応じて実装が自 動的に選択される(=多重ディス パッチ) ◦ 関数は 意味・機能 重視で抽象化 ◦ 使う側は実引数の違いを意識しなくて良 い
  15. Julia 用語解説:多重ディスパッチ • 多重定義 ◦ Julia は引数の組合せ(=型シグニチャ)の違いで共存定義できる(=多重定義)。 • メソッド ◦

    Julia では型シグニチャに合致した関数実装のことを メソッド と呼ぶ。 ◦ Juliaで メソッドを追加 といったら、「(同名の)関数を型シグニチャの違いで多重定義すること」 を意味する。 • 多重ディスパッチ ◦ 多重定義された関数は、実行時 の引数の型に合ったメソッドが選択され実行される(=多重ディ スパッチ)。 ◦ ※多くのオブジェクト指向言語が備えているのは引数のうち1つ(レシーバ)だけがディスパッチの 解決対象となるシングルディスパッチ(Java, C#, Python 他) ◦ ※オーバーロードとの違いは解決のタイミング(静的ではなく動的)
  16. Julia 用語解説:型推論 と JITコンパイル • Julia の JITコンパイル ◦ Juliaでは関数単位に、実行されるべきメソッドが確定した時点で、その型シグニチャに合わせて

    関数実体がコンパイルされる(=(Julia の)JITコンパイル)。 ◦ 『よく使われる関数が優先的にコンパイルされる』のではなく、『すべてのコードが実行時に(未コ ンパイルだったら)コンパイルされ、ネイティブコード が実行される』(Julia の高速性要因の1 つ)。 • Julia の 型推論 ◦ Julia はJITコンパイル時(コンパイル前)に、引数の型から関数定義内の各変数と戻り値の型を 確定する(=(Julia の)型推論) ◦ 型が確定することで適切なコード最適化が働く(Julia の高速性要因の1つ)。
  17. FP in Julia の利点(補足) # 《コード再掲+補足》 # 多重定義(コンパイル単位をコンパクトにして最適化しやすく) double(n::Number) =

    2n # 数値を2倍する double(s::AbstractString) = s^2 # 文字列を二重化する # 多重ディスパッチ map(double, [1, 2, 3]) #> [2, 4, 6] map(double, ["A", "B", "C"]) #> ["AA", "BB", "CC"] # ↓実行時の型でディスパッチするのでこういうのも期待通りに動作 map(double, [1, "B", π]) # `π` は円周率を表す定数 #> [2, "BB", 6.283185307179586] • Julia は 関数単位の抽象化 が肝要 ◦ 関数に機能(意味)を持たせる ◦ 引数で実装を分ける ◦ その組み合わせでプログラム作成 ◦ ↑まさに FP • FP は Julia の 多重ディスパッチ とも JITコンパイル(+型推論) とも 相性が良い! • 『Javaのように硬すぎることはなく Haskellのように抽象的すぎない』 言語仕様も重要因子
  18. Julia の 文 と 式 • Julia はすべて 式 ◦

    どのようなプログラム片も 値 を持つ(評価すると値が得られる) ◦ 一般的な手続き型言語で 文 と呼ばれるものも Julia では 式: ▪ 代入式(値は右辺の値) ▪ if 式(値は最後に評価した式の値) ▪ try 式(値は try/catch/else 節内で最後に評価した式の値) ▪ for 式、while 式(値は nothing) ◦ 式なので、例えば結果を他の変数に代入ができる
  19. Julia の 関数 (1) # 関数定義例(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 不要)
  20. Julia の 関数 (2) # 先ほどの例 double(n::Number) = 2n double(s::AbstractString)

    = s ^ 2 # 追加の例 double(x, y) = string(double(x), double(y)) double(x::Number, y::Number) = double(x) + double(y) double(3.14) # == 2 * 3.14 #> 6.28 double(1, 2.3) # == 2 * 1 + 2 * 2.3 #> 6.6 double("ABC", "XYZ") # == "ABC"^2 * "XYZ"^2 #> "ABCABCXYZXYZ" double("ABC", 1) # == string("ABC"^2, 2 * 1) #> "ABCABC2" • Julia の関数は 多重定義 できる (多重ディスパッチ)(2回目) ◦ ≒型レベルの パターンマッチング ◦ Elixir の 引数マッチングにちょっと似て る(ただし 値ディスパッチ はできない)
  21. Julia の 型システム (1) abstract type AbstractTime end struct MyTime

    <: AbstractTime hour::Int minute::Int second::Int end mytime = MyTime(12, 34, 56) #> MyTime(12, 34, 56) mytime.hour #> 12 mytime.second = 0 #@ ERROR: setfield!: immutable struct of type MyTime cannot be changed • 複合型(Composite Type) =構造体(Structure) ◦ Julia の型はクラスではない (Julia には クラスはない) ◦ あと 継承もない(派生のみ) • Julia の型は immutable (フィールドへの再代入はでき ない!) ◦ mutable struct 〜 end で 定義した構造体は mutable (型レベルでの制御)
  22. Julia の 型システム (2) # 文字列型 Union{} <: String <:

    AbstractString <: Any #> true # 整数型 Union{} <: Int <: Signed <: Integer <: Number <: Any #> true # MyTime は1つ前のスライドで定義した型 Union{} <: MyTime <: AbstractTime <: Any #> true • Any 型 ◦ 所謂「トップ型」、すべての型の基本型 ◦ 他言語の Object 型(Java, etc) 等 に相当 • Union{} 型 ◦ 所謂「ボトム型」、すなわちすべての型 の派生型 ◦ Haskell の Void や TypeScriptの never 等に相当 ◦ Julia では『(例外が発生するために) 何も値を返さない式の型』を表す • <: 演算子 (型派生演算子) ◦ 左オペランドが右オペランドの派生型 なら true
  23. Julia の 型システム (3) # 文字列型と整数型の `Union` 型 const StringOrInt

    = Union{String, Int} #> Union{Int64, String} # 順番が入れ替わっている String <: StringOrInt #> true Int <: StringOrInt #> true Union{} <: StringOrInt <: Union{AbstractString, Number, AbstractArray} <: Any #> true • Union 型 ◦ TypeScript の Union 型と大体 同じ ◦ 共変 (Covariant) ◦ ※(型理論でいうところの) 直和型 とは別物!
  24. おまけ (1):Julia の イテレータ struct FibSequence end; Base.iterate(itr::FibSequence) = iterate(itr,

    (0, 1)); Base.iterate(::FibSequence, (a, b)) = (a, (b, a + b)); Iterators.takewhile(<(100), FibSequence()) |> collect #> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] #= # 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 と 同じ仕組みに実はなっている
  25. おまけ (2):その他 Julia の FP 関連要素 • 並行・並列プログラミング ◦ 関数型プログラミング(関数ベースの抽象化)は並行・並列コンピューティングと相性

    が良い ◦ Julia 標準の 並行・並列処理(Task、Thread、MultiProcessing)も関数ベース の抽象化の仕組みを十二分に利用 • メタプログラミング ◦ Expr 型(AST)の評価(eval())は 型なしラムダ計算 に基づく考え(Lisp 由来) ◦ 生成関数(引数の型が確定(実行時)したときにbodyを動的生成する関数) ◦ マクロ(ASTを受け取って加工したASTを返す関数ベースのマクロ)
  26. おまけ (3):Julia に ない FP 関連要素 • Julia には 構造型・構造的部分型

    はない ◦ 基本は公称型・公称的部分型(名前ベース型付け)、およびパラメトリックサブタイピング • Julia には パターンマッチ構文 はない ◦ 型レベルパターンマッチ的なもの(=関数の多重定義)だけはある ◦ マクロを使って実現は可能(※後述)
  27. 関数合成 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 標準で 関数合成演算子 ∘ が 存在
  28. 高階関数 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] • 関数を引数に受け取る関数 ◦ 加工・判定などの処理を引数に受け取っ た関数に委任 ◦ 関数は第一級オブジェクトなので普通に 実現可能 ◦ (一部の例外を除く)演算子も関数なので 同様に扱える
  29. 部分適用 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) と同等) もあります ◦ Julia v1.12 からは Base.Fix{N}(«op», «val») で第 N 引数を固定(部分適用)も可能
  30. パイプライン演算子(パイプ演算子) 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») と同じ意味(関数適用) ◦ F# 由来 • Chain できる • 関数合成演算子 ∘ とも相性良い
  31. 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” にリ ネームしたい』 らしい
  32. 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 に導入 • シーケンス(イテレータ)を受け取り、 フィルタリング・加工等の ロジックの 連鎖 をしていく仕組み(遅延リスト を生成) ◦ ↑ 無限リストも扱える!ということ • マルチスレッド・マルチプロセスにも 対応
  33. HolyMonads.jl 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 に導入 (拙作) ◦ General(パッケージディレクトリ)に登 録されたので誰でも使えます! • 既存の型も Monad のシステムに 組み込める! ◦ Maybe モナドは Julia 標準の Some と Nothing をそのまま利用
  34. まとめ • Julia でも意外としっかりと FP できる! • むしろ Julia は

    FP と親和性高い! • FP 楽しいよ! • Julia 楽しいよ!
  35. 参考リンク等 (1): Julia 関連 • julialang.org(Julia 本家サイト) ◦ Julia Documentation

    • Wikipedia[ja]: Julia (プログラミング言語) (https://ja.wikipedia.org/wiki/Julia_(プログラミング言 語))
  36. 参考リンク等 (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/副作用_(プログラム))
  37. 参考リンク等 (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 ◦ Documents(DeepWiki 利用): https://deepwiki.com/antimon2/HolyMonads.jl
  38. • Julia プログラマ同士の交流を目的とした 日本語コミュニティ (2024年始動) • 学生・社会人・研究者等 どなたでも参加OK! • 英語に自信のない方も参加OK!(やり取りはすべて日本語)

    • Julia に少しでも興味を持ちましたらぜひ Discord サーバ にご参加を! JuliaLangJa 参加はこちらから!↓→ https://julialangja.github.io
  39. JuliaTokai • Julia の東海地方ユーザグループ (JuliaLangJa サブセクション) • 定期的にオンライン勉強会開催 ◦ 会場は

    JuliaLangJa Discordサーバ内 ◦ オンラインなので東海地方に限らず地域・所属関係なくどなたも参加OK! • 次回勉強会: 2025/06/22 JuliaTokai #22 https://juliatokai.connpass.com/