Share
Embed
Julia でどうしても super().hoge みたいなことしたい人へ for JuliaTokai #18
Julia でどうしても super().hoge みたいなことしたい人へ
JuliaTokai #18 発表資料
GOTOH Shunsuke
March 16, 2024
Transcript
58 slides
-
お品書き • お前誰よ? • 簡単な Julia の紹介 • Julia本1周年 •
Julia で部分型多相のエミュレーション -
自己紹介 • 名前:後藤 俊介 • 所属:有限会社 来栖川電算 • コミュニティ:🌟JuliaTokai, 🌟機械学習名古屋,
⭐JuliaLangJa, Ruby東海, Python東海, … • 言語:Julia, Python, Ruby, … • SNS等: (@antimon2) • SNS等(2): (@antimon2.jl) • 著書:実践Julia入門 -
Julia とは?(1) • The Julia Language • 最新 v1.10.2(2024/03/01) ◦
LTS:v1.6.7(2022/07/19) ◦ 次期:v1.11.0-α1(2024/03/01) • 科学技術計算に強い! • 動作が速い!(LLVM JIT コンパイル) -
Julia とは?(2) • Rのように中身がぐちゃぐちゃでなく、 • Rubyのように遅くなく、 • Lispのように原始的またはエレファントでなく、 • Prologのように変態的なところはなく、
• Javaのように硬すぎることはなく、 • Haskellのように抽象的すぎない ほどよい言語である 引用元:http://www.slideshare.net/Nikoriks/julia-28059489/8 -
Julia とは?(3) • C のように高速だけど、 Ruby のようなダイナミズムを併せ持っている • Lisp のような真のマクロを持ちながら、
MATLAB のような直感的な数式表現もできる • Python のように総合的なプログラミングができて、 R のように統計処理も得意で、 Perl のように文字列処理もできて、 MATLAB のように線形代数もできて、 shell のように複数のプログラムを組み合わせることもできる • 超初心者にも習得は容易でありながら、 ハッカーの満足にも応えられる • インタラクティブな動作環境もあって、コンパイルもできる (Why We Created Julia から抜粋・私訳) -
おさらい(1): ポリモーフィズム(多態性・多相性) • 共通のインターフェースで異なる対象を統一的に扱うこと • 言い換えると、「同じ関数(メソッド)で、引数(やレシーバ)の違いで振る舞いを変 えられるような機能・実装」 • Julia のコンテキストで言い直すと、「同じ識別子(関数名)だけど型シグニチャ
(引数の型や組合せ)の違いで振る舞いを変えられる機能」 ⇒ 多重ディスパッチ によって与えられる機能 • 主なポリモーフィズムの種類: ◦ アドホック多相 ◦ パラメータ多相 ◦ 部分型多相 -
おさらい(2): 部分型多相性(サブタイピング多相) • 共通の基本型に対するインターフェースを提供すること • クラスベースの言語においては、例えば 『型C が 型A を継承したクラスであるとき、型C
のオブジェクトを 型A に キャスト(アップキャスト)することで 型A のメソッドを利用できる』 これがコア。 • Julia にはこの機能は(直接的には)ない! ◦ Julia はそもそもクラスもないし継承もない! ◦ Julia には具象型から具象型へのサブタイピングもない ⇒キャスト(アップキャスト・ダウンキャスト)という概念がそもそも存在しない! ◦ あと Python の super().hoge のような仕組みもない! -
おさらい(3): 部分型多相性(サブタイピング多相) • 共通の基本型に対するインターフェースを提供すること • クラスベースの言語においては、例えば 『型C が 型A を継承したクラスであるとき、型C
のオブジェクトを 型A に キャスト(アップキャスト)することで 型A のメソッドを利用できる』 これがコア。 • Julia にはこの機能は(直接的には)ない! ◦ Julia はそもそもクラスもないし継承もない! ◦ Julia には具象型から具象型へのサブタイピングもない ⇒キャスト(アップキャスト・ダウンキャスト)という概念がそもそも存在しない! ◦ あと Python の super().hoge のような仕組みもない! • ↑と書いたが実はウソだ! -
AbstractTime 型, MyTime 型 (仕様) • 時分秒を取り扱う型 • 文字列化(出力)する と
hh:mm:ss のよう に出力される 例→ MyTime1 <: AbstractTime mytime1 = MyTime(14, 28, 57); println(mytime1) ## 14:28:57 string(mytime1) #> "14:28:57" -
MyTime2 型 (仕様) • コンストラクタの引数 に 0時0分0秒 から の秒数1つだけを受け 取る時刻型
• 文字列化(出力)する と hh:mm:ss (XXXsec(s).) のよ うに出力される 例→ MyTime2 <: AbstractTime mytime2 = MyTime2(10000); # 午前0時の10000秒後は 2時46分40秒 println(mytime2) ## 02:46:40 (10000sec(s).) string(mytime2) #> "02:46:40 (10000sec(s).)" -
MyTimeWithMS 型 (仕様) • 時分秒に加えてミリ秒 も取り扱う型 • 文字列化(出力)する と hh:mm:ss.SSS
のように出力される 例→ MyTimeWithMS <: AbstractTime mytime3 = MyTimeWithMS(12, 34, 56, 789); println(mytime3) ## 12:34:55.789 string(mytime3) #> "12:34:55.789" -
参考:MyTimes.py (Python での実装例) from abc import ABC, abstractmethod class AbstractTime(ABC):
@property @abstractmethod def hour(self) -> int: pass @property @abstractmethod def minute(self) -> int: pass @property @abstractmethod def second(self) -> int: pass def __str__(self) -> str: return "{:02d}:{:02d}:{:02d}".format(self.hour, self.minute, self.second) class MyTime(AbstractTime): def __init__(self, hour, minute, second) -> 'MyTime': self._hour = hour self._minute = minute self._second = second @property def hour(self) -> int: return self._hour @property def minute(self) -> int: return self._minute @property def second(self) -> int: return self._second class MyTime2(AbstractTime): def __init__(self, seconds) -> None: self._seconds = seconds @property def hour(self) -> int: return self._seconds // 3600 @property def minute(self) -> int: return self._seconds // 60 % 60 @property def second(self) -> int: return self._seconds % 60 @property def seconds(self) -> int: return self._seconds # override def __str__(self) -> str: return super().__str__() + " ({}sec(s).)".format(self.seconds) class MyTimeWithMS2(MyTime): def __init__(self, h, m, s, ms) -> None: super().__init__(h, m, s) self._ms = ms @property def millisecond(self) -> int: return self._ms # override def __str__(self) -> str: return super().__str__() + ".{:03d}".format(self.millisecond) -
AbstractTime 型、および関連関数 Point: • function hoge end で関数の宣言の ようなことができる •
Julia では文字列化 の定義も Base.show() の多 重定義(=メソッド追 加)で担う abstract type AbstractTime end function gethour end function getminute end function getsecond end function Base.show(io::IO, time::AbstractTime) print(io, string(gethour(time), pad=2), ':', string(getminute(time), pad=2), ':', string(getsecond(time), pad=2)) end -
MyTime 型、および関連関数(メソッド) Point: • MyTime 型は 『hour, minute, second の各フィール
ドを持つ』だけのシン プル実装 • gethour(), getminute(), getsecond() の各 関数はそれを返すだ け struct MyTime <: AbstractTime hour::Int minute::Int second::Int end gethour(time::MyTime) = time.hour getminute(time::MyTime) = time.minute getsecond(time::MyTime) = time.second -
MyTime 型 (動作確認) 《先ほど見たとおり》 mytime1 = MyTime(14, 28, 57); println(mytime1)
## 14:28:57 string(mytime1) #> "14:28:57" -
MyTime2 型、および関連関数(メソッド) (1) Point: • MyTime2 型は 『seconds ただ1つの フィールドを持つ』実
装 • gethour(), getminute(), getsecond() は seconds から算出 • さらに getseconds() も定 義 struct MyTime2 <: AbstractTime seconds::Int end gethour(time::MyTime2) = time.seconds ÷ 3600 getminute(time::MyTime2) = time.seconds ÷ 60 % 60 getsecond(time::MyTime2) = time.seconds % 60 getseconds(time::MyTime2) = time.seconds # ←ココ! -
MyTime2 型、および関連関数(メソッド) (2) Point: • MyTime2 型用に Base.show() を多 重定義(メソッド追加)
• @invoke show(io, time::AbstractTi me) とすることで 基 本型 AbstractTime に対して定義された メソッドを呼び出せ る! function Base.show(io::IO, time::MyTime2) # ↓ `invoke(show, Tuple{IO, AbstractTime}, io, time)` と同じ @invoke show(io, time::AbstractTime) # ←ココ! print(io, " (", getseconds(time), "sec(s).)") end -
MyTime2 型 (動作確認) 《先ほど見たとおり》 mytime2 = MyTime2(10000); # 午前0時の10000秒後は 2時46分40秒
println(mytime2) ## 02:46:40 (10000sec(s).) string(mytime2) #> "02:46:40 (10000sec(s).)" -
MyTimeWithMS 型、および関連関数(メソッド) (1) Point: • MyTimeWithMS 型は 『hms::MyTime と ms::Int
の2つの フィールドを持つ』実 装、内部コンストラク タも定義 • gethour(), getminute(), getsecond() は hms に 委譲 • getmillisecond() も定義 struct MyTimeWithMS <: AbstractTime hms::MyTime ms::Int MyTimeWithMS(h, m, s, ms) = new(MyTime(h, m, s), ms) end gethour(time::MyTimeWithMS) = gethour(time.hms) getminute(time::MyTimeWithMS) = getminute(time.hms) getsecond(time::MyTimeWithMS) = getsecond(time.hms) getmillisecond(time::MyTimeWithMS) = time.ms -
MyTimeWithMS 型、および関連関数(メソッド) (2) Point: • MyTimeWithMS 型用 に Base.show() を
多重定義(メソッド追 加)、MyTimeWith2 型の時と同様(以下 略) function Base.show(io::IO, time::MyTimeWithMS) # ↓ `invoke(show, Tuple{IO, AbstractTime}, io, time)` と同じ @invoke show(io, time::AbstractTime) # ←ココ! print(io, '.', string(getmillisecond(time), pad=3)) end # ※以下のような実装でもOK #= function Base.show(io::IO, time::MyTimeWithMS) print(io, time.hms, '.', string(getmillisecond(time), pad=3)) end =# -
MyTimeWithMS 型 (動作確認) 《先ほど見たとおり》 mytime3 = MyTimeWithMS(12, 34, 56, 789);
println(mytime3) ## 12:34:55.789 string(mytime3) #> "12:34:55.789" -
Point • @invoke マクロ(invoke() 関数)を用いると、「基本型に対するメソッドを選 択 して呼び出す」ことができる! ◦ Python の
super().hoge よりは、Java などの静的型付き言語の「アップキャスト(+ 基本 型のメソッド呼び出し)」の方がイメージ近い • 指定する型は 対象オブジェクトの型の基本型 でなければならない ◦ 先の例なら time isa AbstractTime なので time::AbstractTime と指定できた ◦ !(time isa MyTime) なら time::MyTime と書くことはできない(実行時エラー) • その他注意点 ◦ @invoke は Julia v1.7 で追加されて v1.9 で Base でエクスポートされた ◦ のでお使いの Julia が v1.7/v1.8 なら Base.@invoke と書く必要がある ◦ Julia v1.6 なら invoke(fn, Tuple{Foo, …}, args…) のようにする必要あり -
AbstractTime 型、および関連関数 Point: • @invoke の場合と前 半は同じ • getseconds() と
getmillisecond() のデフォルト実装も用 意しておく(後で説 明) abstract type AbstractTime end function gethour end function getminute end function getsecond end # ↓のデフォルト実装も先にしておく getseconds(time::AbstractTime) = (gethour(time) * 60 + getminute(time)) * 60 + getsecond(time) getmillisecond(::AbstractTime) = 0 -
トレイト型、およびデフォルト実装 Point: • 抽象型 TimeStyle とその派生型3種 Simple , WithSeconds ,
WithMS を定義 • TimeStyle(SomeTi me) がデフォルトで Simple() を返すよ う実装しておく # Trait types abstract type TimeStyle end struct Simple <: TimeStyle end struct WithSeconds <: TimeStyle end struct WithMS <: TimeStyle end # トレイト型のデフォルト= `Simple()` TimeStyle(::Type{<:AbstractTime}) = Simple() -
Base.show() および showtime() 関数の実装 (1) Point: • Base.show(io, time::T) は
showtime(io, TimeStyle(T), time) を呼ぶだけの 実装 • showtime() の第2 引数 Simple() のメ ソッドはデフォルト実 装 Base.show(io::IO, time::T) where {T <: AbstractTime} = showtime(io, TimeStyle(T), time) function showtime(io::IO, ::Simple, time) print(io, string(gethour(time), pad=2), ':', string(getminute(time), pad=2), ':', string(getsecond(time), pad=2)) end -
Base.show() および showtime() 関数の実装 (2) Point: • showtime() の第2 引数が
WithSeconds(), WithMS() のメソッ ド実装 • その中で showtime(io, Simple(), time) を呼び出していること に注目! • あと getseconds() と getmillisecond() も利 用していることにも注目! function showtime(io::IO, ::WithSeconds, time) showtime(io, Simple(), time) # ←ココ! print(io, " (", getseconds(time), "sec(s).)") end function showtime(io::IO, ::WithMS, time) showtime(io, Simple(), time) # ←ココ! print(io, '.', string(getmillisecond(time), pad=3)) end -
MyTime 型、および関連関数(メソッド) Point: • MyTime 型は @invoke のときと全 く同様(なので詳細割 愛)
struct MyTime <: AbstractTime hour::Int minute::Int second::Int end gethour(time::MyTime) = time.hour getminute(time::MyTime) = time.minute getsecond(time::MyTime) = time.second -
MyTime 型 (動作確認) • 特に多重定義してい ないので TimeStyle(MyTime ) === Simple()
となることに注目 • なので showtime(io, Simple(), time) が呼ばれて hh:mm:ss 形式で出 力 TimeStyle(MyTime) === Simple() #> true mytime1 = MyTime(14, 28, 57); println(mytime1) ## 14:28:57 string(mytime1) #> "14:28:57" -
MyTime2 型、および関連関数(メソッド) Point: • MyTime2 型の定義は @invoke の時とほぼ 同じ •
違うのは TimeStyle(::Type {MyTime2}) = WithSeconds() と いう定義を加えてい ることと、 Base.show() のメ ソッド追加はしていな いこと! struct MyTime2 <: AbstractTime seconds::Int end gethour(time::MyTime2) = time.seconds ÷ 3600 getminute(time::MyTime2) = time.seconds ÷ 60 % 60 getsecond(time::MyTime2) = time.seconds % 60 getseconds(time::MyTime2) = time.seconds # override TimeStyle(::Type{MyTime2}) = WithSeconds() # ←ココ! -
MyTime2 型 (動作確認) • TimeStyle(MyTime 2) === WithSeconds() だったので showtime(io,
WithSeconds(), time) が呼ばれて hh:mm:ss (XXXsec(s).) 形式 で出力 mytime2 = MyTime2(10000); # 午前0時の10000秒後は 2時46分40秒 println(mytime2) ## 02:46:40 (10000sec(s).) string(mytime2) #> "02:46:40 (10000sec(s).)" -
MyTimeWithMS 型、および関連関数(メソッド) Point: • MyTimeWithMS 型も MyTime2 型と同様、 前半は同じ •
TimeStyle(::Type {MyTimeWithMS}) = WithMS() という 定義を加えており、 Base.show() のメ ソッド追加はしていな い! struct MyTimeWithMS <: AbstractTime hms::MyTime ms::Int MyTimeWithMS(h, m, s, ms) = new(MyTime(h, m, s), ms) end gethour(time::MyTimeWithMS) = gethour(time.hms) getminute(time::MyTimeWithMS) = getminute(time.hms) getsecond(time::MyTimeWithMS) = getsecond(time.hms) getmillisecond(time::MyTimeWithMS) = time.ms TimeStyle(::Type{MyTimeWithMS}) = WithMS() # ←ココ! -
MyTimeWithMS 型 (動作確認) • TimeStyle(MyTime WithMS) === WithMS() だったの で
showtime(io, WithMS(), time) が呼ばれて hh:mm:ss.SSS 形式 で出力 mytime3 = MyTimeWithMS(12, 34, 56, 789); println(mytime3) ## 12:34:55.789 string(mytime3) #> "12:34:55.789" -
Point • Holy トレイト は、(継承やサブタイピングに依らずに) 実装の共有・共通化をする仕組み • これを利用して 『どう出力するか』というトレイト型 を用意して、サブタイプでそ
れぞれ出力方法を定義できる(いちいち Base.show() を再実装する必要がな い) • さらに トレイト型を直接指定することで 『共通の実装を呼び出す』 という挙動も 実装可能 ⇒トレイトに上手に役割を与えれば 基本型のメソッドを呼ぶ が実現できる! • さらに… -
おまけ: 指定したスタイルで文字列化 • showtime() の第2 位引数違いの実装中 で showtime(io, Simple(), time)
を呼ぶことで hh:mm:ss 形式で出 力できてた • ならばそれ以外を指 定すれば任意の型(<: AbstractTime) で 任意の形式で出力で きる! Base.string(time::T; style=TimeStyle(T)) where {T<:AbstractTime} = sprint(showtime, style, time) string(mytime) #> "14:28:57" string(mytime, style=Simple()) #> "14:28:57" string(mytime, style=WithSeconds()) #> "14:28:57 (52137sec(s).)" string(mytime, style=WithMS()) #> "14:28:57.000" # `mytime2` (isa MyTime2), `mytime3` (isa MyTimeWithMS) も同様 -
ここまでのまとめ • Holyトレイト により享受される機能は 部分型多相による機能を 超える! ◦ 部分型多相でできることは全部できるしそれ以上のことができる • @invoke
と比較して ◦ サブタイピング関係に依らずに機能を提供できる(@invoke は基本型のメ ソッドしか選択出来ない) ◦ パフォーマンスはやや落ちる(ワンクッションあるのでオーバーヘッドがあり 多少は仕方ない) ◦ トレイト設計が少し面倒(設計がしっかりしていれば使う方は FooStyle(::Type{Hoge}) = Fuga() の1行を追加定義するだけ!) -
結論 • Holyトレイト(多重ディスパッチ)があるから Julia には 部分型多相 いらない! • みんな Holyトレイト
極めよう! • Julia 楽しいよ! -
参考文献・リンク等 • 実践Julia入門(拙著) • julialang.org(Julia 本家サイト) • 実験 notebooks(nbviewer) ◦
MyTimes.py.ipynb(Python による参考実装) ◦ MyTimeWithInvoke.jl.ipynb(@invoke 利用実装) ◦ MyTimeByHolyTraits.jl.ipynb(Holyトレイト利用実装)