Instans dan deriving

Menulis instans type class secara manual dan menghasilkannya dengan deriving.

Daftar isi

Tipe yang mengikuti sebuah type class membutuhkan instans, yaitu implementasi bagi seluruh fungsi class tersebut. Haskell menyediakan dua cara untuk memperolehnya, yaitu menuliskan instans secara manual atau meminta compiler menghasilkannya dengan deriving. Topik ini membahas kedua cara tersebut, urutan yang mendasari hasil deriving, serta kesalahan yang lazim terjadi saat instans ditulis.

Instans yang dihasilkan dengan deriving#

Deklarasi data membentuk tipe baru dengan beberapa konstruktor nilai yang dipisahkan tanda garis vertikal, misalnya Dingin, Sedang, dan Panas untuk tipe Suhu. Klausa deriving pada akhir deklarasi meminta compiler menghasilkan instans untuk kumpulan class tertentu, yaitu Eq, Ord, Enum, Bounded, Show, dan Read. Cara ini menghilangkan penulisan berulang untuk perilaku yang polanya selalu sama, dan pembahasan lengkap deklarasi data berada di luar cakupan bagian ini.

haskell suhu.hs
-- Deriving: compiler menghasilkan instans Eq, Ord, Enum, dan Show
data Suhu = Dingin | Sedang | Panas
  deriving (Eq, Ord, Enum, Show)

main :: IO ()
main = do
  print (Dingin == Dingin)
  print (Dingin == Panas)
  print (Dingin < Panas)
  print (max Dingin Panas)
  print (fromEnum Sedang)
  print [Dingin .. Panas]
text Keluaran
True
False
True
Panas
1
[Dingin,Sedang,Panas]

Instans Eq yang dihasilkan membandingkan dua nilai secara struktural, yaitu menganggap keduanya sama jika konstruktor dan seluruh isinya sama, sehingga Dingin == Dingin bernilai True sementara Dingin == Panas bernilai False. Instans Ord mengurutkan nilai menurut urutan penulisan konstruktor, sehingga Dingin < Panas bernilai True dan max Dingin Panas menghasilkan Panas. Instans Enum memberi nomor pada konstruktor mulai dari nol, sehingga fromEnum Sedang menghasilkan 1, sedangkan instans Show menampilkan nama konstruktor, dan rentang [Dingin .. Panas] menyusun ketiga nilai sesuai urutannya.

Urutan deklarasi menentukan hasil#

Hasil deriving untuk Ord mengikuti urutan penulisan konstruktor pada deklarasi. Konstruktor yang ditulis lebih dahulu dianggap lebih kecil, sehingga deklarasi data Suhu = Dingin | Sedang | Panas menetapkan urutan Dingin, Sedang, Panas dari terkecil ke terbesar. Urutan yang sama menjadi dasar fromEnum, sehingga Ord dan Enum yang dihasilkan selalu konsisten satu sama lain.

Ketentuan serupa berlaku untuk class lain. Instans Eq yang dihasilkan menuntut seluruh field memiliki instans Eq, dan instans Show menuntut seluruh field memiliki instans Show. Apabila salah satu syaratnya tidak terpenuhi, compiler menolak deklarasi deriving dan menampilkan pesan error yang menyebut class yang kurang.

Deriving juga menjaga konsistensi antarclass, karena seluruh instans yang dihasilkan mengikuti satu sumber yang sama, yaitu deklarasi tipenya. Dua hasil deriving untuk tipe yang sama tidak mungkin saling bertentangan, sedangkan jaminan itu tidak ada apabila kedua instansnya ditulis manual oleh penulis yang berbeda.

Instans manual dipilih ketika makna perbandingan tidak mengikuti urutan penulisan konstruktor. Program berikut mendeklarasikan tipe Prioritas dengan konstruktor yang ditulis mulai dari yang paling mendesak, sedangkan urutan yang diinginkan ditetapkan melalui fungsi bobot. Instans Ord mendelegasikan perbandingan ke bilangan bobot, sehingga urutan deklarasi tidak lagi berpengaruh.

haskell prioritas.hs
-- Konstruktor dideklarasikan menurut urutan penulisan, bukan urutan penting
data Prioritas = Tinggi | Normal | Rendah
  deriving (Eq, Show)

-- Instans manual: urutan penting ditetapkan melalui bobot
bobot :: Prioritas -> Int
bobot Tinggi = 3
bobot Normal = 2
bobot Rendah = 1

instance Ord Prioritas where
  compare a b = compare (bobot a) (bobot b)

main :: IO ()
main = do
  print (Tinggi > Normal)
  print (compare Rendah Normal)
  print (max Normal Tinggi)
  print (min Normal Rendah)
text Keluaran
True
LT
Tinggi
Rendah

Pemanggilan Tinggi > Normal bernilai True meskipun Tinggi ditulis lebih dahulu pada deklarasi, karena bobot Tinggi adalah 3 sementara bobot Normal adalah 2. Fungsi compare Rendah Normal menghasilkan LT, dan fungsi min Normal Rendah menghasilkan Rendah, keduanya sejalan dengan bobot yang didefinisikan. Instans manual juga menjadi tempat menuliskan aturan khusus, misalnya perbandingan yang mengabaikan sebagian field.

Baris deriving (Eq, Show) pada Prioritas sengaja tidak memuat Ord, karena perbandingannya ditangani instans manual. Pemilihan ini memperlihatkan bahwa kedua cara tersebut dapat berdampingan pada satu tipe, yaitu class yang polanya standar dihasilkan dengan deriving, sedangkan class yang maknanya khusus ditulis sendiri. Instans Ord yang ditulis manual juga tetap terikat kewajiban instans Eq yang menyertainya, syarat yang sudah terpenuhi oleh klausa deriving tersebut.

Kesalahan umum#

Kesalahan pertama adalah menggunakan fungsi class pada tipe yang belum memiliki instans. Misalnya, mencetak sebuah fungsi dengan print ditolak compiler, karena tipe fungsi tidak memiliki instans Show. Pesan errornya menyebut class dan tipenya, misalnya No instance for (Show ...), dan perbaikannya adalah menyediakan instans melalui deriving atau penulisan manual, atau menghapus kebutuhan tersebut jika tipe memang tidak dapat ditampilkan.

Kesalahan kedua adalah menaruh class di luar daftar yang dapat dihasilkan. Klausa deriving hanya berlaku untuk kumpulan class tertentu, yaitu Eq, Ord, Enum, Bounded, Show, dan Read. Deklarasi deriving (Num) ditolak compiler karena Num berada di luar daftar itu, dan class buatan sendiri juga selalu diberi instans secara manual.

Kesalahan ketiga adalah mendeklarasikan dua instans untuk satu pasangan class dan tipe. Setiap tipe hanya boleh memiliki satu instans untuk satu class, sehingga deklarasi instans yang berulang ditolak compiler. Kebutuhan perilaku yang berbeda bagi satu tipe ditangani dengan membungkus tipe itu dalam tipe baru, sehingga setiap tipe tetap memiliki satu instans per class.

Ringkasan#

  • Klausa deriving menghasilkan instans untuk Eq, Ord, Enum, Bounded, Show, dan Read tanpa penulisan manual.
  • Hasil deriving Ord mengikuti urutan penulisan konstruktor, dan hasil Enum serta Show selaras dengan urutan tersebut.
  • Instans manual menetapkan makna perbandingan sendiri, misalnya melalui fungsi bobot.
  • Fungsi class pada tipe tanpa instans ditolak compiler dengan pesan yang menyebut class dan tipenya.
  • Satu tipe hanya boleh memiliki satu instans untuk satu class.

Referensi#