Gleam: Type-Safe BEAM Language untuk Erlang & Elixir Ecosystem

Gleam adalah bahasa pemrograman fungsional yang berjalan di BEAM VM dengan type system yang kuat dan Hindley-Milner type inference. Gleam bisa compile ke BEAM bytecode dan JavaScript, menjembatani type safety dengan ekosistem Erlang/Elixir yang sudah matang.

1. Mengapa Gleam?

Gleam diciptakan oleh Louis Pilfold pada 2016. Bahasa ini dirancang untuk mengisi gap antara type safety dan ekosistem BEAM yang sudah terbukti. Dengan Gleam, Anda mendapatkan type system Hindley-Milner yang sound, tanpa runtime type errors, dan kompatibilitas penuh dengan library Erlang dan Elixir.

  • Type-safe — Hindley-Milner inference, tidak ada runtime type errors
  • BEAM VM — Fault tolerance, concurrency, dan hot code reloading
  • Dual target — Compile ke BEAM dan JavaScript
  • Erlang/Elixir interop — Gunakan semua library yang sudah ada
  • No null, no exceptions — Result type untuk error handling
  • Sintaks yang simpel — Sangat mudah dipelajari
💡 Gleam vs Elixir

Gleam dan Elixir berjalan di BEAM VM yang sama, tetapi Gleam memiliki static type system sementara Elixir dynamic. Gleam bisa memanggil Elixir dan Erlang code, dan sebaliknya. Pilih Gleam jika Anda menginginkan type safety yang lebih kuat.

2. Instalasi

Bash
# macOS
brew install gleam

# Windows (Scoop)
scoop install gleam

# Linux (using mise/asdf)
mise install erlang
mise install gleam

# Atau download binary dari gleam.run

# Prerequisites: Erlang/OTP 26+ harus terinstall
# macOS: brew install erlang
# Ubuntu: apt install erlang

# Verifikasi
gleam --version
# gleam 1.x.x

# Buat proyek baru
gleam new my_project
cd my_project

# Struktur:
# my_project/
# ├── gleam.toml
# ├── src/
# │   └── my_project.gleam
# ├── test/
# │   └── my_project_test.gleam
# └── README.md

# Build dan jalankan
gleam run

# Jalankan test
gleam test

# Build untuk JavaScript
gleam build --target javascript

# Add dependencies
gleam add gleam_http gleam_json lustre

3. Dasar-Dasar Gleam

src/basics.gleam
import gleam/io
import gleam/int
import gleam/string
import gleam/float

// Constants
const pi = 3.14159

pub fn main() {
  // Variables (immutable, always)
  let nama = "BeebaneLabs"
  let umur = 25
  let tinggi = 1.75
  let aktif = True

  // Type annotations (opsional)
  let x: Int = 42
  let s: String = "hello"
  let f: Float = 3.14
  let b: Bool = True

  // String operations
  io.println("Halo, " <> nama <> "!")
  io.println(string.length(nama) |> int.to_string)

  // String functions
  string.uppercase("hello")       // "HELLO"
  string.lowercase("WORLD")       // "world"
  string.contains("hello", "ell") // True
  string.split("a,b,c", ",")     // ["a", "b", "c"]
  string.replace("hello world", "world", "Gleam") // "hello Gleam"
  string.reverse("hello")         // "olleh"
  string.trim("  hello  ")        // "hello"

  // Int operations
  int.to_string(42)
  int.parse("42")       // Ok(42)
  int.absolute_value(-5) // 5
  int.max(10, 20)       // 20
  int.min(10, 20)       // 10

  // Float operations
  float.to_string(3.14)
  float.parse("3.14")   // Ok(3.14)
  float.ceiling(3.2)    // 4.0
  float.floor(3.8)      // 3.0
  float.round(3.5)      // 4.0

  // Lists
  let angka = [1, 2, 3, 4, 5]
  let lebih = list.prepend(angka, 0)  // [0, 1, 2, 3, 4, 5]
  let gabung = list.append(angka, [6, 7]) // [1, 2, 3, 4, 5, 6, 7]

  list.length(angka)      // 5
  list.reverse(angka)     // [5, 4, 3, 2, 1]
  list.contains(angka, 3) // True
  list.first(angka)       // Ok(1)
  list.last(angka)        // Ok(5)
  list.rest(angka)        // Ok([2, 3, 4, 5])

  // Pipe operator
  "  Hello World  "
  |> string.trim
  |> string.lowercase
  |> string.replace(" ", "_")
  |> io.println

  // If expression (bukan statement!)
  let grade = if umur >= 17 { "Dewasa" } else { "Anak" }
  io.println(grade)
}

Functions

src/functions.gleam
import gleam/int
import gleam/list
import gleam/io
import gleam/string

// Basic function
pub fn tambah(a: Int, b: Int) -> Int {
  a + b
}

// Recursive function
pub fn factorial(n: Int) -> Int {
  case n {
    0 -> 1
    1 -> 1
    _ -> n * factorial(n - 1)
  }
}

// Higher-order functions
pub fn apply_twice(f: fn(a) -> a, x: a) -> a {
  f(f(x))
}

// Lambda / anonymous functions
pub fn demo() {
  let kuadrat = fn(x: Int) -> Int { x * x }
  let hasil = kuadrat(5)
  io.debug(hasil) // 25

  // Lambda dengan pipe
  [1, 2, 3, 4, 5]
  |> list.map(fn(x) { x * 2 })
  |> list.filter(fn(x) { x > 4 })
  |> io.debug // [6, 8, 10]
}

// Labeled arguments
pub fn buat_user(nama nama: String, umur umur: Int) -> String {
  nama <> " (umur: " <> int.to_string(umur) <> ")"
}

// Usage: buat_user(nama: "Andi", umur: 25)

// Multiple return values via tuples
pub fn min_max(arr: List(Int)) -> #(Int, Int) {
  let assert Ok(min) = list.first(arr)
  let assert Ok(max) = list.last(arr)
  #(min, max)
}

// List operations
pub fn hitung_total(arr: List(Int)) -> Int {
  list.fold(arr, 0, fn(acc, x) { acc + x })
}

pub fn kuadrat_semua(arr: List(Int)) -> List(Int) {
  list.map(arr, fn(x) { x * x })
}

pub fn filter_genap(arr: List(Int)) -> List(Int) {
  list.filter(arr, fn(x) { x % 2 == 0 })
}

// Using list comprehensions via pipeline
pub fn proses_data() {
  let data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  data
  |> list.filter(fn(x) { x % 2 == 0 })
  |> list.map(fn(x) { x * x })
  |> list.fold(0, fn(acc, x) { acc + x })
  // 4 + 16 + 36 + 64 + 100 = 220
}

// Closures
pub fn buat_counter() -> fn() -> Int {
  // Gleam tidak punya mutable state, jadi gunakan
  // process state untuk counter mutable
  let count = 0
  fn() { count }
}

pub fn curry_example() {
  let add = fn(a: Int) { fn(b: Int) { a + b } }
  let add_5 = add(5)
  add_5(3) // 8
}

4. Type System

Type system Gleam adalah Hindley-Milner, yang berarti compiler bisa menurunkan tipe secara otomatis tanpa anotasi. Gleam juga mendukung custom types (algebraic data types) yang sangat powerful.

src/types.gleam
import gleam/option.{type Option, None, Some}
import gleam/result

// Custom types (algebraic data types)
pub type Warna {
  Merah
  Hijau
  Biru
  Kuning
}

// Custom type dengan data
pub type Bentuk {
  Lingkaran(radius: Float)
  Persegi(sisi: Float)
  PersegiPanjang(panjang: Float, lebar: Float)
  Segitiga(alas: Float, tinggi: Float)
}

pub fn luas(b: Bentuk) -> Float {
  case b {
    Lingkaran(r) -> 3.14159 *. r *. r
    Persegi(s) -> s *. s
    PersegiPanjang(p, l) -> p *. l
    Segitiga(a, t) -> 0.5 *. a *. t
  }
}

// Generic types
pub type Box(a) {
  Box(value: a)
}

pub type Pair(a, b) {
  Pair(first: a, second: b)
}

// Recursive types
pub type LinkedList(a) {
  Nil
  Cons(head: a, tail: LinkedList(a))
}

pub fn list_length(lst: LinkedList(a)) -> Int {
  case lst {
    Nil -> 0
    Cons(_, tail) -> 1 + list_length(tail)
  }
}

// Option type (built-in)
pub fn find_user(id: Int) -> Option(String) {
  case id {
    1 -> Some("Andi")
    2 -> Some("Budi")
    _ -> None
  }
}

// Result type (built-in)
pub type MyError {
  NotFound
  InvalidInput(String)
  PermissionDenied
}

pub fn parse_age(input: String) -> Result(Int, MyError) {
  case int.parse(input) {
    Ok(age) if age >= 0 && age <= 150 -> Ok(age)
    Ok(_) -> Error(InvalidInput("Umur harus 0-150"))
    Error(_) -> Error(InvalidInput("Bukan angka"))
  }
}

// Using Result
pub fn proses() {
  let result = parse_age("25")
  case result {
    Ok(age) -> io.println("Umur: " <> int.to_string(age))
    Error(msg) -> io.debug(msg)
  }
}

// Type alias
pub type UserId =
  Int

pub type Email =
  String

pub type User {
  User(id: UserId, name: String, email: Email)
}

// Opaque types (hanya bisa dibuat oleh module yang mendefinisikan)
// pub opaque type Secret {
//   Secret(value: String)
// }

// Type with phantom parameter for type safety
pub type Meter {
  Meter
}

pub type Kilometer {
  Kilometer
}

pub type Distance(unit) {
  Distance(value: Float)
}

pub fn meters(n: Float) -> Distance(Meter) {
  Distance(value: n)
}

pub fn kilometers(n: Float) -> Distance(Kilometer) {
  Distance(value: n)
}

5. Pattern Matching

src/patterns.gleam
import gleam/io
import gleam/list
import gleam/string

// Basic pattern matching
pub fn describe(n: Int) -> String {
  case n {
    0 -> "nol"
    1 -> "satu"
    2 -> "dua"
    x if x > 0 -> "positif"
    _ -> "negatif"
  }
}

// Matching on tuples
pub fn classify_point(p: #(Int, Int)) -> String {
  case p {
    #(0, 0) -> "origin"
    #(x, 0) -> "pada sumbu x: " <> int.to_string(x)
    #(0, y) -> "pada sumbu y: " <> int.to_string(y)
    #(x, y) if x > 0 && y > 0 -> "kuadran I"
    #(x, y) if x < 0 && y > 0 -> "kuadran II"
    #(x, y) if x < 0 && y < 0 -> "kuadran III"
    _ -> "kuadran IV"
  }
}

// Matching on custom types
pub type HttpResponse {
  Ok(data: String)
  NotFound
  ServerError(code: Int, message: String)
  Redirect(url: String)
}

pub fn handle_response(resp: HttpResponse) -> String {
  case resp {
    Ok(data) -> "Berhasil: " <> data
    NotFound -> "404: Tidak ditemukan"
    ServerError(code, msg) ->
      "Error " <> int.to_string(code) <> ": " <> msg
    Redirect(url) -> "Redirect ke: " <> url
  }
}

// Let assertions
pub fn assert_ok() {
  let assert Ok(value) = int.parse("42")
  io.debug(value) // 42
  // Jika parsing gagal, program crash!
}

// Nested pattern matching
pub type Shape {
  Simple(kind: String)
  Composite(shapes: List(Shape))
}

pub fn count_shapes(shape: Shape) -> Int {
  case shape {
    Simple(_) -> 1
    Composite(shapes) ->
      list.fold(shapes, 0, fn(acc, s) { acc + count_shapes(s) })
  }
}

// Pattern matching dengan guards
pub fn classify_grade(score: Int) -> String {
  case score {
    s if s >= 90 -> "A"
    s if s >= 80 -> "B"
    s if s >= 70 -> "C"
    s if s >= 60 -> "D"
    _ -> "F"
  }
}

// Matching list patterns
pub fn describe_list(lst: List(Int)) -> String {
  case lst {
    [] -> "kosong"
    [x] -> "satu elemen: " <> int.to_string(x)
    [x, y] -> "dua elemen: " <> int.to_string(x) <> ", " <> int.to_string(y)
    [first, ..] -> "banyak elemen, mulai dari " <> int.to_string(first)
  }
}

6. Modules dan Imports

src/my_module.gleam
// src/my_module.gleam
// Module-level constants (tidak bisa diakses dari luar)
const default_timeout = 30

// Public functions (bisa diakses dari luar)
pub fn greet(nama: String) -> String {
  "Halo, " <> nama <> "!"
}

pub fn add(a: Int, b: Int) -> Int {
  a + b
}

// Private functions (tidak bisa diakses dari luar)
fn helper(x: Int) -> Int {
  x * 2
}

// Public type
pub type Config {
  Config(host: String, port: Int, debug: Bool)
}

pub fn default_config() -> Config {
  Config(host: "localhost", port: 8080, debug: False)
}

// src/app.gleam
// Importing modules
import gleam/io
import gleam/list
import gleam/string
import gleam/int
import gleam/option.{type Option, None, Some}
import gleam/result
import gleam/dynamic.{type Dynamic}
import gleam/http.{Get, Post}
import gleam/http/request
import gleam/json
import my_module.{type Config, Config}

pub fn main() {
  // Using imported functions
  io.println(my_module.greet("World"))

  let config = my_module.default_config()
  io.println("Host: " <> config.host)

  // JSON encoding
  let json_string =
    json.object([
      #("name", json.string("Andi")),
      #("age", json.int(25)),
      #("active", json.bool(True)),
    ])
    |> json.to_string

  io.println(json_string)
}

7. OTP dan Concurrency

Karena Gleam berjalan di BEAM, Anda mendapatkan semua keunggulan OTP: lightweight processes, supervision trees, GenServer, dan fault tolerance.

src/otp_example.gleam
import gleam/erlang/process.{type Subject, type Pid}
import gleam/io
import gleam/otp/actor
import gleam/otp/supervisor

// Actor (GenServer-like)
type CounterMessage {
  Increment
  Decrement
  Get(reply: Subject(Int))
}

fn counter_handler(
  message: CounterMessage,
  count: Int,
) -> actor.Next(CounterMessage, Int) {
  case message {
    Increment -> actor.continue(count + 1)
    Decrement -> actor.continue(count - 1)
    Get(reply) -> {
      process.send(reply, count)
      actor.continue(count)
    }
  }
}

pub fn main() {
  // Start actor
  let assert Ok(counter) =
    actor.start(0, counter_handler)

  // Send messages
  process.send(counter, Increment)
  process.send(counter, Increment)
  process.send(counter, Decrement)

  // Get current value
  let value = process.call(counter, Get, 1000)
  io.println("Counter: " <> int.to_string(value)) // 2

  // Spawn process
  let pid = process.spawn(fn() {
    io.println("Hello from spawned process!")
  })

  // Send to specific process
  process.send(pid, "Message")

  // Process.sleep
  process.sleep(1000)

  // Selective receive
  let subject = process.new_subject()
  process.send(subject, "Hello")
  let assert Ok(msg) = process.receive(subject, 1000)
  io.println(msg)
}

// Timer
fn periodic_task() {
  process.send_every(5000, fn() {
    io.println("Tick!")
  })
}

8. JavaScript Target

Gleam bisa compile ke JavaScript, memungkinkan Anda menggunakan Gleam untuk frontend development.

Bash
# Build untuk JavaScript
gleam build --target javascript

# Dengan Lustre (web framework)
gleam add lustre

# Build dengan esbuild
gleam add gleam_esbuild
gleam run -m gleam_esbuild

# Output: build/dev/javascript/
src/frontend.gleam
import lustre
import lustre/element.{type Element}
import lustre/element/html
import lustre/event

// Model
type Model {
  Model(count: Int, text: String)
}

// Messages
type Msg {
  Increment
  Decrement
  UpdateText(String)
}

// Init
fn init(_flags) -> Model {
  Model(count: 0, text: "")
}

// Update
fn update(model: Model, msg: Msg) -> Model {
  case msg {
    Increment -> Model(..model, count: model.count + 1)
    Decrement -> Model(..model, count: model.count - 1)
    UpdateText(t) -> Model(..model, text: t)
  }
}

// View
fn view(model: Model) -> Element(Msg) {
  html.div([], [
    html.h1([], [element.text("Gleam Counter")]),
    html.button([event.on_click(Decrement)], [element.text("-")]),
    html.span([], [element.text(int.to_string(model.count))]),
    html.button([event.on_click(Increment)], [element.text("+")]),
    html.input([
      event.on_input(UpdateText),
      attribute.value(model.text),
    ]),
    html.p([], [element.text("Input: " <> model.text)]),
  ])
}

// Main
pub fn main() {
  let app = lustre.simple(init, update, view)
  let assert Ok(_) = lustre.start(app, "#app", Nil)
  Nil
}

9. FFI (Erlang dan JavaScript)

src/ffi_gleam.gleam
// Erlang FFI - external functions
@external(erlang, "io_lib", "format")
pub fn erlang_format(template: String, args: List(Dynamic)) -> String

@external(erlang, "calendar", "local_time")
pub fn current_time() -> #(List(Int), List(Int))

@external(erlang, "crypto", "strong_rand_bytes")
pub fn random_bytes(n: Int) -> BitArray

@external(erlang, "os", "cmd")
pub fn shell_command(cmd: String) -> String

@external(erlang, "file", "read_file")
pub fn read_file(path: String) -> Result(BitArray, Dynamic)

@external(erlang, "timer", "sleep")
pub fn sleep(ms: Int) -> Nil

// JavaScript FFI
@external(javascript, "Date", "now")
pub fn timestamp() -> Float

@external(javascript, "Math", "random")
pub fn random() -> Float

@external(javascript, "console", "log")
pub fn console_log(value: a) -> Nil

@external(javascript, "fetch", "")
pub fn fetch(url: String) -> Dynamic

// Erlang module untuk Gleam wrapper
// src/erlang_helpers.erl
// -module(erlang_helpers).
// -export([get_env/1, timestamp/0]).
//
// get_env(Key) ->
//     case os:getenv(Key) of
//         false -> {error, nil};
//         Value -> {ok, Value}
//     end.
//
// timestamp() ->
//     erlang:system_time(millisecond).

// Di Gleam:
@external(erlang, "erlang_helpers", "get_env")
pub fn get_env(key: String) -> Result(String, Nil)

@external(erlang, "erlang_helpers", "timestamp")
pub fn system_timestamp() -> Int

// Menggunakan Elixir library
// Bisa langsung memanggil Elixir module
@external(erlang, "Elixir.JSON", "encode!")
pub fn json_encode(term: Dynamic) -> String

pub fn demo() {
  io.println(shell_command("echo Hello from Gleam FFI!"))
  io.println("Random bytes: " <> bit_array.base16_encode(random_bytes(16)))
  io.debug(current_time())
}

🧠 Kuis: Gleam

1. Mengapa Gleam disebut "type-safe BEAM language"?

  • Karena memiliki static type system Hindley-Milner yang sound
  • Karena semua tipe harus ditulis manual
  • Karena tidak mendukung custom types
  • Karena BEAM VM memiliki type checking

2. Apa keunggulan dual target (BEAM + JavaScript) di Gleam?

  • Hanya untuk testing
  • Bisa digunakan untuk backend dan frontend dengan type-safe yang sama
  • Untuk compile lebih cepat
  • Untuk kompatibilitas dengan Python

3. Bagaimana error handling di Gleam?

  • Menggunakan try-catch exceptions
  • Menggunakan null checking
  • Menggunakan Result dan Option types
  • Tidak ada error handling

4. Apa itu actor di Gleam OTP?

  • Process BEAM yang menerima pesan dan mengelola state
  • Thread OS yang berat
  • Database connection pool
  • HTTP client

5. Bagaimana cara memanggil Erlang library dari Gleam?

  • Tidak bisa, Gleam terpisah dari Erlang
  • Menggunakan @external annotation untuk Erlang FFI
  • Menggunakan NIF bindings
  • Menggunakan gRPC

📚 Sumber Belajar Lanjutan