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

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

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

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

Avatar for GOTOH Shunsuke

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 の型システム ☑ 型の定義

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