Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Julia でどうしても super().hoge みたいなことしたい人へ for Julia...

Julia でどうしても super().hoge みたいなことしたい人へ for JuliaTokai #18

Julia でどうしても super().hoge みたいなことしたい人へ
JuliaTokai #18 発表資料

GOTOH Shunsuke

March 16, 2024
Tweet

More Decks by GOTOH Shunsuke

Other Decks in Technology

Transcript

  1. 自己紹介 • 名前:後藤 俊介 • 所属:有限会社 来栖川電算 • コミュニティ:🌟JuliaTokai, 🌟機械学習名古屋,

    ⭐JuliaLangJa, Ruby東海, Python東海, … • 言語:Julia, Python, Ruby, … • SNS等:                  (@antimon2) • SNS等(2):      (@antimon2.jl) • 著書:実践Julia入門
  2. 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 コンパイル)
  3. Julia とは?(2) • Rのように中身がぐちゃぐちゃでなく、 • Rubyのように遅くなく、 • Lispのように原始的またはエレファントでなく、 • Prologのように変態的なところはなく、

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

    MATLAB のような直感的な数式表現もできる • Python のように総合的なプログラミングができて、 R のように統計処理も得意で、 Perl のように文字列処理もできて、 MATLAB のように線形代数もできて、 shell のように複数のプログラムを組み合わせることもできる • 超初心者にも習得は容易でありながら、 ハッカーの満足にも応えられる • インタラクティブな動作環境もあって、コンパイルもできる (Why We Created Julia から抜粋・私訳)
  5. おさらい(2): 部分型多相性(サブタイピング多相) • 共通の基本型に対するインターフェースを提供すること • クラスベースの言語においては、例えば 『型C が 型A を継承したクラスであるとき、型C

    のオブジェクトを 型A に キャスト(アップキャスト)することで 型A のメソッドを利用できる』 これがコア。 • Julia にはこの機能は(直接的には)ない! ◦ Julia はそもそもクラスもないし継承もない! ◦ Julia には具象型から具象型へのサブタイピングもない ⇒キャスト(アップキャスト・ダウンキャスト)という概念がそもそも存在しない! ◦ あと Python の super().hoge のような仕組みもない!
  6. おさらい(3): 部分型多相性(サブタイピング多相) • 共通の基本型に対するインターフェースを提供すること • クラスベースの言語においては、例えば 『型C が 型A を継承したクラスであるとき、型C

    のオブジェクトを 型A に キャスト(アップキャスト)することで 型A のメソッドを利用できる』 これがコア。 • Julia にはこの機能は(直接的には)ない! ◦ Julia はそもそもクラスもないし継承もない! ◦ Julia には具象型から具象型へのサブタイピングもない ⇒キャスト(アップキャスト・ダウンキャスト)という概念がそもそも存在しない! ◦ あと Python の super().hoge のような仕組みもない! •              ↑と書いたが実はウソだ!
  7. AbstractTime 型, MyTime 型 (仕様) • 時分秒を取り扱う型 • 文字列化(出力)する と

    hh:mm:ss のよう に出力される 例→ MyTime1 <: AbstractTime mytime1 = MyTime(14, 28, 57); println(mytime1) ## 14:28:57 string(mytime1) #> "14:28:57"
  8. 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).)"
  9. 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"
  10. 参考: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)
  11. 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
  12. 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
  13. 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 # ←ココ!
  14. 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
  15. MyTime2 型 (動作確認) 《先ほど見たとおり》 mytime2 = MyTime2(10000); # 午前0時の10000秒後は 2時46分40秒

    println(mytime2) ## 02:46:40 (10000sec(s).) string(mytime2) #> "02:46:40 (10000sec(s).)"
  16. 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
  17. 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 =#
  18. 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…) のようにする必要あり
  19. 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
  20. トレイト型、およびデフォルト実装 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()
  21. 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
  22. 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
  23. 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
  24. 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"
  25. 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() # ←ココ!
  26. 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).)"
  27. 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() # ←ココ!
  28. 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"
  29. Point • Holy トレイト は、(継承やサブタイピングに依らずに) 実装の共有・共通化をする仕組み • これを利用して 『どう出力するか』というトレイト型 を用意して、サブタイプでそ

    れぞれ出力方法を定義できる(いちいち Base.show() を再実装する必要がな い) • さらに トレイト型を直接指定することで 『共通の実装を呼び出す』 という挙動も 実装可能 ⇒トレイトに上手に役割を与えれば 基本型のメソッドを呼ぶ が実現できる! • さらに…
  30. おまけ: 指定したスタイルで文字列化 • 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) も同様
  31. ここまでのまとめ • Holyトレイト により享受される機能は 部分型多相による機能を 超える! ◦ 部分型多相でできることは全部できるしそれ以上のことができる • @invoke

    と比較して ◦ サブタイピング関係に依らずに機能を提供できる(@invoke は基本型のメ ソッドしか選択出来ない) ◦ パフォーマンスはやや落ちる(ワンクッションあるのでオーバーヘッドがあり 多少は仕方ない) ◦ トレイト設計が少し面倒(設計がしっかりしていれば使う方は FooStyle(::Type{Hoge}) = Fuga() の1行を追加定義するだけ!)
  32. 参考文献・リンク等 • 実践Julia入門(拙著) • julialang.org(Julia 本家サイト) • 実験 notebooks(nbviewer) ◦

    MyTimes.py.ipynb(Python による参考実装) ◦ MyTimeWithInvoke.jl.ipynb(@invoke 利用実装) ◦ MyTimeByHolyTraits.jl.ipynb(Holyトレイト利用実装)