Tipe union dan tipe intersection menyusun tipe baru secara langsung dari tipe yang sudah ada. Tipe union (union type) A | B mencakup setiap nilai milik A atau B, sedangkan tipe intersection (intersection type) A & B menuntut nilai yang memenuhi A dan B sekaligus. Topik ini membahas keduanya pada parameter fungsi dan pola match, alias tipe untuk union yang sering digunakan, anggota gabungan pada intersection, serta pesan error compiler apabila anggota yang diminta tidak ada pada salah satu cabang union.
Tipe union pada parameter dan pola match#
Tipe union ditulis dengan | di antara dua tipe dan mencakup seluruh nilai dari kedua sisi tanda itu. Parameter yang dianotasikan String | Int menerima argumen String maupun Int, sehingga tipe ini menggantikan kebutuhan overload, yaitu beberapa definisi fungsi dengan tipe parameter berbeda, pada kasus sederhana. Karena compiler hanya menganggap anggota yang dimiliki seluruh cabang sebagai tersedia, isi nilai union diakses melalui pola match yang menguji tipenya. Alias tipe (type alias) menghindari penulisan berulang ketika union yang sama digunakan di banyak tempat.
// Union type: parameter menerima String atau Int, bukan keduanya sekaligus
type Identitas = String | Int
def tampilkan(id: Identitas): String =
id match
case teks: String => "ID nama : " + teks
case angka: Int => "ID nomor: " + angka
@main def nilaiGabungan(): Unit =
println(tampilkan("ali-102"))
println(tampilkan(102))
println(tampilkan("budi-205"))
println(tampilkan(77))Jalankan program tersebut.
# menjalankan program nilai-gabungan.scala
scala-cli run nilai-gabungan.scalaID nama : ali-102
ID nomor: 102
ID nama : budi-205
ID nomor: 77Parameter id dideklarasikan dengan alias Identitas, sehingga tampilkan menerima kedua bentuk identitas tanpa overload terpisah. Di dalam setiap cabang, ikatan pola bertipe sesuai cabangnya, sehingga teks bertipe String dan penggunaan anggotanya aman tanpa pemeriksaan tambahan. Compiler juga memeriksa kelengkapan match pada union, karena jumlah kemungkinannya tertutup. Apabila satu cabang union tidak ditangani, compiler memunculkan peringatan match may not be exhaustive yang menyebut pola yang akan gagal, misalnya It would fail on pattern case: _: Int.
Tipe intersection dan anggota gabungan#
Tipe intersection ditulis dengan & dan menuntut nilai yang memenuhi kedua tipenya sekaligus. Tipe BisaTerbang & BisaRenang berarti nilai yang bisa terbang dan bisa berenang pada waktu yang sama, dan setiap kelas yang mewarisi kedua trait memenuhi tipe tersebut. Anggota tipe intersection merupakan gabungan anggota kedua sisi, sehingga metode dari trait mana pun dapat dipanggil pada nilainya. Apabila satu anggota dideklarasikan pada kedua trait, anggota itu pada intersection tetap tersedia dengan tipe irisan kedua deklarasinya, sehingga pemanggilannya tetap aman.
// A & B menuntut kedua trait sekaligus; anggota keduanya digabung
trait BisaTerbang:
def nama: String
def gerakanTerbang: String = "mengudara di langit"
trait BisaRenang:
def nama: String
def gerakanRenang: String = "menyusur permukaan air"
case class Bebek(nama: String) extends BisaTerbang, BisaRenang
def kegiatan(hewan: BisaTerbang & BisaRenang): String =
s"${hewan.nama} ${hewan.gerakanTerbang}, lalu ${hewan.gerakanRenang}"
@main def duaDunia(): Unit =
val doni: BisaTerbang & BisaRenang = Bebek("Doni")
println(kegiatan(doni))
println(kegiatan(Bebek("Rani")))Jalankan program tersebut.
# menjalankan program dua-dunia.scala
scala-cli run dua-dunia.scalaDoni mengudara di langit, lalu menyusur permukaan air
Rani mengudara di langit, lalu menyusur permukaan airParameter hewan bertipe BisaTerbang & BisaRenang, sehingga kegiatan menerima Bebek maupun kelas lain yang mewarisi kedua trait tanpa mendeklarasikan satu kelas pun. Anggota nama dideklarasikan pada kedua trait dan tetap dapat dipanggil dari parameter intersection tersebut. Satu perilaku tambahan perlu dicatat pada tipe fungsi, karena tipe parameternya bersifat kontravarian (contravariant). Fungsi yang menerima A siap menerima nilai bertipe yang lebih sempit daripada A. Karena itu, tipe BisaTerbang => String merupakan subtipe dari (BisaTerbang & BisaRenang) => String, sehingga fungsi yang hanya menuntut satu sifat dapat diikat ke variabel bertipe fungsi yang menuntut keduanya. Intersection pada posisi parameter melebarkan tipe fungsi, bukan menyempitkannya.
Pemilihan antara union dan intersection mengikuti pertanyaan sederhana. Union dipilih ketika nilai boleh berasal dari salah satu kemungkinan dan penanganannya dibedakan melalui match, sedangkan intersection dipilih ketika nilai wajib memenuhi beberapa kemampuan sekaligus dan tidak ada cabang yang perlu dibedakan. Keduanya menyusun tipe dari bagian yang sudah ada tanpa mendeklarasikan kelas perantara, sehingga model tetap ramping, tetapi keduanya juga tidak menggantikan desain trait yang matang ketika perilakunya menjadi kompleks.
Anggota yang tidak ada pada salah satu cabang#
Union bukan sekadar penanda, melainkan kontrak bahwa nilai bertipe salah satu cabangnya. Compiler menolak pemanggilan anggota yang tidak dimiliki seluruh cabang, dan penolakan itu terjadi pada waktu kompilasi, jauh sebelum program berjalan. Berkas berikut sengaja memanggil length, anggota yang dimiliki String, pada nilai String | Int, padahal Int tidak memilikinya.
// sengaja salah: length tidak dimiliki cabang Int
def jumlahHuruf(nilai: String | Int): Int =
nilai.length
@main def salahUnion(): Unit =
println(jumlahHuruf("hidroponik"))Kompilasi berkas tersebut untuk melihat errornya.
# mengompilasi salah-union.scala untuk melihat error compiler
scala-cli compile salah-union.scalaCompiling project (Scala 3.9.0, JVM (17))
[error] ./salah-union.scala:3:3
[error] value length is not a member of String | Int
[error] nilai.length
[error] ^^^^^^^^^^^^
Error compiling project (Scala 3.9.0, JVM (17))
Compilation failedPesan value length is not a member of String | Int menyatakan masalahnya secara persis, yaitu tidak semua cabang union memiliki anggota itu. Perbaikannya bukan pemaksaan tipe, melainkan pemilahan nilai melalui pola match sebagaimana dibahas pada bagian pertama, sehingga length hanya dipanggil pada cabang String. Apabila satu operasi memang dimaksudkan untuk kedua cabang, ekstensi pada union dapat menambahkannya sekaligus. Larangan inilah yang menjaga union tetap aman, karena compiler memastikan tidak ada anggota yang dipanggil pada cabang yang tidak mendukungnya.
Ringkasan#
- Tipe union
A | Bmencakup nilai milik A atau B, ditulis langsung pada parameter atau melalui alias tipe, dan isi nilai union diakses melalui pola match yang cabangnya menguji tipe. - Compiler memperingatkan match union yang belum menangani salah satu cabangnya.
- Tipe intersection
A & Bmenuntut kedua sifat sekaligus, dan anggotanya merupakan gabungan anggota kedua sisi. - Pada tipe fungsi yang kontravarian,
A => Cmerupakan subtipe dari(A & B) => C. - Compiler menolak pemanggilan anggota yang tidak ada pada semua cabang union, sehingga kesalahan terdeteksi pada waktu kompilasi.
Referensi#
- Union Types — Scala 3 Reference — Definisi union type, sifat komutatifnya, dan perilaku inferensi compiler.
- Intersection Types — Scala 3 Reference — Definisi intersection type beserta aturan anggota gabungannya.
- Domain Modeling — Scala 3 Book — Penggunaan union dan intersection sebagai alat pemodelan domain pada buku resmi Scala 3.