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
|
---
language: clojure
filename: learnclojure-kr.clj
contributors:
- ["Adam Bard", "http://adambard.com/"]
translators:
- ["netpyoung", "http://netpyoung.github.io/"]
lang: ko-kr
---
Clojure는 Java 가상머신을 위해 개발된 Lisp 계통의 언어입니다
이는 Common Lisp보다 순수 [함수형 프로그래밍](https://en.wikipedia.org/wiki/Functional_programming)을 더욱 강조했으며,
상태를 있는 그대로 다루기 위해 다양한 [STM](https://en.wikipedia.org/wiki/Software_transactional_memory) 을 지원하는 프로그램들을 갖췄습니다.
이를 조합하여, 병행처리(concurrent processing)를 매우 단순하게 처리할 수 있으며,
대게 자동으로 처리될 수 있도록 만들 수 있습니다.
(Clojure 1.2 이상의 버전이 필요로 합니다.)
```clojure
; 주석은 세미콜론(;)으로 시작합니다.
; Clojure는 "폼(forms)"으로 구성되었으며,
; 폼은 괄호로 감싸져있으며, 공백으로 구분된 것들이 나열된 것입니다.
;
; clojure의 reader는 첫번째로 오는 것을
; 함수 혹은 매크로를 호출하는 것, 그리고 나머지를 인자라고 가정합니다.
; namespace를 지정하기 위해, 파일에서 우선적으로 호출해야될 것은 ns입니다.
(ns learnclojure)
; 간단한 예제들:
; str 은 인자로 받은 것들을 하나의 문자열로 만들어줍니다.
(str "Hello" " " "World") ; => "Hello World"
; 직관적인 수학 함수들을 갖고 있습니다.
(+ 1 1) ; => 2
(- 2 1) ; => 1
(* 1 2) ; => 2
(/ 2 1) ; => 2
; = 로 동일성을 판별할 수 있습니다.
(= 1 1) ; => true
(= 2 1) ; => false
; 논리연산을 위한 not 역시 필요합니다.
(not true) ; => false
; 중첩된 폼(forms)은 기대한대로 동작합니다.
(+ 1 (- 3 2)) ; = 1 + (3 - 2) => 2
; 타입
;;;;;;;;;;;;;
; Clojure는 부울(boolean), 문자열, 숫자를 위해 Java의 object 타입을 이용합니다.
; `class` 를 이용하여 이를 확인할 수 있습니다.
(class 1) ; 정수는 기본적으로 java.lang.Long입니다.
(class 1.); 소수는 java.lang.Double입니다.
(class ""); 문자열은 쌍따옴표로 감싸져 있으며, java.lang.String입니다.
(class false) ; 부울값은 java.lang.Boolean입니다.
(class nil); nil은 "null"값입니다.
; 데이터 리스트 자체를 만들고자 한다면,
; '를 이용하여 평가(evaluate)되지 않도록 막아야 합니다.
'(+ 1 2) ; => (+ 1 2)
; (quote (+ 1 2)) 를 줄여서 쓴것
; quote 가 된 리스트를 평가할 수 도 있습니다.
(eval '(+ 1 2)) ; => 3
; 컬렉션(Collections) & 시퀀스(Sequences)
;;;;;;;;;;;;;;;;;;;
; 리스트(List)는 연결된(linked-list) 자료구조이며, 벡터(Vector)는 배열이 뒤로붙는(array-backed) 자료구조입니다.
; 리스트와 벡터 모두 java 클래스입니다!
(class [1 2 3]); => clojure.lang.PersistentVector
(class '(1 2 3)); => clojure.lang.PersistentList
; 간단하게 (1 2 3)로 리스트를 나타낼 수 있지만,
; reader가 함수라고 여기지 못하게 quote(')를 해줘야 합니다.
; 따라서, (list 1 2 3)는 '(1 2 3)와 같습니다.
; "컬렉션"은 단순하게 데이터의 그룹입니다.
; 리스트와 벡터 모두 컬렉션입니다:
(coll? '(1 2 3)) ; => true
(coll? [1 2 3]) ; => true
; "시퀀스" (seq) 는 데이터 리스트를 추상적으로 기술한 것입니다.
; 리스트는 시퀀스입니다.
(seq? '(1 2 3)) ; => true
(seq? [1 2 3]) ; => false
; 시퀀스는 접근하고자 하는 항목만 제공해주면 됩니다.
; 따라서, 시퀀스는 lazy 할 수 있습니다 -- 무한하게 늘어나는 것을 정의할 수 있습니다:
(range 4) ; => (0 1 2 3)
(range) ; => (0 1 2 3 4 ...) (an infinite series)
(take 4 (range)) ; (0 1 2 3)
; cons 를 이용하여 리스트나 벡터의 시작부에 항목을 추가할 수 있습니다.
(cons 4 [1 2 3]) ; => (4 1 2 3)
(cons 4 '(1 2 3)) ; => (4 1 2 3)
; conj 는 컬렉션에 가장 효율적인 방식으로 항목을 추가합니다.
; 리스트는 시작부분에 삽입하고, 벡터는 끝부분에 삽입합니다.
(conj [1 2 3] 4) ; => [1 2 3 4]
(conj '(1 2 3) 4) ; => (4 1 2 3)
; concat 을 이용하여 리스트와 벡터를 서로 합칠 수 있습니다.
(concat [1 2] '(3 4)) ; => (1 2 3 4)
; filter, map 을 이용하여 컬렉션을 다룰 수 있습니다.
(map inc [1 2 3]) ; => (2 3 4)
(filter even? [1 2 3]) ; => (2)
; reduce 를 이용하여 줄여나갈 수 있습니다.
(reduce + [1 2 3 4])
; = (+ (+ (+ 1 2) 3) 4)
; => 10
; reduce 는 초기 값을 인자로 취할 수 도 있습니다.
(reduce conj [] '(3 2 1))
; = (conj (conj (conj [] 3) 2) 1)
; => [3 2 1]
; 함수
;;;;;;;;;;;;;;;;;;;;;
; fn 을 이용하여 함수를 만들 수 있습니다 .
; 함수는 항상 마지막 문장을 반환합니다.
(fn [] "Hello World") ; => fn
; (정의한 것을 호출하기 위해선, 괄호가 더 필요합니다.)
((fn [] "Hello World")) ; => "Hello World"
; def 를 이용하여 var 를 만들 수 있습니다.
(def x 1)
x ; => 1
; var 에 함수를 할당시켜보겠습니다.
(def hello-world (fn [] "Hello World"))
(hello-world) ; => "Hello World"
; defn 을 이용하여 짧게 쓸 수 도 있습니다.
(defn hello-world [] "Hello World")
; [] 는 함수의 인자 목록을 나타냅니다.
(defn hello [name]
(str "Hello " name))
(hello "Steve") ; => "Hello Steve"
; 약자(shorthand)를 써서 함수를 만들 수 도 있습니다:
(def hello2 #(str "Hello " %1))
(hello2 "Fanny") ; => "Hello Fanny"
; 함수가 다양한 인자를 받도록 정의할 수 도 있습니다.
(defn hello3
([] "Hello World")
([name] (str "Hello " name)))
(hello3 "Jake") ; => "Hello Jake"
(hello3) ; => "Hello World"
; 함수는 여러 인자를 시퀀스로 취할 수 있습니다.
(defn count-args [& args]
(str "You passed " (count args) " args: " args))
(count-args 1 2 3) ; => "You passed 3 args: (1 2 3)"
; 개별적으로 받는 것과, 시퀀스로 취하는 것을 같이 쓸 수 도 있습니다.
(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"
; 맵(Maps)
;;;;;;;;;;
; 해쉬맵(hash map)과 배열맵(array map)은 공통된 인터페이스를 공유합니다.
; 해쉬맵은 찾기가 빠르지만, 키의 순서가 유지되지 않습니다.
(class {:a 1 :b 2 :c 3}) ; => clojure.lang.PersistentArrayMap
(class (hash-map :a 1 :b 2 :c 3)) ; => clojure.lang.PersistentHashMap
; 배열맵은 여러 연산을 거쳐 자연스레 해쉬맵이 됩니다.
; 만일 이게 커진다 하더라도, 걱정할 필요가 없습니다.
; 맵은 해쉬가 가능한 타입이라면 어떠한 것이든 키로써 활용이 가능하지만, 보통 키워드를 이용하는 것이 가장 좋습니다.
; 키워드(Keyword)는 문자열과 비슷하지만, 보다 효율적인 면이 있습니다.
(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}
; 여기서, 쉽표가 공백으로 취급되며, 아무 일도 하지 않는다는 것을 주목하시기 바랍니다.
; 맵에서 값을 얻어오기 위해선, 함수로써 맵을 호출해야 합니다.
(stringmap "a") ; => 1
(keymap :a) ; => 1
; 키워드 역시 맵에서 함수를 얻어올 때 사용할 수 있습니다!
(:b keymap) ; => 2
; 하지만, 문자열로는 하면 안됩니다.
;("a" stringmap)
; => Exception: java.lang.String cannot be cast to clojure.lang.IFn
; 없는 값을 얻어오고자 하면, nil이 반환됩니다.
(stringmap "d") ; => nil
; assoc 를 이용하여 해쉬맵에 새로운 키를 추가할 수 있습니다.
(def newkeymap (assoc keymap :d 4))
newkeymap ; => {:a 1, :b 2, :c 3, :d 4}
; 하지만, 변경할 수 없는(immutable) clojure 타입이라는 것을 기억해야 합니다!
keymap ; => {:a 1, :b 2, :c 3}
; dissoc 를 이용하여 키를 제거할 수 있습니다.
(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}
; conj 로 항목을 추가할 수 있습니다.
(conj #{1 2 3} 4) ; => #{1 2 3 4}
; disj 로 제거할 수 도 있습니다.
(disj #{1 2 3} 1) ; => #{2 3}
; 존재하는지 확인할 목적으로, 쎗을 함수로 사용할 수 도 있습니다.
(#{1 2 3} 1) ; => 1
(#{1 2 3} 4) ; => nil
; clojure.sets 네임스페이스(namespace)에는 더 많은 함수들이 있습니다.
; 유용한 폼(forms)
;;;;;;;;;;;;;;;;;
; clojure에선, if 와 매크로(macro)를 가지고,
; 다른 여러 논리 연산들을 만들 수 있습니다.
(if false "a" "b") ; => "b"
(if false "a") ; => nil
; let 을 이용하여 임시적으로 바인딩(binding)을 구축할 수 있습니다.
(let [a 1 b 2]
(> a b)) ; => false
; do 로 문단을 묶을 수 도 있습니다.
(do
(print "Hello")
"World") ; => "World" (prints "Hello")
; 함수는 암시적으로 do 를 가지고 있습니다.
(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 역시 그러합니다.
(let [name "Urkel"]
(print "Saying hello to " name)
(str "Hello " name)) ; => "Hello Urkel" (prints "Saying hello to Urkel")
; 모듈(Modules)
;;;;;;;;;;;;;;;
; "use" 를 이용하여 module에 있는 모든 함수들을 얻어올 수 있습니다.
(use 'clojure.set)
; 이제 쎗(set:집합)연산을 사용 할 수 있습니다.
(intersection #{1 2 3} #{2 3 4}) ; => #{2 3}
(difference #{1 2 3} #{2 3 4}) ; => #{1}
; 함수들 중에 일 부분만을 가져올 수 도 있습니다.
(use '[clojure.set :only [intersection]])
; require 를 이용하여 모듈을 import할 수 있습니다.
(require 'clojure.string)
; / 를 이용하여 모듈에 있는 함수를 호출 할 수 있습니다.
; 여기, clojure.string 라는 모듈에, blank? 라는 함수가 있습니다.
(clojure.string/blank? "") ; => true
; import시, 모듈에 짧은 이름을 붙여줄 수 있습니다.
(require '[clojure.string :as str])
(str/replace "This is a test." #"[a-o]" str/upper-case) ; => "THIs Is A tEst."
; (#"" denotes a regular expression literal)
; :require 를 이용하여, 네임스페이스에서 require 를 사용할 수 있습니다.
; 아레와 같은 방법을 이용하면, 모듈을 quote하지 않아도 됩니다.
(ns test
(:require
[clojure.string :as str]
[clojure.set :as set]))
; Java
;;;;;;;;;;;;;;;;;
; Java는 유용한 많은 표준 라이브러리를 가지고 있으며,
; 이를 어떻게 활용할 수 있는지 알아보도록 하겠습니다.
; import 로 java 모듈을 불러올 수 있습니다.
(import java.util.Date)
; ns 와 함께 import 를 할 수 도 있습니다.
(ns test
(:import java.util.Date
java.util.Calendar))
; 새로운 인스턴스를 만들기 위해선, 클래스 이름 끝에 "."을 찍습니다.
(Date.) ; <a date object>
; . 을 이용하여 메소드를 호출할 수 있습니다.
; 아니면, 줄여서 ".메소드"로도 호출 할 수 있습니다.
(. (Date.) getTime) ; <a timestamp>
(.getTime (Date.)) ; exactly the same thing.
; / 를 이용하여 정적메소드를 호출 할 수 있습니다.
(System/currentTimeMillis) ; <a timestamp> (system is always present)
; doto 를 이용하여 상태가 변하는(mutable) 클래스들을 좀 더 편하게(tolerable) 다룰 수 있습니다.
(import java.util.Calendar)
(doto (Calendar/getInstance)
(.set 2000 1 1 0 0 0)
.getTime) ; => A Date. set to 2000-01-01 00:00:00
; STM
;;;;;;;;;;;;;;;;;
; Software Transactional Memory 는 clojure가 영구적인(persistent) 상태를 다루는 방식입니다.
; clojure가 이용하는 몇몇 자료형(construct)이 있습니다.
; 가장 단순한 것은 atom 입니다. 초기 값을 넣어보도록 하겠습니다.
(def my-atom (atom {}))
; swap! 으로 atom을 갱신(update)할 수 있습니다!
; swap! 은 함수를 인자로 받아, 그 함수에 대해 현재 atom에 들어있는 값을 첫번째 인자로,
; 나머지를 두번째 인자로 하여 호출합니다.
(swap! my-atom assoc :a 1) ; Sets my-atom to the result of (assoc {} :a 1)
(swap! my-atom assoc :b 2) ; Sets my-atom to the result of (assoc {:a 1} :b 2)
; '@' 를 이용하여 atom을 역참조(dereference)하여 값을 얻을 수 있습니다.
my-atom ;=> Atom<#...> (atom 객체가 반환됩니다.)
@my-atom ; => {:a 1 :b 2}
; 여기 atom을 이용한 단순한 카운터가 있습니다.
(def counter (atom 0))
(defn inc-counter []
(swap! counter inc))
(inc-counter)
(inc-counter)
(inc-counter)
(inc-counter)
(inc-counter)
@counter ; => 5
; STM을 구성하는 다른 것들에는 ref 와 agent 가 있습니다.
; Refs: http://clojure.org/refs
; Agents: http://clojure.org/agents
```
### 읽어볼거리
부족한 것이 많았지만, 다행히도 채울 수 있는 것들이 많이 있습니다.
Clojure.org에 많은 문서들이 보관되어 있습니다:
[http://clojure.org/](http://clojure.org/)
Clojuredocs.org는 core 함수들에 대해 다양한 예제와 문서를 보유하고 있습니다:
[http://clojuredocs.org/quickref/Clojure%20Core](http://clojuredocs.org/quickref/Clojure%20Core)
4Clojure는 clojure/FP 스킬을 올릴 수 있는 좋은 길입니다:
[http://www.4clojure.com/](http://www.4clojure.com/)
Clojure-doc.org는 많고 많은 문서들을 보유하고 있습니다:
[http://clojure-doc.org/](http://clojure-doc.org/)
|