ReasonML untuk yang pusing dengan ReasonML

How to make programming REasonable?

Gue termasuk orang yang baru dengan ReasonML tapi udah memberanikan diri untuk menulis kode ReasonML di lingkungan production yang digunakan untuk mentenagai link tersebut juga.

Tulisan ini mungkin cocok untuk orang yang sudah mengenal tentang ReasonML tapi masih pusing dengan ReasonML itu sendiri, jika kamu belum berkenalan dengan ReasonML, tulisan ini mungkin cocok untukmu.

Mari kita mulai dari yang paling dasar, apa sih sebenarnya ReasonML itu?

A Programming Language

ReasonML adalah sebuah bahasa program, bukan framework apalagi library. Setiap bahasa program ada yang berjenis static typing dan dynamic typing, Reason ini static.

Setiap bahasa program ada yang interpreted dan compiled, Reason ini compiled. Compiler Reason ini ditulis menggunakan OCaml, bahkan Wikipedia menyebut Reason sebagai "Syntax Extension" untuk OCaml.

Berbicara tentang compiled, target kompilasi Reason ini beragam. Yang paling populer adalah menjadi sebuah kode JavaScript, via BuckleScript. Sederhananya, anggap BS ini seperti Asm.js yang mengubah kode C menjadi kode JavaScript.

Apakah BuckleScript adalah sebuah Compiler? Benar sekali, alih-alih mengubah kode Reason menjadi byte code ataupun menjadi machine code, BuckleScript mengubahnya menjadi kode JavaScript: Satu-satunya skrip yang bisa dieksekusi oleh peramban.

Runtime

Misal untuk bisa menjalankan kode JavaScript, runtime yang digunakan bisa v8 (Node.js) ataupun JavaScript engine lain yang digunakan di peramban. Karena Reason bukanlah bahasa yang di interpretasi, berarti Reason tidak memiliki VM untuk menjalankan byte code at least untuk saat ini.

Jika kita menggunakan Reason di project kita, berarti kita membutuhkan "toolchain" untuk bisa berinteraksi dengan kode Reason tersebut. Biasanya, toolchain yang sering digunakan adalah compiler, untuk mengubah kode Reason menjadi kode JavaScript yang bisa dimengerti oleh peramban.

Toolchain tersebut adalah bs-platform, BuckleScript. BuckleScript dibuat menggunakan OCaml, jadi kita harus berurusan dengan OCaml jika menggunakan Reason dan pastinya BuckleScript.

Standard Library (stdlib)

Bagaimana cara mengirim request http menggunakan JavaScript? Bisa via fetch ataupun https. 2 API tersebut sudah disediakan oleh JavaScript untuk melakukan request http secara "standard".

Bagaimana di Reason?

Sejauh ini belum menemukan "standard library" khususnya terkait kasus diatas, meskipun di Redex (Reason Package Index) ada library yang bernama bs-fetch. Menggunakan API dari standard library diasumsikan API tersebut disediakan langsung oleh bahasa program/platform, tanpa eksternal dependensi.

Meskipun kita bisa saja memanggil fetch di JavaScript dengan cara "interop", tapi tentu solusi tersebut bukanlah solusi yang elegan.

Sintaksis

Dalam memilih bahasa program baru, melihat "influenced by" tentu pilihan yang bijak untuk mengetahui apakah kita familiar dengan sintaks yang dimiliki. Seperti, JavaScript influenced by Java dan Java influenced by C++. Yang sederhananya, bila kita sudah familiar sedikit dengan C++ ataupun Java, seharusnya memahami JavaScript bukanlah hal yang lumayan sulit.

Reason ini influenced by OCaml, apakah kita familiar dengan OCaml? Dominan belum. Selain OCaml, yakni Haskell. Dan jawabannya sudah tentu sama dengan yang sebelumnya mengingat bahasa pemrograman yang paling populer disini adalah Java.

Yang berarti, masalah "sulit dimengerti" bukanlah hal yang aneh. Seperti, jika kamu terbiasa menulis huruf Latin pasti kamu lumayan kesulitan ketika menulis huruf arab.

Lihat sintaks berikut:

type human = Teacher | Director | Ormas

let greeting = person =>
  switch (person) {
  | Teacher => "Hey Professor!"
  | Director => "Hello Director."
  | Ormas => "Hello evilfactorylabs"
  };

Apakah sulit dimengerti? Salah satu yang sulit dimengerti oleh gue adalah Pattern Matching, contoh diatas tersebut. Menariknya, hal-hal yang ada di type diatas akan diubah menjadi number ataupun type lain tergantung kondisi. Karena type diatas "identik" dan tidak kompleks, maka diubah menjadi number. Misal Teacher diubah menjadi 0, Director menjadi 1 dan Ormas menjadi 2.

Lalu bagiamana dengan memahami sintaks ini?

// Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE
'use strict';

function greeting(person) {
  switch (person) {
    case /* Teacher */0 :
        return "Hey Professor!";
    case /* Director */1 :
        return "Hello Director.";
    case /* Ormas */2 :
        return "Hello evilfactorylabs";
    
  }
}

exports.greeting = greeting;

Tentu lebih mudah dipahami, bukan?

Misal, bagaimana bila kita ingin "menyapa" orang lain selain Teacher, Director, dan Ormas?

type human = Teacher | Director | Ormas | People(string)

let greeting = person =>
  switch (person) {
  | Teacher => "Hey Professor!"
  | Director => "Hello Director."
  | Ormas => "Hello evilfactorylabs"
  | People(name) => "Hello " ++ name
  };
  
Js.log(greeting(People("Fariz")))

Yang bila diubah menjadi JavaScript menjadi seperti ini:

// Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE
'use strict';

function greeting(person) {
  if (typeof person === "number") {
    switch (person) {
      case /* Teacher */0 :
          return "Hey Professor!";
      case /* Director */1 :
          return "Hello Director.";
      case /* Ormas */2 :
          return "Hello evilfactorylabs";
      
    }
  } else {
    return "Hello " + person[0];
  }
}

console.log(greeting(/* People */["Fariz"]));

exports.greeting = greeting;

Lihat bagaimana Reason melakukan "validasi" apakah tipe person tersebut number ataupun yang lain? Kita (JavaScript developer) kurang familiar dengan konsep "pattern matching" karena di JavaScript tidak ada konsep tersebut.

Karena kita melakukan "matching" biasanya berdasarkan nilainya, bukan "pattern" nya. Karena singkatnya, JavaScript tidak memiliki proses kompilasi.

Mental Model

Reason menggunakan OCaml sebagai "backing language" nya, dan tentunya bukan tanpa alasan selain karena alasan kecepatan. Dalam halaman What & Why nya Reason, disitu dijelaskan mengapa menggunakan OCaml sebagai "backing language" nya. Yang salah satunya adalah:

Side-effects, mutation & other escape hatches. These aren't usually the shiny selling points of a language; but being able to bridge toward a part of a codebase without an elaborate interop/rewrite is crucial for us at Facebook. OCaml defaults to immutable and functional code, but having the escape hatches makes the initial adoption sometimes simply possible.

Dan seperti yang kita tau, Immutability adalah konsep utama dalam Functional Programming. Dan karena fokus menggunakan paradigma Functional, di Reason kita akan berhadapan dengan hal-hal yang berkaitan dengan "functional" ini.

Yang pastinya memiliki mental model yang berbeda. Jika di "OOP-based" kita mengasumsikan kode berikut:

$user->getUserById(1337)

Berarti object user memanggil function bernama getUserById, di Reason berbeda. Di Reason, artinya melakukan "flip". Bila kita memanggil getUserById tersebut dengan cara getUserById(1337), di Reason bisa menjadi seperti ini:

1337->getUserById

Kode diatas adalah konsep "Pipe first" yang dimiliki Reason yang tidak memiliki runtime cost agar kode ditulis menjadi lebih mudah dibaca. Jika kita misalnya memiliki kode seperti ini:

console.log(atob(decodeURIComponent(someText)))

Tentu kurang enak dibaca, bukan? Di Reason kita bisa membuatnya menjadi seperti ini:

someText
  ->decodeURIComponent
  ->atob
  ->console.log

Yang tentunya menjadi lebih mudah dibaca.

Null

Null is evil, dan sayangnya beruntungnya di Reason tidak mengenal konsep null. Dan ini menjadi hal yang lumayan menantang. Sebelumnya mari kita jawab pertanyaan berikut, mengapa kita menggunakan null?

Sebagai placeholder?

Tidak juga. Mengapa terkadang kita membuat struktur table di database yang mana "nilai" nya boleh "bernilai" null?

Oke jawabannya kembali ke masing-masing ya, intinya, bagaimana kamu mendesain program dengan mental model yang kamu miliki terhadap null di bahasa program yang tidak memiliki konsep null?

Tentu lumayan sulit.

Untuk menghindari penggunaan null, kita biasanya menggunakan "placeholder" alias initial value. Karena di dynamic language berarti suatu tipe data pada variable berdasarkan nilainya, berarti jika variable tersebut memiliki tipe data string, kita buat initial value nya "", jika number berarti 0, dst.

Bagaimana bila nilai nya tidak bisa kita kontrol? Misal, kasus sederhana: Mengambil data dari remote server. Yang kita harapkan adalah int sedangkan yang datang adalah null?

Reason memiliki None dan Some, ini membantu kita untuk "mempertimbangkan ulang" tentang bagaimana kita menulis kode dengan mental model yang berbeda.

Penutup

Sejauh ini yang paling sulit adalah tentang paradigma, tentang bagaimana kita melihat sesuatu. Cara paling lumayan gampang untuk memahami ReasonML khususnya adalah menyingkirkan sementara pemahaman & pemikiran kita yang telah kita ketahui dari bahasa program lain.

ReasonML benar-benar mengubah cara pandang gue dalam membuat program, bahkan gue menggunakan "mode production" di konfigurasi Webpack gue ketika membuat gow, karena gue bergantung dengan kompiler di Reason yang sangat sensitif ini.

Tulisan ini adalah kumpulan "keresahan" gue terhadap menggunakan & mempelajari Reason, jika malah bingung atau merasa gak ngerti tujuan dari tulisan ini, memang itu tujuan gue.

Terima kasih sudah membaca, gue akan hadir kembali membahas ReasonML dengan kemasan yang berbeda! See you.

Menikmati tulisan ini?

Blog ini tidak menampilkan iklan, yang berarti blog ini didanai oleh pembaca seperti kamu. Gabung bersama yang telah membantu blog ini agar terus bisa mencakup tulisan yang lebih berkualitas dan bermanfaat!

Pendukung

Kamu?