Membedah type system Scala yang ekspresif: tipe primitif dan Unit, container yang menghilangkan null (Option, Either, Tuple), lalu mekanisme pendefinisian tipe sendiri lewat case class, enum, given dan using untuk typeclass, serta opaque type untuk abstraksi yang aman.

Setelah di episode 4 kalian menguasai val, var, dan def, sekarang kita membedah hal yang membuat Scala begitu dihargai: type system-nya. Di bahasa lain, tipe sering dianggap "pembatas"; di Scala, tipe adalah alat desain yang dipakai untuk mencegah bug sebelum kode dijalankan.
Mengapa episode ini penting? Karena sebagian besar keunggulan Scala di produksi — refactoring yang aman, API yang ekspresif, dan kode yang "compiles = works" — berasal dari type system. Kalian tidak perlu hafal semua detail; yang perlu dipahami adalah cara berpikir dengan tipe sebagai kontrak.
Scala mewarisi tipe primitif JVM, tetapi memperlakukan semuanya sebagai objek:
| Tipe | Deskripsi | Contoh |
|---|---|---|
Int | Bilangan bulat 32-bit | 42 |
Long | Bilangan bulat 64-bit | 42L |
Double | Pecahan presisi ganda | 3.14 |
Boolean | true / false | true |
String | Teks | "halo" |
Char | Satu karakter | 'a' |
Unit | "Tidak ada nilai" (analog void) | () |
Unit perlu perhatian khusus: ia adalah tipe yang hanya punya satu nilai, ditulis (). Fungsi yang "tidak mengembalikan apa-apa" sebenarnya mengembalikan Unit:
def log(msg: String): Unit = println(s"[log] $msg")Unit menandai fungsi yang punya efek samping. Ini kode yang jujur: tipe bilang "jangan harapkan nilai berguna dari ini".
Manipulasi string di Scala idiomatis memakai interpolator s"...":
val user = "Sari"
val balance = 99.5
println(s"User $user punya saldo $balance")println(s"1 + 1 = ${1 + 1}")Di dalam prose, ingat: { dan } literal harus dibungkus backtick — interpolation s"${...}" adalah satu-satunya tempat umum keduanya muncul.
Tipe-tipe ini adalah jawaban Scala atas null dan multi-nilai.
Option[A] punya dua kemungkinan: Some(a) atau None. Tidak pernah null:
def findUser(id: Int): Option[String] =
if id == 1 then Some("Sari") else None
findUser(1) // Some(Sari)
findUser(99) // NonefindUser secara tipe memaksa pemanggil menangani kemungkinan "tidak ada". NPE (NullPointerException) yang jadi mimpi buruk Java dihilangkan dengan desain, bukan disiplin. Pattern matching untuk menanganinya ada di episode 7.
Either[L, R] merepresentasikan hasil yang bisa Left(l) (biasanya error) atau Right(r) (biasanya sukses):
def divide(a: Int, b: Int): Either[String, Int] =
if b == 0 then Left("tidak bisa dibagi nol")
else Right(a / b)
divide(10, 2) // Right(5)
divide(10, 0) // Left(tidak bisa dibagi nol)Konvensi: Right adalah sukses. Episode 16 akan membahas penanganan error secara menyeluruh.
Tuple mengelompokkan beberapa nilai dengan tipe berbeda:
val person = ("Sari", 25, true) // (String, Int, Boolean)
println(person._1) // SariSejak Scala 3, tuple bisa di-destructure dengan pola yang lebih jelas:
val (name, age, active) = ("Sari", 25, true)
println(s"$name berusia $age")case class adalah cara standar membuat tipe data immutable dengan banyak perilaku gratis (equality berbasis nilai, copy, pattern matching):
case class User(id: Int, name: String)
val u1 = User(1, "Sari")
val u2 = User(1, "Sari")
u1 == u2 // true - equality struktural, bukan referensi
val u3 = u1.copy(name = "Sari Dewi") // salinan dengan perubahanenum mendefinisikan Algebraic Data Type (ADT) — sekumpulan kemungkinan yang jelas:
enum PaymentStatus:
case Pending
case Paid(amount: Double)
case Failed(reason: String)Nanti di episode 7 kita akan memakai ini untuk modeling domain. Yang perlu diingat sekarang: enum di Scala 3 menggantikan pola sealed trait Scala 2, dengan sintaks lebih ringkas dan ekshaustif yang diperiksa compiler.
Mekanisme given/using menyelesaikan masalah "perilaku untuk tipe" tanpa inheritance. Bayangkan fungsi yang butuh "cara mengurutkan" — ia bisa meminta instance tersebut secara implisit:
trait Ordering[A]:
def compare(a: A, b: A): Int
given IntOrdering: Ordering[Int] with
def compare(a: Int, b: Int): Int = a - b
def maxOf[A](a: A, b: A)(using ord: Ordering[A]): A =
if ord.compare(a, b) >= 0 then a else b
maxOf(3, 7) // 7 - IntOrdering dipakai otomatisFungsi maxOf tidak perlu tahu bagaimana membandingkan; ia meminta penyedia. Inilah pola typeclass yang jadi tulang punggung library seperti cats dan circe.
opaque type membungkus tipe lain sehingga kalian bisa memberi semantik tanpa overhead runtime:
opaque type UserId = Int
object UserId:
def apply(value: Int): UserId = value
extension (id: UserId) def value: Int = id
val id: UserId = UserId(42)Di luar scope definisi, UserId tidak bisa disamakan dengan Int — compiler mencegah UserId(42) + 1 atau mencampurnya dengan Int biasa. Ini menghilangkan kelas bug "salah ketik argumen bertipe angka".
Note
Urutan belajar yang baik: kuasai Option/Either/case class dulu (dipakai setiap hari), lalu enum (episode 7), lalu given/using dan opaque type secara bertahap. Empat hal terakhir ini yang membuat type system Scala "ekspresif" — tetapi tidak perlu semuanya dalam satu hari.
== null di kode Scala — jika kalian menulis ini, berhenti: Option ada justru untuk menghindarinya._1, _2 tuple berlebihan — destructuring lebih terbaca.case class dengan field mutable (var) — ini melanggar semangat immutability; hampir selalu salah.given yang tidak terpakai — compiler Scala 3 memberikan peringatan; hapus instance yang menganggur.Inti yang harus dibawa pulang:
Int, Long, Double, Boolean, String, Char, dan Unit.Option menghilangkan null, Either merepresentasikan hasil gagal/sukses, Tuple mengelompokkan nilai.case class memberi equality struktural dan copy; enum mendefinisikan ADT; given/using membangun typeclass; opaque type memberi abstraksi tanpa overhead.Di episode 6 selanjutnya, kita masuk dunia data: collections dan immutable data — List, Vector, Map, Set yang immutable secara default, transformasi map/filter/flatMap/fold, dan pola-pola yang akan kalian pakai setiap hari. Sampai jumpa di episode 6!