newtype

Wrapper tanpa biaya pada waktu berjalan untuk keamanan tipe.

Daftar isi

newtype mendeklarasikan tipe pembungkus (wrapper) yang membungkus tepat satu field di dalam satu konstruktor. Nilainya berbeda tipe di mata compiler, tetapi direpresentasikan sama dengan field pembungkusnya saat program berjalan, sehingga keamanan tipe diperoleh tanpa biaya tambahan. Topik ini membahas deklarasi newtype, perannya dalam mencegah pencampuran satuan, serta perbedaannya dengan type dan data. Setelah membacanya, pemilihan antara ketiga kata kunci tersebut dapat dilakukan dengan dasar yang jelas.

Deklarasi newtype#

Deklarasi newtype menggunakan kata kunci newtype, diikuti nama tipe, tanda =, satu konstruktor, dan tepat satu field. Sintaksis record tetap berlaku, sehingga field pembungkusnya dapat diberi nama dan diambil melalui aksesor. Klausa deriving juga tetap berlaku sebagaimana pada deklarasi data biasa. Program berikut mendeklarasikan dua tipe satuan sekaligus, yaitu Jarak dan Berat, dan menggunakan keduanya dalam beberapa pencetakan.

haskell satuan.hs
-- newtype: pembungkus satu field, tipe baru saat kompilasi
newtype Jarak = Jarak { dalamMeter :: Double } deriving (Show, Eq)
newtype Berat = Berat { dalamGram  :: Double } deriving (Show, Eq)

tambahJarak :: Jarak -> Jarak -> Jarak
tambahJarak (Jarak a) (Jarak b) = Jarak (a + b)

main :: IO ()
main = do
  let antarKota = Jarak 120.5
      putaran   = Jarak 2.5
  print (tambahJarak antarKota putaran)
  print (dalamMeter antarKota)
  print (dalamGram (Berat 250))
text Keluaran
Jarak {dalamMeter = 123.0}
120.5
250.0

Fungsi tambahJarak menunjukkan pola kerja yang lazim pada tipe pembungkus, yaitu membuka field melalui pencocokan pola, menghitung pada Double, kemudian membungkus hasilnya kembali. Baris pertama keluaran menunjukkan bahwa nilai newtype dapat dicetak dan tetap membawa nama tipenya. Dua baris terakhir menunjukkan aksesor record pada newtype yang bekerja sama persis dengan aksesor pada deklarasi data.

Nama newtype berasal dari sifat khasnya, yaitu membuat tipe baru dengan representasi yang sama. Laporan bahasa menyatakan bahwa tipe hasil newtype berbeda dari tipe fieldnya, tetapi keduanya menggunakan representasi yang sama, sehingga konstruktor newtype tidak membentuk bungkusan baru pada waktu berjalan. Dari sifat itulah klaim tanpa biaya tersebut berasal, dan karena itu pula newtype hanya boleh memuat satu field. Pembungkusan lebih dari satu field membutuhkan representasi baru, dan pekerjaan itu menjadi ranah data.

Mencegah pencampuran satuan#

Keuntungan utama newtype terlihat saat dua besaran kebetulan bertipe dasar sama tetapi bermakna berbeda. Nilai jarak dan berat sama-sama disimpan sebagai Double, tetapi menjumlahkan jarak dengan berat adalah kesalahan logika yang tidak terlihat dari tipenya. Apabila keduanya dideklarasikan sebagai newtype, compiler menolak ekspresi campurannya karena tipe yang diharapkan adalah Jarak, sedangkan yang diberikan adalah Berat.

Bandingkan dengan alias tipe type Berat = Double. Alias hanya mengganti nama tanpa membuat tipe baru, sehingga nilai Double apa pun dapat mengisi parameter Berat tanpa peringatan apa pun dan pencampuran pun lolos tanpa terdeteksi. Deklarasi data Berat = Berat Double memang memberi keamanan yang sama, tetapi membawa representasi tambahan pada waktu berjalan. Newtype menempati titik tengahnya, yaitu tipe baru yang diperiksa ketat dengan representasi yang tetap sama dengan Double.

Newtype untuk instans berbeda#

Instans type class melekat pada tipe, sehingga satu tipe hanya boleh memiliki satu instans untuk satu type class. Ketika perilaku yang dibutuhkan berbeda pada tipe dasar yang sama, newtype menjadi alat pemisahnya. Library standar base menggunakan pola ini pada Sum dan Product, yaitu dua newtype di atas tipe angka yang sama, dengan instans Monoid yang berbeda, yaitu penjumlahan untuk Sum dan perkalian untuk Product. Tanpa pembungkus itu, kedua instans tersebut tidak mungkin melekat pada Int sekaligus.

Pola yang sama dapat ditiru untuk type class milik sendiri. Misalnya, dua newtype di atas String dapat diberi dua instans yang menyusun teks dengan aturan berbeda, dan pemilihan instans cukup dilakukan dengan memilih tipe pembungkusnya. Karena pembungkusnya hilang saat berjalan, pemisahan perilaku ini diperoleh tanpa perubahan representasi. Konsekuensinya, pembungkusan dan pembukaan pada batas fungsi menjadi eksplisit, dan tempat-tempat itulah yang paling tepat untuk memeriksa keabsahan nilai.

Perbedaan dengan type dan data#

Tabel berikut merangkum ketiga kata kunci pembungkus tipe.

Kata kunciHasilnyaBatasanBiaya saat berjalan
typealias nama untuk tipe yang sudah adatanpa tipe baru, tanpa keamanantidak ada
newtypetipe baru dengan satu konstruktor satu fieldsatu konstruktor, tepat satu fieldtidak ada, representasi sama
datatipe baru bebas dengan sejumlah alternatiftidak adasesuai strukturnya

Ketiganya dipilih sesuai kebutuhan, bukan berdasarkan kebiasaan semata. Alias type cocok untuk keterbacaan tanpa konsekuensi pemeriksaan, misalnya memberi nama Berat pada Double yang dihitung dengan bebas. Newtype cocok untuk besaran yang salah kirimnya harus tertangkap saat kompilasi, seperti satuan pada program di atas. Deklarasi data digunakan untuk sisanya, yaitu tipe sum dengan beberapa alternatif maupun tipe product dengan beberapa field. Karena representasinya identik dengan field pembungkusnya, newtype juga sering digunakan untuk melindungi struktur yang sudah ada tanpa mengorbankan performa.

Kesalahan umum#

  • Newtype dengan dua field atau dua konstruktor. Compiler menolak deklarasi tersebut dengan pesan bahwa konstruktor newtype harus memiliki tepat satu field. Deklarasi yang membutuhkan lebih dari satu field atau lebih dari satu alternatif seharusnya menggunakan data.
  • Berharap operator aritmetika bekerja pada pembungkusnya. Tipe Jarak tidak memiliki instans Num, sehingga ekspresi seperti antarKota + Jarak 1 ditolak compiler. Aritmetika dilakukan pada Double setelah field dibuka, kemudian hasilnya dibungkus kembali sebagaimana dilakukan tambahJarak.
  • Mengira alias memberi keamanan tipe. type Berat = Double hanya menyediakan nama lain, sehingga nilai yang tercampur tidak menimbulkan peringatan apa pun. Apabila pencampuran dapat merusak hasil, bungkus nilainya dengan newtype.

Ringkasan#

  • Newtype mendeklarasikan tipe baru dengan satu konstruktor dan tepat satu field, dan sintaksis record serta deriving tetap berlaku.
  • Nilai newtype direpresentasikan sama dengan field pembungkusnya saat berjalan, sehingga keamanan tipenya tanpa biaya tambahan.
  • type hanya membuat alias tanpa tipe baru, sedangkan data digunakan untuk tipe dengan lebih dari satu field atau alternatif.
  • Pola kerja newtype yang lazim adalah membuka field, menghitung pada tipe dasarnya, lalu membungkus hasilnya kembali.

Referensi#