summaryrefslogtreecommitdiffhomepage
path: root/ms-my/clojure-my.html.markdown
blob: 6ae6b4be2e9968e7bab1476095871b7ec6128c1e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
---
language: clojure
filename: learnclojure-ms.clj
contributors:
    - ["Adam Bard", "http://adambard.com/"]
translators:
    - ["Burhanuddin Baharuddin", "https://github.com/burhanloey"]
lang: ms-my
---

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.) ; <object date>

; Gunakan . untuk menggunakan method. Atau gunakan shortcut seperti ".method"
(. (Date.) getTime) ; <sebuah timestamp>
(.getTime (Date.)) ; sama sahaja.

; Gunakan / untuk menggunakan static method
(System/currentTimeMillis) ; <sebuah timestamp> (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:
[http://www.4clojure.com/](http://www.4clojure.com/)

Clojure-doc.org (yup, serius) juga mengandungi beberapa artikel sebagai permulaan:
[http://clojure-doc.org/](http://clojure-doc.org/)