--- filename: learnclojure-ms.clj contributors: - ["Adam Bard", "http://adambard.com/"] translators: - ["Burhanuddin Baharuddin", "https://github.com/burhanloey"] --- Clojure ialah salah satu bahasa pengaturcaraan dalam keluarga Lisp yang dibangunkan untuk Java Virtual Machine. Ia lebih menekankan kepada konsep [functional programming](https://en.wikipedia.org/wiki/Functional_programming) jika dibandingkan dengan Common Lisp, tetapi juga menyediakan kemudahan [STM](https://en.wikipedia.org/wiki/Software_transactional_memory) untuk mengendalikan *state* apabila diperlukan. Gabungan tersebut membolehkan Clojure untuk mengendalikan beberapa proses serentak (*concurrency*) dengan mudah, dan kebiasaannya secara automatik. (Anda perlukan Clojure versi 1.2 ke atas) ```clojure ; Komen bermula dengan koma bertitik (semicolon). ; Clojure ditulis dalam bentuk yang seragam, iaitu ; senarai perkataan di dalam kurungan (parentheses), dipisahkan dengan ruang kosong (whitespace). ; ; Pembaca Clojure akan menganggap bahawa perkataan pertama dalam senarai tersebut ; sebagai `function` atau `macro` untuk digunakan, dan yang selebihnya sebagai arguments. ; Panggilan pertama di dalam fail Clojure mestilah bermula dengan ns, untuk menentukan `namespace` (ns learnclojure) ; Contoh-contoh asas yang lain: ; str akan mewujudkan sebuah string daripada beberapa `argument` (str "Hello" " " "World") ; => "Hello World" ; Operasi matematik pun mudah (+ 1 1) ; => 2 (- 2 1) ; => 1 (* 1 2) ; => 2 (/ 2 1) ; => 2 ; Tanda = boleh digunakan untuk membuat perbandingan yang sama (= 1 1) ; => true (= 2 1) ; => false ; Gunakan not untuk mengubah lojik (not true) ; => false ; Bentuk `nested` berfungsi seperti yang dijangkakan (+ 1 (- 3 2)) ; = 1 + (3 - 2) => 2 ; Type (Jenis Data) ;;;;;;;;;;;;; ; Clojure menggunakan jenis `object` dari Java untuk `boolean`, `string` dan nombor. ; Gunakan `class` untuk memeriksa jenis sesebuah data. (class 1) ; Secara default jenis data untuk `Integer` ialah java.lang.Long (class 1.); Jenis data untuk Float pula ialah java.lang.Double (class ""); `String` sentiasa berada dalam tanda petikan (quotation mark), dan merupakan java.lang.String (class false) ; `Boolean` ialah java.lang.Boolean (class nil); Nilai "null" dipanggil nil ; Jika mahu membuat senarai data secara harfiah, gunakan ' untuk elakkan senarai tersebut ; daripada terus berfungsi '(+ 1 2) ; => (+ 1 2) ; (singkatan untuk (quote (+ 1 2))) ; Senarai data secara harfiah boleh berfungsi menggunakan eval (eval '(+ 1 2)) ; => 3 ; Collection & Sequence (Koleksi & Urutan) ;;;;;;;;;;;;;;;;;;; ; `List` ialah struktur data `linked-list`, manakala `Vector` pula berasaskan `array`. ; `Vector` dan `List` juga merupakan class dari Java! (class [1 2 3]); => clojure.lang.PersistentVector (class '(1 2 3)); => clojure.lang.PersistentList ; Sesebuah list boleh ditulis seperti (1 2 3), tetapi kita perlu meletakkan ' ; untuk mengelakkannya daripada berfungsi. ; Juga, (list 1 2 3) adalah sama dengan '(1 2 3) ; "Collections" hanyalah kumpulan data ; Kedua-dua list dan vector ialah collection: (coll? '(1 2 3)) ; => true (coll? [1 2 3]) ; => true ; "Sequences" (seq) ialah kriteria untuk sesebuah senarai data. ; Hanya list yang dikira sebagai seq. (seq? '(1 2 3)) ; => true (seq? [1 2 3]) ; => false ; Sesebuah seq hanya perlukan satu kemasukan data untuk diakses. ; Jadi, seq yang boleh jadi `lazy` (malas) -- boleh menjadi tidak terkira (infinite): (range 4) ; => (0 1 2 3) (range) ; => (0 1 2 3 4 ...) (tiada penghujung) (take 4 (range)) ; (0 1 2 3) ; Gunakan cons untuk menambah sesuatu di awal sesebuah list atau vector (cons 4 [1 2 3]) ; => (4 1 2 3) (cons 4 '(1 2 3)) ; => (4 1 2 3) ; Conj akan menambah sesuatu ke dalam collection dengan paling berkesan. ; Untuk list, data tersebut dimasukkan di permulaan. Untuk vector, dimasukkan di pengakhiran. (conj [1 2 3] 4) ; => [1 2 3 4] (conj '(1 2 3) 4) ; => (4 1 2 3) ; Gunakan concat untuk menggabungkan list atau vector (concat [1 2] '(3 4)) ; => (1 2 3 4) ; Gunakan filter dan map untuk berinteraksi dengan data di dalam collection (map inc [1 2 3]) ; => (2 3 4) (filter even? [1 2 3]) ; => (2) ; Gunakan reduce untuk dikecilkan (kepada satu nilai) (reduce + [1 2 3 4]) ; = (+ (+ (+ 1 2) 3) 4) ; => 10 ; Reduce boleh diberi nilai permulaan (reduce conj [] '(3 2 1)) ; = (conj (conj (conj [] 3) 2) 1) ; => [3 2 1] ; Function ;;;;;;;;;;;;;;;;;;;;; ; Gunakan fn untuk membuat `function`. Sesebuah function pasti memulangkan semula ; hasil daripada barisan yang terakhir. (fn [] "Hello World") ; => fn ; (Anda perlukan satu lagi kurungan supaya function tersebut dikira) ((fn [] "Hello World")) ; => "Hello World" ; Anda boleh membuat var menggunakan def (def x 1) x ; => 1 ; Tetapkan sebuah function ke dalam var (def hello-world (fn [] "Hello World")) (hello-world) ; => "Hello World" ; Proses di atas boleh diringkaskan menggunakan defn (defn hello-world [] "Hello World") ; Tanda [] merupakan senarai argument untuk function tersebut. (defn hello [name] (str "Hello " name)) (hello "Steve") ; => "Hello Steve" ; Cara ini juga boleh digunakan untuk membuat function dengan lebih ringkas: (def hello2 #(str "Hello " %1)) (hello2 "Fanny") ; => "Hello Fanny" ; Anda juga boleh membuat satu function yang mempunyai beberapa bilangan argument (defn hello3 ([] "Hello World") ([name] (str "Hello " name))) (hello3 "Jake") ; => "Hello Jake" (hello3) ; => "Hello World" ; Function boleh diberi argument ekstra dalam bentuk seq (defn count-args [& args] (str "You passed " (count args) " args: " args)) (count-args 1 2 3) ; => "You passed 3 args: (1 2 3)" ; Anda boleh letakkan sekali argument biasa dan argument ekstra (defn hello-count [name & args] (str "Hello " name ", you passed " (count args) " extra args")) (hello-count "Finn" 1 2 3) ; => "Hello Finn, you passed 3 extra args" ; Map ;;;;;;;;;; ; Hash map dan array map menggunakan `interface` yang sama. Hash map lebih laju untuk diakses ; tetapi tidak mengekalkan urutan. (class {:a 1 :b 2 :c 3}) ; => clojure.lang.PersistentArrayMap (class (hash-map :a 1 :b 2 :c 3)) ; => clojure.lang.PersistentHashMap ; Arraymap akan bertukar menjadi hashmap secara automatik untuk kebanyakan operasi ; apabila mereka menjadi semakin besar, jadi anda tidak perlu bimbang. ; Map boleh menggunakan apa-apa sahaja jenis data sebagai key, tetapi kebiasaannya keyword adalah yang terbaik ; Keyword adalah sama seperti string cuma lebih efisyen (class :a) ; => clojure.lang.Keyword (def stringmap {"a" 1, "b" 2, "c" 3}) stringmap ; => {"a" 1, "b" 2, "c" 3} (def keymap {:a 1, :b 2, :c 3}) keymap ; => {:a 1, :c 3, :b 2} ; Oh, sebelum terlupa, tanda koma di atas hanya dianggap seperti whitespace, tak buat apa-apa. ; Dapatkan nilai daripada map dengan menggunakannya seperti function (stringmap "a") ; => 1 (keymap :a) ; => 1 ; Keyword juga boleh digunakan untuk mendapatkan nilai daripada map tersebut! (:b keymap) ; => 2 ; Jangan cuba teknik di atas menggunakan string, tak jadi. ;("a" stringmap) ; => Exception: java.lang.String cannot be cast to clojure.lang.IFn ; Apabila key yang digunakan tidak wujud, map akan memberi nil (stringmap "d") ; => nil ; Gunakan assoc untuk menambah key yang baru ke dalam hash-map (def newkeymap (assoc keymap :d 4)) newkeymap ; => {:a 1, :b 2, :c 3, :d 4} ; Tetapi ingat, data dalam clojure adalah `immutable` (tidak berubah)! keymap ; => {:a 1, :b 2, :c 3} ; Gunakan dissoc untuk membuang key (dissoc keymap :a :b) ; => {:c 3} ; Set ;;;;;; (class #{1 2 3}) ; => clojure.lang.PersistentHashSet (set [1 2 3 1 2 3 3 2 1 3 2 1]) ; => #{1 2 3} ; Tambah data menggunakan conj (conj #{1 2 3} 4) ; => #{1 2 3 4} ; Buang data menggunakan disj (disj #{1 2 3} 1) ; => #{2 3} ; Periksa kewujudan data dengan menggunakan set tersebut sebagai function: (#{1 2 3} 1) ; => 1 (#{1 2 3} 4) ; => nil ; Ada pelbagai lagi function untuk set di namespace clojure.sets. ; Form yang berguna ;;;;;;;;;;;;;;;;; ; Lojik dalam clojure hanyalah sebuah macro, dan kelihatan seperti ; yang lain (if false "a" "b") ; => "b" (if false "a") ; => nil ; Gunakan let untuk membuat binding sementara (let [a 1 b 2] (> a b)) ; => false ; Kumpulkan beberapa statement sekali menggunakan do (do (print "Hello") "World") ; => "World" (prints "Hello") ; Function sebenarnya ada do secara tersirat (defn print-and-say-hello [name] (print "Saying hello to " name) (str "Hello " name)) (print-and-say-hello "Jeff") ;=> "Hello Jeff" (prints "Saying hello to Jeff") ; Let pun sama (let [name "Urkel"] (print "Saying hello to " name) (str "Hello " name)) ; => "Hello Urkel" (prints "Saying hello to Urkel") ; Gunakan `threading macro` (-> dan ->>) untuk menulis penggubahan data ; dengan lebih jelas. ; Macro "thread-first" (->) memasukkan hasil perkiraan ke setiap form ; yang selepasnya, sebagai argument pertama (item yang kedua) (-> {:a 1 :b 2} (assoc :c 3) ;=> (assoc {:a 1 :b 2} :c 3) (dissoc :b)) ;=> (dissoc (assoc {:a 1 :b 2} :c 3) :b) ; Code di atas boleh ditulis seperti ini: ; (dissoc (assoc {:a 1 :b 2} :c 3) :b) ; dan hasilnya ialah {:a 1 :c 3} ; Yang dua anak panah pula membuat benda yang sama, tetapi memasukkan hasil perkiraan ; setiap baris ke pengakhiran form selepasnya. Cara ini berguna untuk operasi ; yang melibatkan collection: (->> (range 10) (map inc) ;=> (map inc (range 10) (filter odd?) ;=> (filter odd? (map inc (range 10)) (into [])) ;=> (into [] (filter odd? (map inc (range 10))) ; Result: [1 3 5 7 9] ; Jika anda mahu lebih fleksibel untuk meletakkan hasil perkiraan, ; anda boleh menggunakan macro `as->`. Dengan menggunakan macro tersebut, ; anda boleh menentukan nama untuk output dan menggunakannya semula ; ke dalam operasi berangkai: (as-> [1 2 3] input (map inc input);=> You can use last transform's output at the last position (nth input 2) ;=> and at the second position, in the same expression (conj [4 5 6] input [8 9 10])) ;=> or in the middle ! ; Module ;;;;;;;;;;;;;;; ; Gunakan "use" untuk mendapatkan semua function daripada sesebuah module (use 'clojure.set) ; Sekarang kita boleh menggunakan operasi untuk set (intersection #{1 2 3} #{2 3 4}) ; => #{2 3} (difference #{1 2 3} #{2 3 4}) ; => #{1} ; Anda juga boleh memilih sebahagian daripada function untuk diimport (use '[clojure.set :only [intersection]]) ; Gunakan require untuk mengimport sesebuah module (require 'clojure.string) ; Gunakan / untuk menggunakan function daripada module ; Di sini, nama module tersebut ialah clojure.string dan function-nya ialah blank? (clojure.string/blank? "") ; => true ; Anda juga boleh memberi nama yang lebih ringkas untuk module semasa import (require '[clojure.string :as str]) (str/replace "This is a test." #"[a-o]" str/upper-case) ; => "THIs Is A tEst." ; (#"" ialah ungkapan untuk regular expression, regex) ; Anda boleh menggunakan require (dan use, tetapi elakkan) daripada namespace menggunakan :require. ; Anda tidak perlu menulis semula nama module dengan cara ini. (ns test (:require [clojure.string :as str] [clojure.set :as set])) ; Java ;;;;;;;;;;;;;;;;; ; Java mengandungi banyak standard library yang kita boleh manfaatkan, jadi ; anda patut tahu bagaimana untuk menggunakannya. ; Gunakan import untuk load module java (import java.util.Date) ; Anda juga boleh import menggunakan ns. (ns test (:import java.util.Date java.util.Calendar)) ; Gunakan nama class berserta "." di hujungnya untuk membuat object baru (Date.) ; ; Gunakan . untuk menggunakan method. Atau gunakan shortcut seperti ".method" (. (Date.) getTime) ; (.getTime (Date.)) ; sama sahaja. ; Gunakan / untuk menggunakan static method (System/currentTimeMillis) ; (System sentiasa wujud dalam Java) ; Gunakan doto untuk menjadikan proses yang melibatkan class mutable (boleh berubah) lebih mudah (import java.util.Calendar) (doto (Calendar/getInstance) (.set 2000 1 1 0 0 0) .getTime) ; => Sebuah Date. yang ditetapkan kepada 2000-01-01 00:00:00 ; STM ;;;;;;;;;;;;;;;;; ; Software Transactional Memory ialah mekanisme dalam Clojure untuk mengendalikan ; state yang kekal berterusan. Ada beberapa kaedah dalam Clojure yang menggunakan teknik tersebut. ; Atom adalah yang paling mudah. Letakkannya sewaktu meletakkan nilai permulaan. (def my-atom (atom {})) ; Kemas kini sebuah atom menggunakan swap!. ; swap! mengambil satu function dan menggunakannya menggunakan nilai asal atom ; sebagai argument pertama, dan argument selebihnya sebagai argument kedua (swap! my-atom assoc :a 1) ; Tetapkan my-atom kepada hasil perkiraan (assoc {} :a 1) (swap! my-atom assoc :b 2) ; Tetapkan my-atom kepada hasil perkiraan (assoc {:a 1} :b 2) ; Gunakan '@' untuk mendapatkan nilai daripada atom my-atom ;=> Atom<#...> (memberi object atom itu sendiri) @my-atom ; => {:a 1 :b 2} ; Ini adalah contoh untuk mengira menggunakan atom (def counter (atom 0)) (defn inc-counter [] (swap! counter inc)) (inc-counter) (inc-counter) (inc-counter) (inc-counter) (inc-counter) @counter ; => 5 ; Kaedah lain yang menggunakan STM ialah ref dan agent. ; Ref: http://clojure.org/refs ; Agent: http://clojure.org/agents ``` ### Bacaan Lanjut Ini masih belum lengkap, tetapi harap-harap cukup untuk membuatkan anda lebih bersedia. Clojure.org mempunyai banyak artikel: [http://clojure.org/](http://clojure.org/) Clojuredocs.org mempunyai dokumentasi berserta contoh untuk menggunakan kebanyakan function teras: [http://clojuredocs.org/quickref/Clojure%20Core](http://clojuredocs.org/quickref/Clojure%20Core) 4Clojure ialah cara yang baik untuk mengasah skill Clojure dan functional programming: [https://4clojure.oxal.org/](https://4clojure.oxal.org/) Clojure-doc.org (yup, serius) juga mengandungi beberapa artikel sebagai permulaan: [http://clojure-doc.org/](http://clojure-doc.org/)