cv.cm

Pemrograman · Bahasa pemrograman

Tutorial Clojure untuk pemula

6 menit baca

Jawaban singkat

Clojure adalah Lisp dinamis dan fungsional yang berjalan di Java Virtual Machine (dengan ClojureScript untuk JavaScript). Kode ditulis sebagai list dalam tanda kurung, data bersifat immutable secara default, dan program dibangun dengan menggabungkan fungsi-fungsi kecil atas vector, map, dan set. Anda memasang Clojure CLI, memulai REPL dengan clj, dan menjalankan file dengan clojure -M hello.clj.

Buka Penguji regex

Apa itu Clojure dan siapa yang memakainya

Clojure dibuat oleh Rich Hickey dan pertama kali dirilis pada 2007. Clojure adalah dialek Lisp: setiap ekspresi adalah list yang elemen pertamanya berupa fungsi atau operator, misalnya (+ 1 2). Clojure dikompilasi menjadi bytecode JVM, sehingga dapat memanggil library Java apa pun secara langsung. ClojureScript mengompilasi bahasa yang sama ke JavaScript untuk browser dan Node.js.

Clojure dipakai untuk layanan web back-end, data processing pipeline, dan sistem bisnis internal, terutama oleh tim yang menghargai data immutable dan pengembangan interaktif. Developer biasanya bekerja di dalam REPL (read-eval-print loop) yang terhubung ke editor, mengevaluasi satu ekspresi sekali waktu sementara program berjalan.

Memasang Clojure dan menjalankan Hello, world

Pasang Java runtime (JDK 17 atau lebih baru adalah pilihan aman), lalu pasang Clojure CLI tools dari clojure.org (Homebrew di macOS, script instalasi di Linux, atau installer Windows). CLI menyediakan dua perintah: clj (REPL dengan penyuntingan baris) dan clojure (untuk menjalankan program).

;; hello.clj
(println "Hello, world!")
clojure -M hello.clj
# Hello, world!
clj
# user=> (+ 1 2)
# 3

Proyek nyata menyimpan dependensinya dalam file deps.edn dan kodenya di bawah src/, dengan satu namespace per file. Leiningen (lein) adalah build tool yang lebih lama tetapi masih populer, dengan file project.clj.

Nilai, tipe, dan nama

Clojure bertipe dinamis. def mengikat nama global (sebuah var) dan let mengikat nama lokal. Nilai tidak berubah setelah dibuat: tidak ada statement assignment.

TipeContohCatatan
Integer42long 64-bit; 42N adalah BigInt
Floating point3.14double Java
Ratio(/ 1 3)Pecahan eksak 1/3, bukan 0.333
String"hello"String Java, hanya dengan tanda kutip ganda
Karakter\aSatu karakter
Keyword:nameLabel yang cepat dan mengevaluasi dirinya sendiri, sering dipakai sebagai key map
Boolean / niltrue, false, nilHanya false dan nil yang falsy
(def pi-approx 3.14159)
(let [r 2
      area (* pi-approx r r)]
  (println "Area:" area))
;; Area: 12.56636
(str "a" 1 :b)   ;; => "a1:b"

Alur kontrol

Di Clojure segalanya adalah ekspresi yang mengembalikan nilai, termasuk if. if menerima tepat satu form "then" dan satu form "else"; gunakan when untuk beberapa efek samping tanpa cabang else, dan cond untuk beberapa kondisi.

(defn grade [score]
  (cond
    (>= score 90) "A"
    (>= score 80) "B"
    :else         "C"))
(println (grade 85))            ;; B
(when (pos? 5) (println "positive") :done)
(doseq [x [1 2 3]] (println (* x x)))   ;; 1 4 9
(for [x (range 5) :when (odd? x)] (* x 10))   ;; => (10 30)
(loop [i 0 acc 0]
  (if (< i 5) (recur (inc i) (+ acc i)) acc))   ;; => 10

Tidak ada perulangan for dengan penghitung yang diubah-ubah. doseq menjalankan efek samping untuk setiap elemen, for membangun lazy sequence (list comprehension), dan loop/recur menyatakan iterasi tanpa menambah call stack.

Fungsi

Definisikan fungsi bernama dengan defn. Sebuah fungsi dapat punya beberapa arity (jumlah argumen berbeda), dan #(...) adalah bentuk singkat untuk fungsi anonim kecil dengan % sebagai argumennya.

(defn greet
  ([] (greet "world"))
  ([name] (str "Hello, " name "!")))
(greet)          ;; => "Hello, world!"
(greet "Ana")    ;; => "Hello, Ana!"
(defn sum-all [& nums] (reduce + nums))
(sum-all 1 2 3)  ;; => 6
(map #(* % %) [1 2 3])        ;; => (1 4 9)
(filter even? (range 10))     ;; => (0 2 4 6 8)

Fungsi adalah nilai: Anda dapat meneruskannya ke map, filter, dan reduce, mengembalikannya dari fungsi lain, dan menyimpannya di dalam map.

Koleksi: list, vector, map, dan set

Empat koleksi inti Clojure bersifat immutable dan persistent: "mengubah" salah satunya menghasilkan versi baru yang berbagi sebagian besar strukturnya dengan versi lama, sehingga pembaruan murah.

KoleksiLiteralPenggunaan umum
List'(1 2 3)Kode dan data sekuensial yang ditambahkan di depan
Vector[1 2 3]Sequence berindeks, penambahan di akhir
Map{:name "Ana" :age 30}Record key-value
Set#{:red :green}Nilai unik, pengujian keanggotaan
(def user {:name "Ana" :age 30})
(:name user)                   ;; => "Ana"
(assoc user :email "[email protected]")   ;; new map with :email
(update user :age inc)         ;; => {:name "Ana", :age 31}
(conj [1 2] 3)                 ;; => [1 2 3]
(contains? #{:red :green} :red) ;; => true
(get-in {:a {:b 1}} [:a :b])   ;; => 1
user                           ;; unchanged: {:name "Ana", :age 30}

Fitur khas: threading macro, atom, dan macro

Threading macro membuat data pipeline terbaca dari atas ke bawah. -> menyisipkan hasil sebelumnya sebagai argumen pertama form berikutnya; ->> menyisipkannya sebagai argumen terakhir.

(->> (range 1 11)
     (filter odd?)
     (map #(* % %))
     (reduce +))   ;; => 165
(def counter (atom 0))
(swap! counter inc)
(swap! counter + 10)
@counter           ;; => 11
(defmacro unless [test & body]
  `(if ~test nil (do ~@body)))
(unless false (println "runs"))

Atom menyimpan state yang bisa berubah: swap! menerapkan fungsi secara atomik, sehingga aman dipakai dari beberapa thread. Ref dan agent mencakup kasus konkurensi lainnya. Karena kode adalah data (list), macro dapat mentransformasi kode sebelum dikompilasi; when, cond, dan -> sendiri adalah macro.

Penanganan error

Clojure memakai exception JVM dengan try, catch, dan finally. Untuk menyertakan data pada error, lempar ex-info dan baca kembali datanya dengan ex-data.

(defn parse-age [s]
  (let [n (Long/parseLong s)]
    (when (neg? n)
      (throw (ex-info "Age must not be negative" {:value n})))
    n))
(try
  (parse-age "-5")
  (catch clojure.lang.ExceptionInfo e
    (println (ex-message e) (ex-data e)))
  (catch NumberFormatException _
    (println "not a number")))
;; Age must not be negative {:value -5}

Program lengkap kecil: frekuensi kata

(ns wordcount
  (:require [clojure.string :as str]))
(defn word-counts [text]
  (->> (str/split (str/lower-case text) #"\W+")
       (remove str/blank?)
       frequencies
       (sort-by val >)))
(doseq [[word n] (take 2 (word-counts "The cat and the hat and the bat"))]
  (println word n))
;; the 3
;; and 2

Program ini mengubah teks menjadi huruf kecil, memecahnya pada karakter non-kata dengan literal regular expression #"\W+", menghitung setiap kata dengan frequencies (yang mengembalikan map), dan mengurutkan entri berdasarkan jumlah secara menurun.

Langkah selanjutnya

  • Panduan resmi di clojure.org: Getting Started, Learn Clojure, dan panduan deps.edn.
  • Halaman referensi Clojure untuk sintaks reader, special form, dan struktur data.
  • Siapkan editor dengan REPL yang terhubung (misalnya Calva untuk VS Code, CIDER untuk Emacs, atau Cursive untuk IntelliJ).
  • Coba ClojureScript jika Anda ingin menargetkan browser.

FAQ

Apakah Clojure bahasa fungsional?

Ya. Clojure mengutamakan fungsi murni dan data immutable, tetapi bersifat pragmatis: mengizinkan efek samping dan menyediakan state mutable yang terkelola lewat atom, ref, dan agent.

Apakah saya perlu tahu Java untuk belajar Clojure?

Tidak. Anda dapat mempelajari bahasanya tanpa Java, tetapi Java runtime harus terpasang, dan sedikit pengetahuan tentang JVM membantu saat membaca stack trace atau memakai library Java.

Apa perbedaan list dan vector di Clojure?

List '(1 2 3) menambahkan elemen di depan dan terutama dipakai untuk kode; vector [1 2 3] menambahkan di akhir dan mendukung akses cepat berdasarkan indeks. Sebagian besar data dalam program Clojure disimpan dalam vector dan map.

Mengapa Clojure memakai begitu banyak tanda kurung?

Kode Clojure ditulis sebagai struktur data Lisp, sehingga setiap pemanggilan adalah sebuah list. Sintaks yang seragam inilah yang memungkinkan macro, dan editor dengan structural editing membuat tanda kurung mudah dikelola.

Apa perbedaan Clojure dan ClojureScript?

Keduanya bahasa yang sama pada host yang berbeda: Clojure berjalan di JVM, ClojureScript dikompilasi ke JavaScript. Interop dengan host berbeda, tetapi fungsi inti dan struktur datanya sama.

Semua tutorial pemrograman