convert dan promote

Aturan koersi convert dan promote yang membuat aritmetika campuran antartipe bilangan bekerja.

Daftar isi

Julia mengatur koersi (coercion), yaitu perubahan tipe secara terkendali, melalui dua mekanisme yang saling melengkapi, yakni convert untuk konversi antartipe dan promote untuk promosi tipe (type promotion) pada operasi campuran. Keduanya menjelaskan mengapa 1 + 2.5 dapat dihitung, sekaligus mengapa argumen fungsi tidak dikonversi secara diam-diam. Topik ini membahas aturan keduanya beserta konsekuensinya pada aritmetika dan definisi fungsi.

Fungsi convert#

Fungsi convert(T, x) mengubah x menjadi tipe T apabila konversinya tidak menghilangkan informasi. Konversi Int64 ke Float64 tepat untuk bilangan yang muat, sedangkan arah sebaliknya hanya sah apabila nilainya bulat sempurna. Pelanggaran aturan itu memunculkan InexactError.

julia contoh-convert.jl
println(convert(Float64, 2))
println(convert(Int64, 3.0))
try
    println(convert(Int64, 3.5))
catch e
    println(typeof(e))
    println(split(sprint(showerror, e), "\n")[1])
end
text Keluaran
2.0
3
InexactError
InexactError: Int64(3.5)

Dua konversi pertama berlangsung tanpa kehilangan informasi, yaitu 2 menjadi 2.0 dan 3.0 menjadi 3. Konversi ketiga berhenti dengan InexactError karena 3.5 tidak dapat dinyatakan sebagai Int64 tanpa membuang bagian pecahannya. Konversi yang menghilangkan informasi harus dilakukan secara sadar, misalnya melalui fungsi round.

convert juga bukan konverter serbaguna. Konversi dari Int64 ke String tidak disediakan, karena pengubahan nilai ke teks memakai fungsi string, sedangkan convert diperuntukkan bagi pasangan tipe yang aturan konversinya memang didefinisikan.

julia convert bukan konverter serbaguna
try
    convert(String, 42)
catch e
    println(typeof(e))
    println(split(sprint(showerror, e), "\n")[1])
end
println(string(42))
text Keluaran
MethodError
MethodError: Cannot `convert` an object of type Int64 to an object of type String
42

Pesan error menyatakan tidak ada aturan konversi dari Int64 ke String, sedangkan fungsi string menghasilkan teks “42” dengan jelas. Pemisahan ini menjaga convert tetap konsisten, yaitu hanya mengubah antartipe yang maknanya setara.

Fungsi promote dan promote_type#

Fungsi promote menaikkan sekumpulan nilai ke satu tipe bersama (common type) dan mengembalikannya sebagai tuple. Fungsi promote_type menghitung tipe bersama tersebut dari nama tipe saja, tanpa perlu nilai. Keduanya bertumpu pada aturan promote_rule yang didefinisikan per pasangan tipe.

julia Promosi tipe dengan promote
println(promote(1, 2.5))
println(promote(1, 2.5f0))
println(promote(1, 3//4))
println(typeof(promote(1, 2.5)))
text Keluaran
(1.0, 2.5)
(1.0f0, 2.5f0)
(1//1, 3//4)
Tuple{Float64, Float64}

Hasil tuple memperlihatkan bahwa seluruh anggota dinaikkan ke tipe yang sama, yaitu Float64 untuk campuran Int64 dan Float64 serta Rational{Int64} untuk campuran dengan bilangan rasional. Notasi 2.5f0 menuliskan literal Float32, dan promosi bertahan di Float32 apabila pasangannya Int64.

julia Tipe bersama dari promote_type
println(promote_type(Int64, Float64))
println(promote_type(Int64, Float32))
println(promote_type(Int64, Bool))
println(promote_type(Int64, Int64))
text Keluaran
Float64
Float32
Int64
Int64

promote_type menjawab pertanyaan yang sama pada tingkat tipe. Pasangan Int64 dengan Bool bertemu pada Int64 karena Bool adalah subtype dari Integer yang bernilai 0 dan 1, sedangkan dua tipe yang sama menghasilkan tipe itu juga. Daftar aturan lengkapnya didefinisikan melalui promote_rule dan dapat diperluas untuk tipe buatan pengguna.

Aturan promosi ditetapkan sekali per pasangan tipe, lalu dipakai ulang oleh seluruh operasi yang mendukungnya. Penjumlahan, perkalian, hingga perbandingan memanfaatkan aturan yang sama, sehingga tipe hasil operasi campuran konsisten satu sama lain. Perluasan aturan untuk tipe buatan sendiri cukup dilakukan pada satu tempat, yaitu definisi promote_rule-nya.

Aritmetika campuran#

Operator aritmetika memanggil promote terlebih dahulu sebelum menghitung, sehingga operasi campuran antartipe bekerja tanpa penulisan konversi manual. Inilah sebabnya 1 + 2.5 menghasilkan Float64, sedangkan 1 + 2.5f0 menghasilkan Float32. Aturan yang sama berlaku untuk bilangan rasional, dan operator pembagian / selalu menghasilkan bilangan pecahan.

Mekanisme ini membuat kode numerik tetap ringkas tanpa mengorbankan kepastian tipe. Setiap operasi campuran mengikuti tabel promote_type yang sama, sehingga tipe hasil tidak bergantung pada urutan penulisan operand. Aturan ini juga berlaku pada operasi perbandingan, sehingga 1 < 2.5 dihitung dengan promosi yang sama. Apabila tipe hasil perlu dipastikan, typeof pada hasil operasi memberi jawaban yang sama dengan promote_type pada tipe operandnya.

julia Aritmetika campuran antartipe
println(1 + 2.5)
println(typeof(1 + 2.5))
println(typeof(1 + 2.5f0))
println(1 + 1//2)
println(typeof(1 + 1//2))
println(typeof(1 / 2))
text Keluaran
3.5
Float64
Float32
3//2
Rational{Int64}
Float64

Hasil 3.5 berasal dari promosi 1 menjadi Float64 sebelum penjumlahan, dan 3//2 berasal dari promosi 1 menjadi Rational{Int64}. Pembagian dengan / menghasilkan Float64 bahkan untuk 1 / 2 yang hasilnya bulat, karena pembagian dianggap operasi pecahan. Dengan demikian, tipe hasil setiap operasi dapat diprediksi dari promote_type tipe operandnya.

Tidak ada koersi diam-diam pada argumen fungsi#

Berbeda dari aritmetika, Julia tidak mengonversi argumen fungsi secara otomatis. Metode dengan anotasi Float64 hanya menerima Float64 atau turunannya, dan pemanggilan dengan Int64 memunculkan MethodError. Pemanggil yang ingin memakai metode tersebut wajib mengonversi nilainya secara eksplisit.

julia tanpa-koersi.jl
gandakan(x::Float64) = 2x

println(gandakan(2.5))
try
    gandakan(3)
catch e
    println(typeof(e))
    println(split(sprint(showerror, e), "\n")[1])
end
println(gandakan(convert(Float64, 3)))
text Keluaran
5.0
MethodError
MethodError: no method matching gandakan(::Int64)
6.0

Pemanggilan gandakan(3) gagal karena tidak ada metode untuk Int64, dan baris terakhir berhasil setelah nilai dikonversi dengan convert. Ada dua cara yang lazim untuk memperluas fungsi, yaitu menambahkan metode gandakan(x::Integer) atau mengonversi argumen sebelum memanggil. Konstruktor bawaan struct merupakan pengecualian yang disengaja, karena konstruktor itu memanggil convert untuk setiap field.

Perbedaan ini menegaskan batas promosi tipe. Promosi terjadi di dalam operasi aritmetika yang memang memanggil promote, bukan pada pemanggilan fungsi biasa, sehingga 1 + 2.5 diproses melalui aturan promosi, sedangkan gandakan(3) berhenti sebagai MethodError. Prinsip tanpa konversi diam-diam menjaga setiap perubahan tipe tetap terlihat dalam kode.

Kesalahan umum#

Tiga kesalahan berikut paling sering muncul saat bekerja dengan konversi tipe. Masing-masing berhubungan dengan aturan yang sudah dibahas pada bagian sebelumnya.

  1. Mengonversi pecahan ke bilangan bulat secara langsung. convert(Int64, 3.5) memunculkan InexactError, sehingga nilai perlu dibulatkan lebih dahulu dengan round apabila pembulatan memang dikehendaki.
  2. Mengira 1 + 2.5f0 menghasilkan Float64. Aturan promote_type(Int64, Float32) menghasilkan Float32, sehingga tipe hasil perlu diperiksa dengan typeof bila presisi menjadi penting.
  3. Mengharapkan fungsi menerima Int64 padahal anotasinya Float64. Julia tidak mengonversi diam-diam, sehingga metode untuk tipe tersebut perlu ditambahkan atau argumennya dikonversi secara eksplisit.

Ketiganya kembali pada kebiasaan yang sama, yaitu memeriksa tipe dengan typeof dan menyatakan konversi secara eksplisit. Kebiasaan itu memangkas kejutan InexactError maupun MethodError, karena kedua error tersebut pada dasarnya menandai konversi yang belum dinyatakan secara sadar. Pesan keduanya juga menyebut tipe yang terlibat, sehingga penyebabnya mudah dilacak.

Ringkasan#

  • convert(T, x) melakukan konversi eksplisit yang sah dan memunculkan InexactError bila informasi akan hilang.
  • promote dan promote_type menentukan tipe bersama sekumpulan tipe bilangan menurut aturan promote_rule.
  • Aritmetika campuran memanggil promosi terlebih dahulu, sedangkan argumen fungsi tidak pernah dikonversi diam-diam.

Referensi#