Salah satu alasan mengapa bahasa Kotlin menjadi populer dan banyak disukai oleh para developer adalah karena syntax-nya yang ringkas dan mudah dimengerti.
Untuk menulis kode yang lebih ringkas, Kotlin memungkinkan kita untuk membuat Domain Specific Language atau DSL!
DSL (Domain Specific Language) merupakan bahasa / syntax yang didesain khusus untuk bagian spesifik dari program kita. Kebalikan dari DSL adalah GPL (General Purpose Language) yaitu bahasa yang bisa digunakan untuk banyak bagian dari program kita, Contoh dari GPL adalah bahasa pemrograman Kotlin, Swift, dll.
Karena DSL didesain khusus untuk bagian tertentu dari program kita serta scope-nya lebih terfokus, maka hasil dari DSL yang kita buat akan jadi lebih ringkas & mudah untuk dibaca. Sehingga orang lain yang tidak mengerti detail implementasi dari kode kita pun tetap bisa memahami makna dari kode yang kita tulis.
Berikut adalah contoh dari Domain Specific Language DSL pada Kotlin:
Salah satu contoh DSL yang paling populer adalah konfigurasi Gradle.
1plugins {2 id("org.jetbrains.kotlin.jvm") version "1.8.20"3}45repositories {6 mavenCentral()7}89dependencies {10 implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")11 testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")12}
Atau jika kamu pernah menggunakan library Koin untuk melakukan Dependency Injection, Koin juga mempunyai DSL sendiri.
1val appModule = module {2 single { Service() }3}45startKoin {6 logger()7 modules(appModule)8}
DSL mempunyai 2 karakteristik utama yaitu Context dan Fluency.
Dengan adanya Context, kita tidak perlu lagi menuliskan kode-kode yang repetitif.
Contohnya jika tanpa Context, mungkin kode yang kita tulis akan seperti ini:
Tanpa Context1val contact = Contact()2contact.email = "me@alfin.dev"3contact.phone = "088233104788"4contact.ig = "alfinsyahruddin"5myProfile.contact = contact
Namun dengan Context, kode kita akan menjadi lebih ringkas:
Dengan Context1profile {2 contact {3 email = "me@alfin.dev"4 phone = "088233104788"5 ig = "alfinsyahruddin"6 }7}
Dengan DSL, kita bisa menulis kode layaknya bahasa manusia sehingga kode kita akan lebih mudah untuk dibaca.
Perhatikan 2 kode di bawah ini, manakah menurutmu yang lebih mudah untuk dimengerti ketika pertama kali membaca kode tersebut? 😁
Regular Kotlin Code1val dob = LocalDate.of(2003, Month.FEBRUARY, 10)
DSL1val dob = 10 feb 2003
Ketika kita membuat DSL pada General Purpose Language seperti Kotlin, sebenarnya kita sedang membuat Internal DSL, karena kita menggunakan bahasa yang sudah ada (Kotlin). Kelebihannya kita bisa memanfaatkan fitur2 dari bahasa Kotlin tersebut seperti for-loop, Scope Functions, dll.
Di samping itu, ada juga External DSL, yaitu ketika kita membuat DSL dengan grammar, lexer, dan parser yang benar-benar baru. Contoh dari External DSL yang paling populer adalah SQL.
Sekarang, kita akan belajar bagaimana cara membuat custom DSL pada Kotlin! 😃
Kita akan mengubah kode berikut:
Regular Kotlin Code1val myProfile = Profile()2myProfile.name = "Alfin"3myProfile.dob = LocalDate.of(2003, Month.FEBRUARY, 10)45val contact = Contact()6contact.email = "me@alfin.dev"7myProfile.contact = contact89myProfile.interests += "TECH - Mobile Engineering"10myProfile.interests += "TECH - Augmented Reality"11myProfile.interests += "PHILOSOPHY - Libertarianism"
Menjadi seperti ini, tanpa mengubah struktur datanya:
DSL1val myProfile = profile {2 name = "Alfin"3 dob = 10 feb 200345 contact {6 email = "me@alfin.dev"7 }89 interests {10 tech("Mobile Engineering")11 tech("Augmented Reality")12 philosophy("Libertarianism")13 }14}
Berikut adalah struktur data dari class Profile dan Contact :
1class Profile {2 var name: String? = null3 var dob: LocalDate? = null4 var contact: Contact? = null5 var interests = mutableListOf<String>()6}78class Contact {9 var email: String? = null10}
Agar kita bisa menggunakan syntax seperti ini:
1val myProfile = profile {2 name = "Alfin"34 contact {5 email = "me@alfin.dev"6 }7}
Silakan tambahkan code berikut:
1fun profile(builder: Profile.() -> Unit): Profile {2 return Profile().apply(builder)3}45fun Profile.contact(builder: Contact.() -> Unit) {6 contact = Contact().apply(builder)7}
Profile.() -> Unit akan memberikan Context Object dengan tipe data Profile di dalam lambda kita.
1val myProfile = profile { // this: Profile
apply merupakan salah satu dari 5 Scope Functions pada Kotlin. Baca juga: Scope Functions
Agar kita bisa menggunakan syntax seperti ini:
1dob = 10 feb 2003
Silakan tambahkan code berikut:
1infix fun Int.feb(year: Int): LocalDate = LocalDate.of(year, Month.FEBRUARY, this)
Infix Function memungkinkan kita untuk memanggil sebuah function tanpa perlu menuliskan tanda titik, kurang buka & kurung tutup.
10 feb 2003 jika tanpa Infix Function, kita perlu menuliskannya seperti ini: 10.feb(2003)
Daripada kita menulis kode yang repetitif dan meng-hardcode topic seperti TECH, PHILOSOPHY seperti ini:
1myProfile.interests += "TECH - Mobile Engineering"2myProfile.interests += "TECH - Augmented Reality"3myProfile.interests += "PHILOSOPHY - Libertarianism"
Kita bisa merefactornya menggunakan Lambda dengan custom Scope menjadi seperti ini:
1interests { // this: InterestScope2 tech("Mobile Engineering")3 tech("Augmented Reality")4 philosophy("Libertarianism")5}
Silakan tambahkan kode berikut:
1fun Profile.interests(builder: InterestScope.() -> Unit) {2 interests += InterestScope().apply(builder).interests3}45class InterestScope {6 var interests: List<String> = mutableListOf()7 private set89 fun tech(topic: String) {10 interests += "TECH - $topic"11 }1213 fun philosophy(topic: String) {14 interests += "PHILOSOPHY - $topic"15 }16}
InterestScope yang baru saja kita buat sebenarnya masih ada issue, yaitu kita bisa menggunakan Scope tersebut di mana pun, bukankah seharusnya kita hanya bisa menggunakan Scope tersebut di dalam lambda profile saja ?
Untuk mengatasi hal tersebut, kita bisa memanfaatkan fitur pada Kotlin bernama Context Receiver, silakan modifikasi kodenya menjadi seperti ini:
2private object ProfileContext34fun Profile.interests(builder: InterestScope.() -> Unit) {6 with(ProfileContext) {7 interests += InterestScope().apply(builder).interests9 }10}1113context(ProfileContext)14class InterestScope {15 var interests: List<String> = mutableListOf()16 private set1718 fun tech(topic: String) {19 interests += "TECH - $topic"20 }2122 fun philosophy(topic: String) {23 interests += "PHILOSOPHY - $topic"24 }25}
Jadi, ketika kita mencoba mengakses InterestScope dari luar lambda profile, maka akan muncul error seperti ini:
NOTE: Pada saat artikel ini ditulis, Context Receiver merupakan fitur yang masih experimental, untuk menggunakannya, silakan tambahkan kode berikut di dalam file build.gradle.kts:
build.gradle.kts1tasks.withType(KotlinCompile::class.java) {2 kotlinOptions.freeCompilerArgs += "-Xcontext-receivers"3}
Berikut adalah kode lengkap dari tutorial ini:
App.kt1package dev.alfin.kotlincode23import java.time.LocalDate4import java.time.Month56fun main() {7 val myProfile = profile {8 name = "Alfin"9 dob = 10 feb 20031011 contact {12 email = "me@alfin.dev"13 }1415 interests {16 tech("Mobile Engineering")17 tech("Augmented Reality")18 philosophy("Libertarianism")19 }20 }21}222324// MARK: - Data Structure2526class Profile {27 var name: String? = null28 var dob: LocalDate? = null29 var contact: Contact? = null30 var interests = mutableListOf<String>()31}3233class Contact {34 var email: String? = null35}363738// MARK: - DSL3940fun profile(builder: Profile.() -> Unit): Profile {41 return Profile().apply(builder)42}4344fun Profile.contact(builder: Contact.() -> Unit) {45 contact = Contact().apply(builder)46}4748infix fun Int.feb(year: Int): LocalDate = LocalDate.of(year, Month.FEBRUARY, this)4950private object ProfileContext5152fun Profile.interests(builder: InterestScope.() -> Unit) {53 with(ProfileContext) {54 interests += InterestScope().apply(builder).interests55 }56}5758context(ProfileContext)59class InterestScope {60 var interests: List<String> = mutableListOf()61 private set6263 fun tech(topic: String) {64 interests += "TECH - $topic"65 }6667 fun philosophy(topic: String) {68 interests += "PHILOSOPHY - $topic"69 }70}
Oke mungkin itu saja yang bisa saya bagikan kali ini, kalau kamu merasa artikel ini bermanfaat silakan Like & Share artikel ini ke teman-teman kamu atau jika kamu punya pertanyaan, tulis aja di kolom komentar, Thank you! 😁 🙏
Referensi
Always open to new ideas. 🕊️
Articles that you might want to read.
let, with, run, apply, dan also pada Kotlin.
Menulis kode yang mudah untuk di-test pada Swift.
Tutorial multilingual menggunakan react-i18next