Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

型と多重ディスパッチ for 数学と物理におけるJuliaの活用 2023-07-10

型と多重ディスパッチ for 数学と物理におけるJuliaの活用 2023-07-10

『数学と物理におけるJuliaの活用』研究会 チュートリアル講演(2) 講演資料スライド(2023/07/10)

GOTOH Shunsuke

July 11, 2023
Tweet

More Decks by GOTOH Shunsuke

Other Decks in Technology

Transcript

  1. 本講義の予定 1. はじめに(5分) 2. 型システムの基本 (10分) 3. 複合型と抽象型 (10分) 4.

    多重ディスパッチの基本(10分) 5. 実習:Julia の多重ディスパッチと型定義(10分) 6. まとめ(5分)
  2. 講義の目的と目標 • 型(の基本)の理解 ◦ 型とは何か? ◦ Julia の型システム ◦ 型の定義

    ◦ 型パラメータ、型制約 • 多重ディスパッチ(の基本)の理解 ◦ 多重ディスパッチとは? ◦ メソッドの選択の仕組み
  3. 実験コード • 本講演資料に記載のサンプルコードを 以下のURLで公開しています。 https://antimon2.github.io/julia_imi_workshop2023_tutorial/1st_examp le_type_md.html • チュートリアル(1) で Pluto.jl

    を導入済の方は、上記URL→[Edit or Run] ボタン→「On your computer」の手順に従って手元の環境で実行できます。 • 実習課題も編集可能な状態で用意してありますので ご利用ください。
  4. 型とは何か(1) プログラミング全般において… • その値(オブジェクト) が『何』であるかを表 すもの。 • (Julia の場合) typeof()

    関数でそ の値の型が分かる julia> typeof(1) Int64 julia> typeof("文字列") String julia> typeof([1.0, 2.0, 3.0]) Vector{Float64} (alias for Array{Float64, 1})
  5. Julia の型システム(1) Julia の型システムの基本… • 公称型システム (Nominative Type System) ◦

    名前で型が決まる! • 公称的サブタイピング (Nominal Subtyping) ◦ 明示的な宣言で型の 基本型-派生型の関 係が決まる! • typeof() 関数、 <: 演算 子、isa 演算子等 julia> typeof(1) === Int true # Int という型は唯一 julia> Int <: Signed <: Integer <: Real <: Number <: Any true # ↑ `<:` は派生型演算子(左辺が右辺の派生型なら `true`) julia> 1 isa Number true # `a isa T` は `typeof(a) <: T` と(ほぼ)等価
  6. Julia の型システム(2) サブタイピングについて… • (直接の)基本型はただ1 つだけ ◦ supertype() 関数 •

    派生型はいくらでもあり うる ◦ subtypes() 関数 • Any型 ◦ 全ての型の基本型 julia> supertype(Int) Signed julia> supertype(String) AbstractString julia> subtypes(AbstractString) # 環境によって結果は変る 6-element Vector{Any}: Core.Compiler.LazyString LazyString String SubString SubstitutionString Test.GenericString julia> length(subtypes(Any)) # 環境によって結果は変る 587
  7. 代表的な型の紹介(1) 整数型 • 符号付き整数 ◦ Int8, Int16, Int32, Int64, Int128

    ◦ Int は Int64 のエイリアス • 符号なし整数 ◦ UInt8, UInt16, UInt32, UInt64, UInt128 ◦ UInt は UInt64 のエイリア ス • 多倍長整数(BigInt) • リテラル ◦ 10進表記なら符号付き、16 進表記(など)なら符号なし ◦ 桁数や大きさで型が自動的に 決まる julia> typeof(4294967296) Int64 julia> typeof(9223372036854775808) Int128 julia> typeof(170141183460469231731687303715884105728) BigInt julia> typeof(0x01) UInt8 julia> typeof(0x0123456789abcdef) UInt64
  8. 代表的な型の紹介(2) 浮動小数点数型 • Float16, Float32, Float64 • BigFloat • リテラルは様々な書式が

    ある julia> typeof(1.0) === typeof(0.) === typeof(-.0) === Float64 true # よくある表記は大抵 Float64 julia> typeof(1e308) === typeof(0x193p-2) === Float64 true # `0x《16進表記》p《2の冪数》`という書式もOK julia> typeof(3.14f0) Float32 # `xxxfyy` と言う書式にすると Float32 になる julia> Float16(3.1416) # Float16 は明示的に指定 Float16(3.14) julia> big(π) # BigFloat の例 3.1415926535897932384626433832795028841971693993751058209749445 92307816406286198
  9. 代表的な型の紹介(3) その他の数値型 • 有理数 ◦ ◦//△ • 複素数 ◦ ◦

    + △im julia> typeof(1//2) Rational{Int64} julia> -12//20 -3//5 # 結果は適宜約分される julia> 3//0 # これもOK(`0//0` のみエラー) 1//0 julia> im # 虚数単位(定数として定義済) im julia> typeof(1 + 2im) Complex{Int64} julia> typeof(0.0 + 1.0im) ComplexF64 (alias for Complex{Float64})
  10. 代表的な型の紹介(4) 文字型と文字列型 • Julia では 文字型 と 文字列型 は別物 •

    シングルクォーテーション で括ったものは文字型 (Char) • ダブルクォーテーションで 括ったものは文字列型 (String) julia> typeof('a') Char julia> typeof("a") String julia> typeof(""" 複数行にわたる文字列 これも String 型 """) String
  11. 代表的な型の紹介(5) 配列型 • Julia では(同じ型の)値 の列はリストではなく(1 次元)配列 • Julia の配列は1次元だ

    けでなく 多次元配列 も OK • 要素の型と次元数は型パ ラメータ(後述)で表現 julia> typeof([1.0, 2.0, 3.0]) Vector{Float64} (alias for Array{Float64, 1}) # ↑リストではなくベクトル(=1次元配列) julia> typeof([ 1 2 3 4 ]) # 行列(=2次元配列) Matrix{Int64} (alias for Array{Int64, 2}) julia> typeof([1;2;;3;4;;;5;6;;7;8]) Array{Int64, 3} # 2×2×2 の3次元配列
  12. 代表的な型の紹介(6) 辞書型・集合型 • 辞書型(Dict):キーと値 のマッピングで管理するコ レクション • 集合型(Set):重複を許さ ない値のコレクション •

    配列型、辞書型、集合型と 後述のタプル・名前付きタ プルを総称して コレク ション型 と言う julia> Dict("Alice"=>1, "Bob"=>2, "Carol"=>3) Dict{String, Int64} with 3 entries: "Carol" => 3 "Alice" => 1 "Bob" => 2 # 結果は順不同 julia> Set([3, 1, 4, 1, 5, 9, 2, 6, 5, 3]) Set{Int64} with 7 elements: 5 # 重複は排除される 4 6 2 9 3 1 # こちらも順不同
  13. 代表的な型の紹介(7) タプル型・名前付きタプル型 • タプル型(Tuple):(同じ 型とは限らない)値の列 • 名前付きタプル型 (NamedTuple):タプル の各値に名前(キー)が紐 付いているもの(辞書に類

    似) • これらは、各値の型も情報 として保持するのが特徴 julia> typeof((1, 'b', "三")) Tuple{Int64, Char, String} julia> typeof((a=1, b='b', c="三")) NamedTuple{(:a, :b, :c), Tuple{Int64, Char, String}}
  14. 型アノテーション • x::T という書式で 型ア ノテーション を付けられ る • 宣言時や代入時の左辺の

    場合は(変数の)型の指定 (変換に失敗するとエ ラー) • 代入時の右辺などの場合 は 型検査 (互換性のない 型だとエラー) • ※型ヒントではない! (重要) julia> let x::Int = sin(π) # == 0.0 なので変換されて 0 が代入される x end 0 julia> let x = (sin(π)::Int) # こちらはエラー(!(sin(π) <: Int) なので) x end ERROR: TypeError: in typeassert, expected Int64, got a value of type Float64 Stacktrace: [1] top-level scope @ REPL[XX]:2
  15. 複合型(1) (構造体) • struct 《型名》 ~ end で 構造体 を定義可能

    • 構造体として定義された 型のことを 複合型 と呼ぶ • ※基本的な型以外のほと んどの型は複合型 (有理数型・複素数型、辞 書型・集合型・名前付きタ プル型も実は複合型) julia> struct SSample x y end julia> ssample = SSample(1, 2) SSample(1, 2) julia> typeof(ssample) SSample
  16. 複合型(2) (フィールドの型指定) • 構造体のフィールドには 型が指定できる(型アノ テーション) • ※型アノテーションが付い ていないフィールドは実 は

    ::Any と同じ意味(詳 細略) • 補足:目的がはっきりして いるときはそのフィールド には型アノテーションを付 けましょう(詳細後述) julia> struct SISample x::Int y::Int end julia> sisample = SISample(1, 2) SISample(1, 2) julia> typeof(sisample.x) Int64 julia> SISample(2.0, 3.14) # `3.14` の方でエラーが発生 ERROR: InexactError: Int64(3.14) # :《以下略》
  17. 複合型(3) (基本型の指定) • 型定義時に基本型を指定 (その型の派生型という宣 言)ができる • ※基本型の指定がない型 定義は、<: Any(Any型

    の派生型の定義)と同じ意 味(詳細略) julia> struct MyDecimal <: Real value::BigInt point::Int end # 固定小数点数を意図した型定義 julia> MyDecimal(1, 0) isa Number true julia> MyDecimal(1, 0) isa Integer false
  18. 抽象型(1) • abstract type 《型名》 end で 抽象型 を定義で きる

    • 抽象型は他の抽象型や複 合型等の基本型にできる (=型階層が作れる) julia> abstract type AbstractFPoint end julia> struct FPoint2D <: AbstractFPoint x::Float64 y::Float64 end julia> struct FPoint3D <: AbstractFPoint x::Float64 y::Float64 z::Float64 end julia> FPoint2D(1.0, 2.0) isa AbstractFPoint true julia> FPoint3D(3, π, 99.9) isa AbstractFPoint true
  19. 型パラメータ(1) • 型定義時に {T} のように 型パラメータ を指定でき る • 型パラメータの使い途:

    ◦ フィールドの型指定 ◦ 性質を表すパラメー タ(フラグ)(詳細後 述) julia> struct TWrapperSample{T} value::T end julia> intwrapper = TWrapperSample(1) TWrapperSample{Int64}(1) julia> typeof(intwrapper) TWrapperSample{Int64} julia> typeof(TWrapperSample("文字列")) TWrapperSample{String}
  20. 型制約(1) • 型パラメータ指定時に {T <: Real} のようにその 型に制約を設けることが できる(型制約) •

    制約に合わない型が来る とエラーになる julia> abstract type AbstractPoint{T <: Real} end julia> struct Point2D{T} <: AbstractPoint{T} x::T y::T end julia> Point2D(1.0, 3.2) isa AbstractPoint{Float64} true julia> Point2D(1 + 0im, 0 + im) ERROR: TypeError: in AbstractPoint, in T, expected T<:Real, got Type{Complex{Int64}} # :《以下略》
  21. 型パラメータ(2) +型制約(2) この形           → は非常によく見るので、   ぜひ覚えておきましょう! 利点: • フィールドの型を特定しな いので柔軟な設計が可能

    • 実行時には確定するので 型安定性 に繋がる • 型制約と組み合わせると さらに賢い設計に! julia> abstract type AbstractPoint{T <: Real} end julia> struct Point2D{T} <: AbstractPoint{T} x::T y::T end
  22. 多重ディスパッチとは(1) 関数の多重定義(1) • Julia では同名の関数を 引数の違いで 多重定義 できる • 個々の実体のことを

    メ ソッド と呼ぶ • 引数の違いとは ◦ 引数の個数の違い ◦ 引数の型の違い ◦ その組み合わせ julia> add(x, y) = x + y add (generic function with 1 method) julia> add(x, y, z) = x + y + z add (generic function with 2 methods) julia> add(x, y, z...) = add(x + y, z...) add (generic function with 3 methods) julia> methods(add) # 3 methods for generic function "add" from Main: [1] add(x, y) [2] add(x, y, z) [3] add(x, y, z...)
  23. 多重ディスパッチとは(2) 関数の多重定義(2) • Julia では同名の関数を 引数の違いで多重定義で きる • 個々の実体のことをメ ソッドと呼ぶ

    • 引数の違いとは ◦ 引数の個数の違い ◦ 引数の型の違い ◦ その組み合わせ julia> double(x) = 2x double (generic function with 1 method) julia> double(s::AbstractString) = s ^ 2 double (generic function with 2 methods) julia> double(x, y) = string(double(x), double(y)) double (generic function with 3 methods) julia> double(x::Number, y::Number) = double(x) + double(y) double (generic function with 4 methods) julia> methods(double) # 4 methods for generic function "double" from Main: # :《略》
  24. 多重ディスパッチとは(3) ディスパッチ • 多重定義された関数は、 呼び出し時の実引数の組 み合わせで適切なメソッド が選択されて実行(=ディ スパッチ)される julia> double("文字列")

    # "文字列" isa AbstractString なので s ^ 2 の実装(メソッド)が選択される "文字列文字列" julia> double(1, 2) # 1 isa Number かつ 2 isa Number なので double(x) + double(y) 6 julia> double(160, "円") # どうしてこうなるのか考えてみよう! "320円円"
  25. Juliaにおける多重ディス パッチの利用例(1) 引数の個数による意味分け • 同じ関数で、引数の個数 によって意味を分ける • 例:log() 関数 •

    例:atan() 関数 julia> log(20) # 自然対数 2.995732273553991 julia> log(5, 20) # 底を5とする対数 1.8613531161467862 julia> atan(0.3) # tan(θ) == 0.3 となる θ 0.2914567944778671 julia> atan(1, 3) # tan(θ) == 1/3 となる θ 0.3217505543966422
  26. Juliaにおける多重ディス パッチの利用例(2) 演算子オーバーロード • 同じ演算子でも両辺(被演 算子)の型で挙動(結果の 値や型)に違いが出る • わかりやすい例:* 演算子

    ◦ 数値同士なら乗算 ◦ 文字列同士なら結合 ◦ 数値同士でも、型の 違いによって適切な 処理 julia> 1 * 2 # 整数どうしの `*` 演算は結果も整数 2 julia> 3.0 * π # 浮動小数点数と無理数の `*` 演算は浮動小数点数 9.42477796076938 julia> "Hello, " * "Julia!" # 文字列同士の `*` 演算は結合 "Hello, Julia!"
  27. お題:double()関数(1) Step1: 型定義 • MyType という型(複合 型)を定義する ◦ フィールドは value

    1つだけ ◦ フィールドの型は型 パラメータ T • ※これに関しては答えを 示しておきます→ julia> struct MyType{T} value::T end julia> MyType(1) MyType{Int64}(1)
  28. お題:double()関数(2) Step2: 関数の多重定義(1) • 先ほど例示した double() 関数を、 MyType 型が扱えるよう に多重定義する。

    • まずは引数に MyType{T} 1つだけを 受け取るメソッドの定義: ◦ フィールド value に double() 関数 を適用した結果を格 納する Mytype を 返す julia> struct MyType{T} value::T end julia> MyType(1) MyType{Int64}(1) julia> double(mytype::MyType) = ... # ここを適切に実装 double (generic function with 5 methods) julia> double(MyType(1)) MyType{Int64}(2) julia> double(MyType("ABC")) MyType{String}("ABCABC")
  29. お題:double()関数(3) Step3: 関数の多重定義(2) • double() 関数を、 MyType 型が扱えるように 多重定義する。 •

    引数を2つ(一方が MyType) を受け取るメ ソッドの定義(難易度高め): ◦ 結果は MyType、た だし MyType{MyType{ ...}} のような入れ子 にならないように注 意! julia> double(x::MyType, y::MyType) = ... # ここを適切に実装 double (generic function with 6 methods) julia> double(x::MyType, y) = ... # ここを適切に実装 double (generic function with 7 methods) julia> double(x, y::MyType) = ... # ここを適切に実装 double (generic function with 8 methods) julia> double(2, MyType(π)) MyType{Float64}(10.283185307179586) julia> double(MyType(35), "v") MyType{String}("70vv") julia> double(MyType("ABC"), MyType("XYZ")) MyType{String}("ABCABCXYZXYZ")
  30. お題:double()関数(4) Extra Step(時間が余った人用) • double() 関数を、他の型 にも適用しよう ◦ 例:文字型⇒文字列と して扱う

    ◦ 例:コレクション型⇒各 要素にdouble()を適 用したコレクションに する ◦ 他⇒自分で仕様を考 えて実装してみよう • 型パラメータの利用 ◦ 型パラメータの違いで 処理を分ける、など julia> double(x::Char) = ... # ここを適切に実装 double (generic function with ? methods) julia> double(x::Tuple) = ... # ここを適切に実装 double (generic function with ? methods) julia> double(x::Array) = ... # ここを適切に実装 double (generic function with ? methods) julia> double((1, `b`, [1.0, 2.0, 3.0])) (2, “bb”, [2.0, 4.0, 6.0]) julia> double(x::MuType{T}) where {T <: Tuple} = ... # ここを適切に実装 double (generic function with ? methods) julia> double(Mytype((1, `b`, “三”))) #> MyType((1, `b`, “三”, 1, `b`, “三”)) # ←一例
  31. 講義の目的と目標(再掲) ✓ 型(の基本)の理解 ☑ 型とは何か? ☑ Julia の型システム ☑ 型の定義

    ☑ 型パラメータ、型制約 ✓ 多重ディスパッチ(の基本)の理解 ☑ 多重ディスパッチとは? ☑ メソッドの選択の仕組み