Multiple Dispatch

Memilih metode berdasarkan tipe seluruh argumen sehingga perilaku fungsi terdefinisi per kombinasi tipe.

Daftar isi

Multiple dispatch adalah mekanisme Julia dalam memilih metode yang dijalankan berdasarkan tipe seluruh argumen pemanggilan, bukan tipe satu argumen saja. Satu nama fungsi dapat memuat banyak metode, dan setiap metode menetapkan kombinasi tipe argumen yang dilayani. Pemilihan metode berlangsung setiap kali fungsi dipanggil, dan kumpulan metode dapat ditambahkan kapan saja tanpa mengubah kode lama. Topik ini membahas cara mendefinisikan beberapa metode, peran seluruh argumen dalam pemilihan, ambiguitas metode, serta dispatch pada tipe abstrak dan Union.

Satu fungsi, banyak metode#

Setiap definisi fungsi dengan anotasi tipe membentuk satu metode. Definisi berikutnya dengan anotasi berbeda menambah metode baru di bawah nama fungsi yang sama, tanpa mengubah metode yang sudah ada. Saat fungsi dipanggil, Julia memilih metode paling spesifik yang cocok dengan tipe argumen pemanggilan. Fungsi methods mendaftar seluruh metode sebuah fungsi, sehingga jumlah metode dapat diperiksa dengan length.

julia luas-bangun.jl
struct Persegi
    sisi::Float64
end

struct Segitiga
    alas::Float64
    tinggi::Float64
end

struct Lingkaran
    radius::Float64
end

luas(p::Persegi) = p.sisi^2
luas(t::Segitiga) = t.alas * t.tinggi / 2
luas(c::Lingkaran) = pi * c.radius^2

println(luas(Persegi(3.0)))
println(luas(Segitiga(4.0, 5.0)))
println(luas(Lingkaran(2.0)))
println(length(methods(luas)))
text Keluaran
9.0
10.0
12.566370614359172
3

Fungsi luas memiliki tiga metode dengan rumus masing-masing bangun. Setiap pemanggilan diarahkan ke metode yang sesuai dengan tipe argumennya, dan tidak ada satu pun pencabangan di dalam badan fungsi. Pemisahan ini menjaga setiap metode tetap pendek dan mudah diuji, sekaligus membuka kemungkinan menambah bangun baru cukup dengan mendefinisikan metode baru.

Anotasi tipe pada metode bersifat opsional. Definisi tanpa anotasi menerima argumen dengan tipe apa pun, sedangkan pemanggilan yang tidak menemukan metode mana pun yang cocok memunculkan MethodError. Contoh berikut memanggil luas dengan String, tipe yang tidak dilayani metode mana pun.

julia Pemanggilan tanpa metode yang cocok
struct Persegi
    sisi::Float64
end

luas(p::Persegi) = p.sisi^2

println(luas(Persegi(3.0)))
try
    luas("teks")
catch e
    println(typeof(e))
    println(split(sprint(showerror, e), "\n")[1])
end
text Keluaran
9.0
MethodError
MethodError: no method matching luas(::String)

Pesan error menyebut kombinasi tipe yang dicari, yaitu luas(::String), sehingga metode yang belum ada mudah dikenali dan ditambahkan. Inilah perilaku standar Julia terhadap pemanggilan tanpa metode. Pesan serupa juga muncul apabila jumlah argumen tidak cocok dengan metode mana pun.

Dispatch memperhatikan seluruh argumen#

Pada fungsi dengan beberapa argumen, kombinasi tipe seluruh argumen menentukan metode yang dipilih, dan urutan argumen adalah bagian dari kombinasi tersebut. Metode total_luas(p::Persegi, t::Segitiga) tidak melayani pemanggilan dengan urutan terbalik, karena tipe argumen pertamanya berbeda. Perilaku ini membedakan multiple dispatch dari overloading klasik yang umumnya hanya melihat tipe argumen pertama.

julia Dispatch pada kombinasi dua argumen
struct Persegi
    sisi::Float64
end

struct Segitiga
    alas::Float64
    tinggi::Float64
end

total_luas(p::Persegi, t::Segitiga) = p.sisi^2 + t.alas * t.tinggi / 2
total_luas(p1::Persegi, p2::Persegi) = p1.sisi^2 + p2.sisi^2

println(total_luas(Persegi(2.0), Segitiga(3.0, 4.0)))
println(total_luas(Persegi(2.0), Persegi(3.0)))
try
    total_luas(Segitiga(3.0, 4.0), Persegi(2.0))
catch e
    println(typeof(e))
    println(split(sprint(showerror, e), "\n")[1])
end
text Keluaran
10.0
13.0
MethodError
MethodError: no method matching total_luas(::Segitiga, ::Persegi)

Dua pemanggilan pertama dipilih sesuai kombinasi tipe masing-masing, yaitu 10.0 untuk sepasang Persegi dan Segitiga serta 13.0 untuk dua Persegi. Pemanggilan ketiga membalik urutan argumen, dan tidak ada metode untuk kombinasi (Segitiga, Persegi), sehingga Julia memunculkan MethodError. Apabila kedua urutan perlu didukung, metode total_luas(t::Segitiga, p::Persegi) cukup didefinisikan sebagai metode tambahan.

Cara ini berbeda dari overloading pada bahasa berorientasi objek yang mengikat metode pada argumen pertamanya. Pada Julia, tidak ada metode yang menjadi milik sebuah tipe, sehingga fungsi total_luas dapat diperluas untuk tipe baru tanpa menyentuh definisi lama. Fungsi total_luas pada contoh memuat dua metode, dan setiap kombinasi baru dinyatakan sebagai metode tersendiri tanpa mengubah metode yang sudah teruji.

Ambiguitas metode#

Dua metode dapat sama-sama cocok tanpa ada yang lebih spesifik satu sama lain. Pemanggilan seperti itu bersifat ambigu, dan Julia menolak menebak dengan memunculkan MethodError yang menyebut kata “ambiguous”. Contoh berikut memperlihatkan pasangan metode ambigu beserta penyelesaiannya, yaitu metode ketiga yang lebih spesifik.

julia Ambiguitas dua metode dan penyelesaiannya
g1(a::Number, b::Integer) = "metode A"
g1(a::Integer, b::Number) = "metode B"
g1(a::Integer, b::Integer) = "metode C"

println(g1(1.5, 2))
println(g1(2, 1.5))
println(g1(2, 3))

g2(a::Number, b::Integer) = "metode A"
g2(a::Integer, b::Number) = "metode B"

try
    g2(2, 3)
catch e
    println(typeof(e))
    println(split(sprint(showerror, e), "\n")[1])
end
text Keluaran
metode A
metode B
metode C
MethodError
MethodError: g2(::Int64, ::Int64) is ambiguous.

Pada fungsi g1, pemanggilan g1(2, 3) cocok dengan ketiga metode sekaligus, dan metode C terpilih karena paling spesifik. Pada fungsi g2, metode A dan metode B sama-sama cocok untuk g2(2, 3), tetapi tidak ada yang lebih spesifik, sehingga pemanggilan menjadi ambigu. Menambahkan metode g2(a::Integer, b::Integer) menyelesaikan ambiguitas dengan cara yang sama seperti pada g1.

Julia memeriksa ambiguitas pada saat pemanggilan, bukan pada saat metode didefinisikan. Dua metode ambigu boleh didefinisikan berdampingan, dan error baru muncul apabila ada pemanggilan yang tidak dapat diputuskan. Pemeriksaan ini berlaku untuk metode dengan sejumlah argumen apa pun, sehingga ambiguitas serupa juga dapat terjadi pada fungsi tiga argumen. Dengan demikian, metode penengah dapat ditambahkan belakangan tanpa perombakan pada metode yang sudah ada.

Dispatch pada tipe abstrak dan Union#

Metode tidak harus ditulis untuk tipe konkret. Anotasi dengan tipe abstrak seperti Real melayani seluruh turunannya sekaligus, sedangkan Union{Int64, String} melayani dua tipe yang tidak berhubungan dalam satu metode. Union dipakai apabila dua tipe tidak berbagi induk yang berguna, sehingga satu metode tetap dapat melayani keduanya. Apabila beberapa metode cocok, metode dengan anotasi paling spesifik tetap terpilih.

julia Metode untuk tipe abstrak dan Union
tampilkan(x::Union{Int64, String}) = "berisi $x"
tampilkan(x::Real) = "bilangan real $x"

println(tampilkan(42))
println(tampilkan("teks"))
println(tampilkan(2.5))
text Keluaran
berisi 42
berisi teks
bilangan real 2.5

Nilai 42 dan “teks” keduanya jatuh ke metode Union karena Int64 dan String adalah anggota Union{Int64, String} tersebut. Untuk Int64, metode Union lebih spesifik daripada metode Real, sehingga metode yang pertama terpilih. Nilai 2.5 bertipe Float64 dan tidak termasuk Union, sehingga jatuh ke metode Real.

Metode bertipe lebar seperti Real menjadi pilihan cadangan bagi tipe yang tidak dilayani metode spesifik. Pola yang umum adalah menulis metode spesifik untuk kasus yang hendak dioptimalkan, lalu satu metode abstrak sebagai penangkap sisanya. Susunan seperti ini menjaga perilaku fungsi tetap terdefinisi untuk seluruh tipe yang relevan tanpa mengulang kode.

Ringkasan#

  • Satu nama fungsi memuat banyak metode, dan dispatch memilih metode paling spesifik berdasarkan tipe seluruh argumen.
  • Urutan argumen merupakan bagian dari kombinasi tipe, sehingga kombinasi tanpa metode memunculkan MethodError.
  • Pasangan metode yang sama-sama spesifik membuat pemanggilan ambigu, dan metode penengah yang lebih spesifik menyelesaikannya.
  • Tipe abstrak dan Union{...} memperlebar cakupan metode, sedangkan metode yang lebih spesifik tetap diprioritaskan.

Referensi#