Julia - 定義した method を消す方法

環境

Julia 1.0

無駄な method を消す

Julia といえば多重ディスパッチが有名ですよね*1

julia> f(x::Int64) = println("Intだよ")
f (generic function with 1 method)

julia> f(x::Float64) = println("Floatだよ")
f (generic function with 2 methods)

julia> f(Int64(1))
Intだよ

julia> f(Float64(1))
Floatだよ

こんな感じに同じ関数でも引数の型を指定して定義することで、型に応じて動作を変えることができるので便利です。

しかし、ここで一つ問題があります。 上記の例では引数が Int64 だったら Intだよ と出力するようにしましたが、後々になって Integer の subtype 全体に対して 整数だよ と出力したくなったとしましょう。

素直に考えたら次のように定義すると思います。

julia> f(x::Integer) = println("整数だよ")
f (generic function with 3 methods)

しかし、この定義の仕方だと Int64 の数値を入れると一番初めに定義した method が呼び出されてしまいます。

julia> f(Int32(1))
整数だよ

julia> f(Int64(1))
Intだよ

julia> methods(f)
# 3 methods for generic function "f":
[1] f(x::Float64) in Main at REPL[2]:1
[2] f(x::Int64) in Main at REPL[1]:1
[3] f(x::Integer) in Main at REPL[5]:1

単純な対処法は f(x::Int64) を上書きしてしまうことでしょう。

f(x::Int64) = println("整数だよ")

しかし、また気が変わって Integer の subtype 全体に対して Intだよ と出力したくなった場合、f(x::Int64)f(x::Integer) の2つを上書きしなければなりません。これはどう考えても面倒です。

f(x::Int64) = println("Intだよ")
f(x::Integer) = println("Intだよ")

今は Integer の subtype ならば全て同じ動作にしたいので f(x::Int64) が邪魔です。いっそなかったことにしたいです。

そんな時に便利なのが Base.delete_method です。2018/9/24 現在公式ドキュメントに記述は一切ありません。 which と組み合わせて以下のように使います。

julia> f(Int64(1))
Intだよ

julia> Base.delete_method(@which f(Int64(1)))

julia> f(Int64(1))
整数だよ

julia> methods(f)
# 2 methods for generic function "f":
[1] f(x::Float64) in Main at REPL[2]:1
[2] f(x::Integer) in Main at REPL[5]:1

見事に f(x::Int64) の method が消えました。@which マクロを使う場合は消したい method の具体的な値を入れてください。which を使う場合、

Base.delete_method( which(f, (Int64, ) ) )

と書きます。具体的な値を入れてしまう方が楽だとは思いますが、「この型の method を消してるんだぞ!」と強調したいときは which 使うと良いのではないでしょうか。

とまぁ、無事に不必要な method を消すことができた訳ですが、正直に言ってこの程度のことだったら一度プロセスを切ってもう一度 Julia を起動し直したほうが断然楽ですね。

ものすごく読み込みが遅いパッケージを使っている最中ならば Base.delete_method を使ったほうが楽かもしれませんが、大抵の場合は再起動したほうが早いでしょう。

上記の例ではあまりメリットを感じないので、もう少し実用的なものを考えてみましょう。私は pretty-print 好きなので pretty-print に応用しましょう*2

pretty-print

ここでは複素数極形式で表示するようにしてみましょう。

julia> 1.0 + im
1.0 + 1.0im

julia> function Base.show(io::IO, z::Complex{Float64})
           r = round(abs(z), digits=3)
           θ = round(atan(z.im, z.re), digits=3)
           print(io, r, " * ( cos( ", θ, " ),  sin( ", θ, " ) )")
       end

julia> 1.0 + im
1.414 * ( cos( 0.785 ),  sin( 0.785 ) )

定義した method を消します。

julia> Base.delete_method(which(Base.show, (IO, Complex{Float64})))

julia> 1.0 + im
1.0 + 1.0im

どうですか!今までは pretty-print を自分で定義した場合、元の書式に戻すには再起動するしかなかったのに Base.delete_method を使うことで再起動を回避できたではありませんか!!!

とはいえ、これでは切り替えが面倒なので簡単に切り替えできるように関数を定義しましょう。

julia> 1.0 + im
1.0 + 1.0im

julia> POLAR = false
false

julia> function polarform(x::Bool)
           if !POLAR && x
               eval(quote
                  function Base.show(io::IO, z::Complex{Float64})
                      r = round(abs(z), digits=3)
                      θ = round(atan(z.im, z.re), digits=3)
                      print(io, r, " * ( cos( ", θ, " ),  sin( ", θ, " ) )")
                  end
              end)
              global POLAR = true
              return 
          end

          if POLAR && !x
              Base.delete_method(which(Base.show, (IO, Complex{Float64})))
              global POLAR = false
              return
          end
       end
polarform (generic function with 1 method)

julia> polarform(true)

julia> 1.0 + im
1.414 * ( cos( 0.785 ),  sin( 0.785 ) )

julia> polarform(false)

julia> 1.0 + im
1.0 + 1.0im

polarform(true) にすると極形式で、polarform(false) にすると Cartesian Form*3 で表示することに成功しました!

ついに私は pretty-print の切替方法を確立してしまった。 今後のパッケージ作りに役に立ちそうだ。

落とし穴

先程の pretty-print の例では Base.delete_method を使えばいつでもデフォルトの pretty-print に戻せるように感じたかもしれませんが、そんなことは全くもってありません。

複素数の pretty-print に関しては Complex{Float64} の supertype である Complex にのみ定義されているから先程の例は上手くいっただけで、普通はこんなに上手くいかないので注意してください。

export されてもいなければ、公式ドキュメントにすら書かれていない、エンドユーザーが使うことを想定してない関数がそんなにおいしいわけないですね。

julia> 1.0+im
1.0 + 1.0im

julia> Complex{Float64} <: Complex
true

julia> @which show(stdout, 1.0+im)
show(io::IO, z::Complex) in Base at complex.jl:183

julia> Base.delete_method(@which Base.show(stdout, 1.0+im))

julia> 1.0+im
Complex{Float64}(1.0, 1.0)

julia> @which show(stdout, 1.0+im)
show(io::IO, x) in Base at show.jl:315

julia> Base.delete_method(@which Base.show(stdout, 1.0+im))

julia> @which show(stdout, 1.0+im)
ERROR: no unique matching method found for the specified argument types
Stacktrace:
 [1] which(::Any, ::Any) at ./reflection.jl:922
 [2] top-level scope at none:0

julia> 1.0+im
Error showing value of type Complex{Float64}:
ERROR: MethodError: no method matching display(::Complex{Float64})
Closest candidates are:
  display(::Any) at multimedia.jl:284
  display(::AbstractDisplay, ::AbstractString, ::Any) at multimedia.jl:178
  display(::AbstractString, ::Any) at multimedia.jl:179
  ...
Stacktrace:
 [1] display(::Complex{Float64}) at ./multimedia.jl:294

参考

Limitations · Revise.jl

LICENSE

  • Copyright 2018 goropikari
  • Licensed under MIT:

The MIT License | Open Source Initiative

*1:私はいまいち理解しきれていませんが

*2:むしろ pretty-print のためにこの method の消去方法を覚えました

*3:日本語で何ていうの?