juliaの多重ディスパッチのありがたみをpythonと比較しながら理解する
プログラミングを学び始めた頃、同じ関数の名前でも引数の型によって関数を区別できる多重ディスパッチのありがたみがピンとこないことがあった。
そこで、本記事は、juliaとpythonと比較することで多重ディスパッチのありがたみについてまとめた。
また、多重ディスパッチした際の関数の実行速度についても少し触れる。
python
以下のpythonコードを見てみる
def f(x: int):
return x
f(1) #1
#以下も通す
f(1.0) #1.0
f("1") #'1'
このようにpythonでは関数の引数に入る変数の型が違くても関数を通してしまう。
また、同名の関数が定義された場合、最後に定義された関数が実行される。
def f(x: int):
return x
def f(x:str):
return x + x
#この場合、最後に定義されたfが実行される
f(1) #2
f("a") #'aa'
Julia
多重ディスパッチが使えるjulia言語は、引数の型に応じて、同一の名前の関数のうちのどれを呼出す(ディスパッチする)かを決定することができる。
具体的には、
function f(x::Int)
return x
end
f(1) #OK
f(1.0) #エラー
f("1") #エラー
このように、型に応じて関数を実行し、定義されていない型に対してエラーを吐くことができる。
また同名の関数が複数定義されている場合、型に応じて関数を選択的に実行できる。
function f(x::Int)
return x
end
function f(x::String)
return x*x
end
f(1) # 1
f("a") # "aa"
このように同じ名前の関数を定義でき、引数の方に応じて選択的に実行してくれる。
速度について
-
多重ディスパッチを使ったと言って速くなるわけではない。
-
関数の引数の型注釈は、計算の速度に影響しない
例えば、以下の2つの関数は同じ速度である。
f(x,y) = x + y
f(x::Int, y::Int) = x + y
f(2, 3) が入ると内部で f(x::Int, y::Int) 用の実装がJITコンパイルされる. 一回コンパイルされればその結果を保持する.
f(-1, -4) のように別の値が渡されても f(x::Int, y::Int)
の情報をもとに実行される。
詳しくは、こちらを参照。
https://docs.julialang.org/en/v1/manual/types/#man-abstract-types
おまけ
おまけ的に、juliaの多重ディスパッチの具体例を2つ紹介して終わりたい。
例1)引数の一番目をとってくる関数
module MyPkg
function get_begin(x::AbstractVector)
return x[1]
end
function get_begin(x::Int)
x = [x] # 配列にしている。
get_begin(x)
end
end
using .MyPkg
julia> MyPkg.get_begin(1)
julia> MyPkg.get_begin([3,2,1])
例2)Plots
Plotsも同名の関数の名前で定義されており、引数に応じて異なる振る舞いを実行している。
using Plots
plot(sin)
plot(cos, sin, 0, π, aspect_ratio=:equal)
plot([x -> x^n for n in 1:2:10], xlim=[-2,2])
まとめ
多重ディスパッチについて例を通して理解を深めた。