evilfactorylabs

Cover image for Bersukaria dengan Gleam
Rizaldy
Rizaldy

Posted on

Bersukaria dengan Gleam

Akhirnya libur telah tiba! Atau mungkin akan berakhir.

Terbebas dari rutinitas adalah waktu yang tepat untuk melakukan hal baru, atau mungkin untuk melakukan hal lama yang sempat terhiraukan. Intinya, kita memiliki kebebasan, dan yang saya pilih adalah mempelajari bahasa pemrograman baru, dari sekian banyak aktivitas yang bisa dipilih.

Dan bahasa pemrograman tersebut bernama Gleam.

Gleam menjual dirinya sebagai "a friendly language for building type-safe systems that scale!" yang membuat pertanyaan seperti "memangnya se-asik apa si Gleam ini?". Gleam sudah lama berada di radar saya bahkan sebelum mencapai versi 1 pada 4 maret 2024 kemarin. Gleam bagi saya menjadi bahasa yang "terlalu menarik untuk dihiraukan dan terlalu disayangkan untuk dicoba" yang entah arahnya kemana. Daftar paling atas bahasa pemrograman lain yang ingin saya coba adalah Swift dan Elixir, karena rasa penasaran.

Swift berhasil dicoba dengan mengorbankan status pekerjaan saya selama 3 bulan namun saat ke Elixir hasrat saya akan rasa penasaran tersebut menjadi memudar. Elixir terlalu rumit untuk "yang ingin saya lakukan" pada saat itu. Lalu saya teringat Gleam, karena Gleam sendiri sedikit terpengaruh oleh Elixir dan sama-sama menggunakan BEAM nya Erlang.

Melihat ke dokumentasinya, saya dihadapkan dengan kode contoh seperti ini:

import gleam/io

pub fn main() {
  io.println("Hello, Joe!")
}
Enter fullscreen mode Exit fullscreen mode

Mengandalkan intuisi, saya ubah menjadi:

import gleam/io

pub fn main() {
  "Hello, Joe!" |> io.println
}
Enter fullscreen mode Exit fullscreen mode

Dan bekerja!

Sejak saat itu saya memutuskan untuk memberikan waktu saya untuk mempelajari Gleam dengan semangat "trial & error" dan terbitlah tulisan ini!

Gleam dalam 5 menit

Saya tidak akan memberikan tutorial tentang Gleam disini, namun favorit saya dalam mempelajari bahasa pemrograman baru adalah Learn X in Y minutes. Untuk instalasi, silahkan kunjungi situs Gleam, bahkan tidak perlu mengikuti coding bootcamp untuk bisa memasang Gleam di komputer!

Gleam akan terasa familiar untuk penulis kode yang:

  • Tidak asing dengan pemrograman berparadigma fungsional
  • Menyukai "type system"
  • Mudah bergaul dengan compiler

Gleam bukanlah scripting language jadi gleam run main.gleam tidak akan bekerja. Selain itu, per tulisan ini diterbitkan, ada 2 target untuk kode yang ditulis oleh Gleam: Erlang dan JavaScript. Jika untuk Erlang, maka hasil kode yang dibuat akan dijalankan di BEAM. Dan untuk JavaScript itu sendiri, dapat dijalankan oleh JavaScript runtime apapun seperti Bun ataupun Node.

Gleam mendukung konsep Foreign Function Interface (FFI) yang memungkinkan untuk memanggil bagian kode lain dari bahasa pemrograman lain, di Gleam. Dan per tulisan ini diterbitkan, bahasa yang didukung adalah JavaScript, Erlang dan Elixir. Misal, seperti mengakses nilai document.title dari browser via JavaScript di kode Gleam:

// ffi.js
export function get_title() {
  return document.title
}

// document.gleam
@external(javascript, "./ffi.js", "get_title")
fn get_title() -> String

io.debug(get_title()) // "New Post"
Enter fullscreen mode Exit fullscreen mode

Kode diatas tentu saja akan berjalan jika target nya adalah JavaScript.

Bagian favorit yang saya senangi adalah di pemuatan module nya. Kode io.debug/1 diatas berasal dari import gleam/io dan that's it: eksplisit. Saya kurang nyaman dengan bahasa pemrograman yang menggunakan konsep "prelude" ataupun module-wide import misal seperti import Foundation di Swift lalu bisa menggunakan apapun yang ada di module Foundation tersebut, misal hal-hal untuk memanipulasi String ataupun untuk mengakses Date.

Bagian favorit terakhir yang saya senangi adalah di type system nya, dan tulisan ini akan menjual banyak tentang itu. Saya bukan penggemar "type system" ini, sampai hari ini pun saya tidak menyukai TypeScript. Tapi semenjak saya mempelajari Gleam, saya menjadi menyukai type sistem ini, meskipun tetap masih tidak menyukai TypeScript.

Saat saya melihat kode seperti ini:

fn slugify(text: String) -> Result(String, Error)
Enter fullscreen mode Exit fullscreen mode

Saya bisa menjaga ekspektasi saya dari jenis parameter yang dibutuhkan sampai ke hasil akhir. Termasuk kemungkinan-kemungkinan lain yang mungkin terjadi.

Tentu ada hal lain seperti Pattern Matching, Pipelines, dan hal-hal basic seperti flow control yang belum sempat disebut, tapi saya rasa 5 menit saya sudah habis?

Fizz Buzz

Masih disini? Oke, sepertinya cukup menjual ya?

Merujuk ke lini masa di twitter, tampaknya para penulis kode senang bermain dengan platform LeetCode untuk memperkuat... apa namanya? Fundamental?

Dari berbagai "masalah" yang ada juga di LeetCode, salah satu yang cukup populer adalah Fizz Buzz. Ini seringkali digunakan dalam wawancara kerja yang entah motivasinya apa.

Fizz Buzz intinya adalah begini: Program harus mencetak angka dari 1 sampai N, tapi dengan catatan:

  • Jika angka adalah kelipatan 3, cetak "Fizz"
  • Jika angka adalah kelipatan 5, cetak "Buzz"
  • Jika angka adalah kelipatan keduanya (3 dan 5), cetak "FizzBuzz"
  • Selainnya, cetak angka sebagaimana adanya

Berarti, jika n=4, program harusnya mencetak seperti ini:

1 2 Fizz 4
Enter fullscreen mode Exit fullscreen mode

Masalah Fizz Buzz ini seringkali ditemukan ketika membuat AI berbasis blockchain menggunakan ring buffer.

Oke oke maaf candaannya tidak lucu.

Intinya, ini berguna untuk melatih cara berpikir™ dan dalam Gleam, kode nya kurang lebih seperti ini:

import gleam/int
import gleam/io
import gleam/list
import gleam/string

pub fn main() {
  print_fizzbuzz(15)
}

fn print_fizzbuzz(n: Int) {
  list.range(1, n)
  |> list.map(fn(i) {
    case i % 3, i % 5 {
      0, 0 -> "FizzBuzz "
      0, _ -> "Fizz "
      _, 0 -> "Buzz "
      _, _ -> int.to_string(i) <> " "
    }
  })
  |> string.concat
  |> io.println
}
Enter fullscreen mode Exit fullscreen mode

Saya rasa kodenya cukup self-explanatory, bukan?

Versi yang lebih "rapih" nya mungkin seperti ini:

import gleam/int
import gleam/io
import gleam/list
import gleam/string

pub fn main() {
  print_fizzbuzz(15)
}

fn fizzbuzz(i: Int) -> String {
  case i % 3, i % 5 {
    0, 0 -> "FizzBuzz "
    0, _ -> "Fizz "
    _, 0 -> "Buzz "
    _, _ -> int.to_string(i) <> " "
  }
}

fn print_fizzbuzz(n: Int) {
  list.range(1, n)
  |> list.map(fizzbuzz)
  |> string.concat
  |> io.println
}
Enter fullscreen mode Exit fullscreen mode

Kode nya exactly melakukan yang berada dalam catatan tersebut, karena kita tahu: jika hasilnya 0 berarti itu adalah kelipatannya, dan tanda _ intinya adalah "nilai yang tidak kita dipedulikan", dan... that's it?

Wow, setelah kita bisa menyelesaikan FizzBuzz, daya berpikir dan/atau berlogika kita naik sekian persen!

Kasus nyata

Baiklah, anggap contoh FizzBuzz diatas adalah kasus imajiner.

Kita akan membuat HTTP server sederhana untuk membuat "FizzBuzz as a service (FBaaS)" sehingga "angka" nya bisa dinamis tergantung permintaan. Disini kita akan menggunakan elli untuk web server nya melalui gleam_elli, dan juga kita akan berkenalan dengan beberapa sintaks Gleam yang akan sering digunakan.

Kode nya sederhana:

import gleam/bytes_tree
import gleam/result

import gleam/http/request
import gleam/http/response

import gleam/int
import gleam/list
import gleam/string

import gleam/http/elli

pub fn main() {
  elli.become(service, on_port: 3000)
}

fn service(req: request.Request(t)) -> response.Response(bytes_tree.BytesTree) {
  let numbers =
    // ambil nilai dari request path
    // misal: /4 = ["4"], /4/index.php = ["4", "index.php"]
    request.path_segments(req)
    // ambil nilai dari elemen pertamanya aja
    |> list.first
    // jika error (kosong), buat menjadi string kosong
    |> result.unwrap("")
    // ubah string tadi menjadi integer
    |> int.parse
    // jika error (bukan angka), fallback menjadi 1
    |> result.unwrap(1)

  let fizzbuzz = print_fizzbuzz(numbers)

  let body = bytes_tree.from_string(fizzbuzz)

  response.new(200)
  |> response.set_body(body)
}

fn print_fizzbuzz(n: Int) {
  list.range(1, n)
  |> list.map(fizzbuzz)
  |> string.concat
}

fn fizzbuzz(i: Int) -> String {
  case i % 3, i % 5 {
    0, 0 -> "FizzBuzz "
    0, _ -> "Fizz "
    _, 0 -> "Buzz "
    _, _ -> int.to_string(i) <> " "
  }
}
Enter fullscreen mode Exit fullscreen mode

Dan berikut hasilnya:

Image description

Sangat membosankan.

Sekarang, anggap kita punya masalah: setiap pemanggilan fungsi fizzbuzz memiliki penalti 0.5s. Jadi, untuk kasus print_fizzbuzz(4), setidaknya menghabiskan 2 detik untuk menampilkan "1 2 Fizz 4" yang anggap karena pemanggilan function tersebut dilakukan di tempat lain yang anggap lagi melalui RPC.

Image description

Bagaimana cara mengatasinya?

Yap, benar sekali: memoization. Kita akan menyimpan nilai yang sebelumnya sudah pernah di "komputasi" dan ini bagian favorit saya: kita akan menulis kode "kasus nyata/konkret" sebagaimana subjudul dari tulisan ini.

Termasuk menulis sedikit kode Erlang!

Pertama, kita memerlukan "wadah" untuk menyimpan cache dari per-memoization ini, dan wadah yang paling tepat menurut saya adalah Erlang Term Storage (ETS) alih-alih Redict ataupun layanan lain seperti yang dijual oleh Vercel.

Kita hanya akan membuat 4 function untuk berinteraksi dengan ETS yang kodenya adalah seperti ini:

% cache.erl

-module(cache).

-export([create_table/0, lookup/2, insert/3, delete/2]).

create_table() ->
    Table = ets:new(fzbz, [set, public, {write_concurrency, true}, {read_concurrency, true}]),
    Table.

lookup(Table, Key) ->
    Now = erlang:system_time(second),
    case ets:lookup(Table, Key) of
        [{_, Value, Expiry}] when Now =< Expiry ->
            {ok, Value};
        [{_, _, _}] ->
            ets:delete(Table, Key),
            {error, nil};
        [] ->
            {error, nil}
    end.

insert(Table, Key, Value) ->
    TTL = erlang:system_time(second) + 5,
    ets:insert(Table, {Key, Value, TTL}).

delete(Table, Key) ->
    ets:delete(Table, Key).
Enter fullscreen mode Exit fullscreen mode

Bagian {write_concurrency, true}, {read_concurrency, true} hanya agar terlihat keren aja.

Anyway, disitu terlihat bahwa kita menggunakan TTL untuk "cache eviction" selama 5 detik, hanya sebagai demo.

Perlu diketahui bila nama module di Erlang harus sesuai dengan nama file nya, dan jika beda, compiler akan memberitahumu juga :))

Sekarang, kita akan membuat Foreign Function Interface (FFI) untuk memanggil kode Erlang dari kode Gleam kita:

@external(erlang, "cache", "create_table")
fn create_table() -> Dynamic

@external(erlang, "cache", "lookup")
fn get(table: Dynamic, key: Int) -> Result(String, Nil)

@external(erlang, "cache", "insert")
fn insert(table: Dynamic, key: Int, value: String) -> Nil

@external(erlang, "cache", "delete")
fn delete(table: Dynamic, key: Int) -> Nil
Enter fullscreen mode Exit fullscreen mode

Kode diatas maksudnya, jika kita memanggil get di Gleam, berarti kita memanggil lookup dari module cache yang ada di Erlang. Begitupula seterusnya.

Sekarang, kita akan membuat umm apa namanya.. Struct? Oh, Records! Anyway saya senang dengan pemanggilan seperti ini: cache.get(k) daripada harus get_cache(cache, k) karena alasan pribadi, jadi kita akan membuat Record nya:

type ETS {
  ETS(
    get: fn(Int) -> Option(String),
    set: fn(Int, String) -> Nil,
    del: fn(Int) -> Nil,
  )
}
Enter fullscreen mode Exit fullscreen mode

Ada 3 API yang nantinya bisa kita gunakan. Sekarang, kita buat penerapannya!

fn new_ets() -> ETS {
  let t = create_table()

  ETS(
    get: fn(k) {
      case get(t, k) {
        Ok(v) -> Some(v)
        Error(_) -> None
      }
    },
    set: fn(k, v) { insert(t, k, v) },
    del: fn(k) { delete(t, k) },
  )
}
Enter fullscreen mode Exit fullscreen mode

Ada 3 type baru yang kita lihat disini: Option, Some dan None. Beberapa mungkin sudah familiar, namun bagi yang belum:

  • Option berarti "bisa jadi ada nilainya bisa jadi tidak", dan jika Option(String) berarti "bisa jadi nilainya tidak ada, tapi jika ada, pasti tipe nya String"
  • Some berarti nilainya ada!
  • None i mean, you know, right???

Dan markicob.

Image description

Ok it works! Sekarang mari kita terapkan masalahnya.

Pertama, tambahkan process.sleep(500) di function fizzbuzz, untuk, biasalah, simulasi:

import gleam/erlang/process

fn fizzbuzz(i: Int) -> String {
  process.sleep(500)

  case i % 3, i % 5 {
    0, 0 -> "FizzBuzz "
    0, _ -> "Fizz "
    _, 0 -> "Buzz "
    _, _ -> int.to_string(i) <> " "
  }
}
Enter fullscreen mode Exit fullscreen mode

Jika saya memanggil /4, setidaknya membutuhkan 2 detik untuk selesai. Dan jika /8, berarti berapa? Benar sekali, 4 detik. Markicob.

Image description

Ok masalah kita berhasil terbuat. Katanya engineering adalah tentang merekayasa, bukan?

Sekarang kita buat solusinya. Yang berarti, sebisa mungkin jangan panggil fizzbuzz/1 jika memang tidak harus. Ya, memoization pada akhirnya tentang itu.

Pertama, berarti kita perlu mengubah ini:

fn print_fizzbuzz(n: Int) {
  list.range(1, n)
  |> list.map(fizzbuzz)
  |> string.concat
}
Enter fullscreen mode Exit fullscreen mode

Menjadi ini:

fn memoized_fizzbuzz(i: Int, cache: ETS) -> String {
  case cache.get(i) {
    Some(result) -> result
    None -> {
      let result = fizzbuzz(i)
      cache.set(i, result)
      result
    }
  }
}

fn print_fizzbuzz(n: Int, cache: ETS) {
  list.range(1, n)
  |> list.map(memoized_fizzbuzz(_, cache))
  |> string.concat
}
Enter fullscreen mode Exit fullscreen mode

Kode diatas terdapat 2 fetish functional programmer yang digunakan: Pattern matching dan pipelines.

Dan hasilnya?

Image description

Sesuai harapan warga! Biar keren, kita akan menampilkan status seperti Cache HIT: n dan Cache MISS: n di standard output:

fn memoized_fizzbuzz(i: Int, cache: ETS) -> String {
  case cache.get(i) {
    Some(result) -> {
      io.println("Cache HIT: " <> int.to_string(i))

      result
    }
    None -> {
      io.println("Cache MISS: " <> int.to_string(i))

      let result = fizzbuzz(i)
      cache.set(i, result)
      result
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Dan seperti biasaa:

Image description

Melakukan optimasi sederhana ini layak untuk diberikan promosi!

Function color

Ah, here we go again.

Di bahasa pemrograman modern, umumnya kita akan bertemu dengan sintaks async/await yang berguna untuk melakukan sesuatu secara asinkron lalu melakukan sesuatu saat selesai. Pertanyaannya, kapan function tersebut dibuat async sehingga harus di await, teman-teman?

Misal, kode seperti ini:


async function get_user() {
  await fetch(...)
}

try {
  const user = await get_user()
} catch (err) {
  throw Error(err)
}
Enter fullscreen mode Exit fullscreen mode

Kode diatas sangat jelas: pemanggilan ke tempat lain (misal server) melalui jaringan memerlukan waktu untuk menunggu response dari si server tersebut dan kita tidak ingin kode tersebut memblokir kode lain.

Hasil yang kita harapkan adalah JSON jika berhasil atau error jika gagal (misal karena bad gateway). Hasil tersebut akan di proses dengan then jika berhasil dan catch jika gagal.

Kembali ke Gleam, hmm, pertama, pada dasarnya, kita akan sering membuat sebuah function yang hasilnya adalah Result(Something, Error), sehingga tidak jarang kita akan menggunakan pendekatan seperti ini:

function get_user(username: String) -> Result(User, Error)
// memanggil ke db

...

fn capitalize_user(username: String) -> String {
  case get_user(username) {
    Ok(user) -> user
    Error(_) -> ""
  } |> string.capitalise
}
Enter fullscreen mode Exit fullscreen mode

Jika merasa yakin get_user tidak pernah gagal, nah, jangan seyakin itu. Tidak ada yang bisa menjamin jika db bisa diakses.

Untuk kasus lain, yang kita benar-benar yakin bahwa tidak akan gagal, penggunaan Result tentu tidak diperlukan. Seperti fizzbuzz kita sebelumnya karena tidak berurusan dengan... hmmm... gampangnya sistem eksternal. Maka dari itu kita membuat returnya String karena pasti String.

Oke oke, kita membahas dua hal berbeda untuk satu kasus. Tapi ini gampangnya: Di contoh JavaScript kita sebelumnya, program yang menjalankan kode JavaScript tersebut memiliki tugas lain: menggambar sesuatu. Konteks blocking nya adalah "belum menggambar sesuatu karena menunggu kode lain selesai".

Di contoh kode Gleam kita, kita tidak harus melakukan hal lain khususnya yang harus dilakukan secara parallel. Hal yang dilakukan di /{n} tidak memblokir kode yang berjalan di misalnya /api/user/{n} karena pada akhirnya itu tanggung jawab web server (elli) untuk dapat bisa memproses permintaan masuk dalam kondisi apapun.

Dan beda proses anyway.

Yang maksudnya, tentu, di Gleam bisa melakukan operasi asinkron. Bahkan BEAM—runtime Gleam untuk target Erlang—dirancang untuk itu! Seperti, saat client memanggil /register, client tidak perlu menunggu proses:

  • penyimpanan ke DB
  • pemanggilan ke layanan lain melalui SMTP
  • pemanggilan ke layanan lain untuk menentukan skor reputasi si user
  • pengiriman ke layanan lain melalui webhook

...selesai.

Dan jika menganggap async/await hanyalah syntax sugar, Gleam pun memiliki syntax sugar! Kita bisa menggunakan use saat waktunya telah tiba! Kabar baiknya, kita tidak perlu memusingkan kapan harus menggunakan use karena cukup jelas: Jika outputnya Result, berarti bisa menggunakan use.

Intinya, di Gleam dapat dengan mudah menentukan kapan function tersebut murni dan kapan function tersebut memiliki efek samping.

Dan saya lupa menyebutkan: Semua hal (struktur data) di Gleam bersifat immutable. Ah, fetish lain yang disenangi functional programmer. Tapi bukan berarti tidak bisa melakukan hal-hal yang memiliki efek samping seperti menulis sesuatu ke file ataupun menampilkan sesuatu di standard output.

Gleam tidak memiliki konsep global state, apalagi mengubah state yang ada. Di FBaSS, kita menggunakan ETS nya Erlang untuk "memanipulasi" state, yang meskipun Erlang pun immutable... tapi ETS adalah pengecualian.

Akan panjang jika membahas mutable vs immutable, tapi semoga sedikit mencerahkan terkait "warna function" di kasus Gleam.

Kekurangan

Tentu Gleam memiliki kekurangan, dan Gleam tidak bisa memenuhi keinginan semua orang.

Yang pertama, sebagai scripting language. Akan menyenangkan bisa menjalankan gleam check_uptime.gleam tapi saya bisa menulis Shell script juga.

Kedua, function arity. Di Elixir, kita bisa membuat 1 nama function namun dengan parameter berbeda, misal seperti ini:

def get_user(username)

def get_user(username, name)
Enter fullscreen mode Exit fullscreen mode

Dan di Erlang juga bisa.

Di Gleam, setidaknya kita bisa menggunakan pattern matching misal seperti ini:

case something {
  [] -> handle_ketika_kosong()
  [username] -> get_user(username)
  [username, name, ..] -> get_by_username_or_name(username, name)
}
Enter fullscreen mode Exit fullscreen mode

Terakhir, uh, dynamics.

Gleam adalah statically typed, strict, namun setidaknya melakukan type inference juga. Saat ingin berurusan dengan struktur data di luar Gleam, apa yang harus dilakukan? Benar sekali, decoding.

Misal, kita ingin sebuah data, yang diambil melalui jaringan, dalam format JSON seperti ini:

{"username": "rizaldy", "password": "hunter2"}
Enter fullscreen mode Exit fullscreen mode

Kita tahu jika ada field username dan password berikut dengan tipe datanya. Tapi Gleam—spesifiknya si compiler nya—tidak tahu. Untuk menyimpan data tersebut di Gleam, Record adalah tipe data yang cocok:

type User {
  User(username: String, password: String)
}
Enter fullscreen mode Exit fullscreen mode

Decoder sederhana yang bisa kita gunakan adalah seperti ini:

dynamic.decode2(
  User,
  dynamic.field("username", dynamic.string),
  dynamic.field("password", dynamic.string)
)(data_si_json)
Enter fullscreen mode Exit fullscreen mode

Lalu dimana bagian menyebalkannya? Benar sekali, ketika field berjumlah banyak (let's say 33) karena kita harus memasukkan semua field nya daaaan dynamic.decode hanya sampai 8 sehingga kita perlu mengakalinya seperti melakukan rekursif + pattern matching.

Selain 3 hal diatas, saya rasa sudah cukup oke. Maturity? Production ready? Enterprise ready? Nah, ini rule of thumb nya: kalo JetBrains sudah membuat IDE khusus untuk bahasa pemrograman X, berarti bahasa pemrograman X mature dan enterprise/production ready. That's it.

Sangkalan

kemungkinan konten sensitif

Ingat jika Gleam menurut saya adalah bahasa yang "terlalu menarik untuk dihiraukan dan terlalu disayangkan untuk dicoba" saat di awal-awal? Ini sedikit... rumit.

Konteks "Friendly" di Gleam adalah ke inklusivitas, dan inklusif itu... baik. Komunitas Gleam menyertakan siapapun tanpa membedakan hak dan kewajiban, jika menyebut dua.

Gleam dengan terbuka mendukung "Black lives matter. Trans rights are human rights. No nazi bullsh*t." dan, well, tidak ada salahnya juga, bukan? Jika saya termasuk di komunitas Gleam, secara tidak langsung saya pun mendukung apa yang komunitas Gleam dukung.

Mungkin ini hanya tentang kepercayaan, atau mungkin lebih dari itu. Mari lihat diluar komunitas Gleam: ada komunitas yang melakukan boikot terhadap merk tertentu sebagai bentuk kepedulian yang menjelaskan siapa berpihak kepada siapa. Ada tanggung jawab moral jika menjadi naif namun menjadi apatis pun bukan jalan keluar.

Saya pribadi bukan tidak setuju, namun hanya tidak mengerti. Tentu, jawaban dari tidak mengerti adalah dengan mempelajari, tapi, gimana, ya? Misal, dulu isu langganan di Indonesia adalah terkait ajaran sesat. Dianggap 'sesat' karena tidak sesuai dengan ajaran yang seharusnya, menurut kita.

Mereka padahal yakin yang mereka yakini itu benar, dan mungkin, mereka pun tidak menyalahkan yang di luar mereka. Alasan dibubarkannya biasanya karena "meresahkan masyarakat" karena mungkin bisa menyebabkan hal lain, seperti hal ekstrim, atau "radikal", yang mana kata yang biasa digunakan oleh media.

Perhatian saya sebenarnya hanya satu: LGBTQ. Well, tentu manusia memiliki haknya sebagai manusia. Kewajibannya sebagai manusia tidak bisa dibedakan karena suku, kepercayaan, ataupun ras. Jika saya tidak boleh tinggal di Bandung hanya karena saya percaya bumi itu bulat, tentu itu tindakan diskriminasi.

Yang saya pahami, gender hanya ada dua sebagaimana yang saya pahami jika agama samawi hanya ada tiga. Mungkin perbandingannya tidak adil, tapi saya rasa kasusnya sama: jika ada agama baru yang memiliki rasul lain, maka tidak bisa disebut sebagai agama asmawi, karena rasul terakhir yang diyakini adalah Nabi Muhammad SAW.

Tapi bukan berarti tidak ada agama lain. Ada agama ardhi yang tidak memiliki rasul dan ajarannya bukan berdasarkan "wahyu" dan, ya, itu tidak mengubah keyakinan saya jika agama asmawi hanya ada 3.

Kembali ke konteks, yang saya yakini, sekali lagi, gender hanya ada dua. Dan diskriminasi memang menyebalkan. Lucu jika tergabung di sebuah komunitas namun tidak meyakini apa yang komunitas tersebut yakini. Atau perjuangkan. Bagian panjang ini sebagai sangkalan jika nantinya ada yang bertanya "lo dukung ini riz?" dan semoga menjawab dimana posisi saya.

Yeah, di ajaran saya, saya hanya meyakini apa yang saya yakini. Tidak ada tukar-menukar, dan ini jalan terbaik dalam hal toleransi antar keyakinan.

Penutup

Gleam secara keseluruhan adalah bahasa yang menyenangkan! Menyenangkan untuk dibaca, ditulis dan dijalankan. Saya jadi teringat dengan Go, sebelumnya, saya cukup senang menulis Go, lalu berubah saat harus menulis Go karena pekerjaan. Haha.

Gleam, di lain sisi, ekosistemnya masih relatif kecil. Masih banyak dokumentasi yang perlu diperkaya, tutorial yang perlu diperbanyak dan tools/library yang perlu dibuat. Pekerjaan yang mebutuhkan Gleam? Well, masih terlalu dini untuk mempertanyakannya.

Tapi tools standard nya sudah lebih dari cukup! Compiler nya, dukungan LSP nya, standard library nya, dan lain sebagainya.

Sebagai penutup, saya penasaran apakah Gleam akan mudah diterima di komunitas pengembang di Indonesia, khususnya sebagai niche programming language. Sintaksnya mungkin cukup mudah dipahami, tapi paradigmanya sebagai functional programming dan strongly typed?

We'll see!

Kode akhir

import gleam/bytes_tree
import gleam/io
import gleam/result

import gleam/http/elli
import gleam/http/request
import gleam/http/response

import gleam/int
import gleam/list
import gleam/string

import gleam/dynamic.{type Dynamic}
import gleam/option.{type Option, None, Some}

import gleam/erlang/process

type ETS {
  ETS(
    get: fn(Int) -> Option(String),
    set: fn(Int, String) -> Nil,
    del: fn(Int) -> Nil,
  )
}

@external(erlang, "cache", "create_table")
fn create_table() -> Dynamic

@external(erlang, "cache", "lookup")
fn get(table: Dynamic, key: Int) -> Result(String, Nil)

@external(erlang, "cache", "insert")
fn insert(table: Dynamic, key: Int, value: String) -> Nil

@external(erlang, "cache", "delete")
fn delete(table: Dynamic, key: Int) -> Nil

fn new_ets() -> ETS {
  let t = create_table()

  ETS(
    get: fn(k) {
      case get(t, k) {
        Ok(v) -> Some(v)
        Error(_) -> None
      }
    },
    set: fn(k, v) { insert(t, k, v) },
    del: fn(k) { delete(t, k) },
  )
}

pub fn main() {
  let cache = new_ets()

  elli.become(fn(req) { service(req, cache) }, on_port: 3000)
}

fn service(
  req: request.Request(t),
  cache: ETS,
) -> response.Response(bytes_tree.BytesTree) {
  let numbers =
    // ambil nilai dari request path
    // misal: /4 = ["4"], /4/index.php = ["4", "index.php"]
    request.path_segments(req)
    // ambil nilai dari elemen pertamanya aja
    |> list.first
    // jika error (kosong), buat menjadi string kosong
    |> result.unwrap("")
    // ubah string tadi menjadi integer
    |> int.parse
    // jika error (bukan angka), fallback menjadi 1
    |> result.unwrap(1)

  let fizzbuzz = print_fizzbuzz(numbers, cache)

  let body = bytes_tree.from_string(fizzbuzz)

  response.new(200)
  |> response.set_body(body)
}

fn memoized_fizzbuzz(i: Int, cache: ETS) -> String {
  case cache.get(i) {
    Some(result) -> {
      io.println("Cache HIT: " <> int.to_string(i))

      result
    }
    None -> {
      io.println("Cache MISS: " <> int.to_string(i))

      let result = fizzbuzz(i)
      cache.set(i, result)
      result
    }
  }
}

fn print_fizzbuzz(n: Int, cache: ETS) {
  list.range(1, n)
  |> list.map(memoized_fizzbuzz(_, cache))
  |> string.concat
}

fn fizzbuzz(i: Int) -> String {
  process.sleep(500)

  case i % 3, i % 5 {
    0, 0 -> "FizzBuzz "
    0, _ -> "Fizz "
    _, 0 -> "Buzz "
    _, _ -> int.to_string(i) <> " "
  }
}
Enter fullscreen mode Exit fullscreen mode

Fun fact yang tidak fun: saya menggunakan code block "elixir" untuk kode gleam hanya agar kodenya memiliki warna.

Top comments (0)