= Clojure =
https://clojure.org/guides/getting_started
https://clojure.org/guides/learn/syntax
{{tag> clojure functional_programming jvm language }}
= 환경 구축 =
= Console 기반 =
https://clojure.org/guides/getting_started
== Leiningen ==
* https://leiningen.org/
* Mac : http://www.hildeberto.com/2015/07/installing-leiningen-on-mac-os.html
* 사전에 [[https://brew.sh/index_ko.html|brew]] 설치 필요
* Linux
#java8
sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java8-installer
#leiningen
wget https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein
chmod a+x lein
sudo mv lein /usr/local/bin
Build 및 의존성 관리 도구. npm과 같은 역할. (Windows의 경우 자동 Installer도 제공.)
# Project 생성
lein new app project-name
# Project 실행
## 해당 Project Directory 진입 후에
lein run
# 프로젝트 배포 (실행가능한 jar 파일 만들기)
## 해당 Project Directory 진입 후에
lein uberjar
java -jar ..\target\uberjar\~~-standalone.jar
== Reads Evaluates Prints Loops ==
코드 테스트 도구
lein repl
== Luminus ==
* http://www.luminusweb.net/
MVC Framework
#Leiningen으로 쉽게 프로젝트 구성!
lein new luminus my-app
cd my-app
lein run
$(document).ready(function() {
$('select').material_select();
});
(.ready (jquery (js* "document"))
#(.material_select (jquery (js* "'select'")))))
= IntelliJ =
https://cursive-ide.com/
http://manmyung.github.io/2015/03/17/post.html
http://archive.is/CseCi
* 하단 오른쪽에 "Strucural Off"를 눌러야 괄호 편집이 쉬워진다.
== 단축키 설정 ==
* **Load file in REPL** 에 Alt+L
* 현재 활성화 된 clj를 repl과 연결시킨다.
* **Send form before caret to REPL** 에 Alt+J
* 커서 위치 바로 전의 함수를 평가한다.
* **Send top form in REPL** 에 Alt+K
* 현재 커서를 포함하고 있는 함수를 평가한다.
* Drag로 선택중인 함수를 평가한다.
= 기초 용어 =
* Form (형식) : 유효한 코드
* Expression (식) : Clojure의 Form
* **__Evaluate (평가) : Clojure의 Form을 계산__**
* var : def/defn으로 생성된 Immutable value
* Lexical Scope : 함수 몸체 안에서만 바인딩이 유효한 범위
* Higher-order function(고차 함수) : 함수를 인자로 취하거나 함수를 반환하는 함수
* first-class function(1급 함수) : 고차 함수를 1급 객체(언어 내부에서 값으로 표현되고 전달될 수 있는 자료형)[(https://namu.wiki/w/%EA%B3%A0%EC%B0%A8%20%ED%95%A8%EC%88%98)]으로 취급하는 표현
= 문법 =
== 관용적 표현 ==
- 단어간의 구분은 "-"
- 함수 뒤에 붙는 기호의 의미
- ? : 함수의 반환값이 Boolean
- ! : 함수가 상태를 바꾼다는 의미
- !! : 대기 호출
== 자료 구조 ==
https://clojure.org/reference/data_structures
* 모든 자료구조는 Immutable!! (ex. indexing을 이용하여 값을 변경 불가)
=== Symbol ===
예약어와 비슷한 개념. Java나 Clojure에서 제공하는 패키지/클래스/메서드/함수명들.
=== Keyword ===
**:**(콜론)으로 표기. Keyword를 평가하면 그 자신을 반환하는 특징때문에 struct(구조체)나 Map의 Key로 사용하기에 좋다.
(def person {:name "Luke" :age 33})
person
=> {:name "Luke", :age 33}
=== Collections ===
* Immutable!
* 원소를 수정하는 함수를 사용하면 새로운 Collections 반환
==== Lists ====
* '()
* Linked List
* 맨 앞부터, 순차적으로 접근할 때 사용하기 적합.
* 임의 index 참조 불가
* get이 아닌 nth 함수로 원소를 가져온다. 이 때, 순차탐색을 하기 때문에 get보다 느리다!
* conj를 사용하면 **"앞"**에 원소가 추가된다!
;생성. 이 때, 어떤 요소든 추가 가능.
'(1 "a" :key +)
=> (1 "a" :key +)
(list 1 2 3 4)
=> (1 2 3 4)
;생성 및 원소 추가
(cons 1 '())
=> (1)
(first '(1 "a" :key +))
=> 1
;첫 번째 요소 제외 나머지 요소 평가
(rest '(1 "a" :key +))
=> ("a" :key +)
(last '(1 "a" :key +))
=> +
(nth '(1 2 3 4) 3)
=> 4
(conj '(1 2 3 4) 999)
=> (999 1 2 3 4)
===== Queue =====
(conj '(1 2 3) 4) ;; (4 1 2 3)
(pop '(1 2 3 4)) ;; (2 3 4)
(peek '(1 2 3 4)) ;; 1
==== Vectors ====
* []
* Array/Array List와 비슷한, 0-indexed collection.
* 임의 index 접근 가능.
; 생성
[1 "a" :key]
=> [1 "a" :key]
(cons 1 [])
=> (1)
(vector 1.0 1 "hello")
=> [1.0 1 "hello"]
(get [3 2 1] 0)
=> 3
(get ["hello" {:key "value"} 1.0] 1)
=> {:key "value"}
;원소 추가
(conj [1 2 3] 4)
=> [1 2 3 4]
===== Stack =====
(conj [1 2 3] 4) ;; [1 2 3 4]
(pop [1 2 3 4]) ;; [1 2 3]
(peek [1 2 3 4]) ;; 4
==== Map ====
* {}
* hash-map, Ordered Map 두 가지.
{:name "Luke" :age 30}
(hash-map :name "Luke" :age 30)
(get {:a 0 :b {:c "test"}} :b)
=> {:c "test"}
(get-in {:a 0 :b {:c "test"}} [:b :c])
=> "test"
; key가 없는 경우 default 반환. 만약 default 없으면 nil.
; 아래 식은 모두 같다.
(get {:a 0 :b 1} :c "This is default!")
({:a 0 :b 1} :c "This is default!")
(:c {:a 0 :b 1} "This is default!")
=> "This is default!"
;key만 반환
(keys {:key1 111 :key2 "hello"})
=> (:key1 :key2)
;값만 반환
(vals {:key1 111 :key2 "hello"})
=> (111 "hello")
;추가/수정
(assoc {:key1 111 :key2 "hello"} :key3 12.3)
=> {:key1 111, :key2 "hello", :key3 12.3}
(assoc {:key1 111 :key2 "hello"} :key2 "world")
=> {:key1 111, :key2 "world"}
;삭제
(dissoc {:key1 111 :key2 "hello"} :key2)
=> {:key1 111}
;병합
(merge {:key1 111 :key2 "hello"}
{:key1 777 :keyX 123456})
=> {:key1 777, :key2 "hello", :keyX 123456}
==== Sets ====
* #{}
;생성
#{1 2 3}
(hash-set 1 1 2 2)
=> #{1 2}
(set [1 1 2 2])
=> #{1 2}
;추가
(conj #{:a :b} :b)
=> #{:b :a}
;제거
(disj #{:a :b} :b)
=> #{:a}
;원소 포함 여부
(contains? #{:a :b} 1)
=> false
;원소 가져오기
;아래 식은 모두 같다
(get #{:a :b} :a)
(#{:a :b} :a)
(:a #{:a :b})
=> :a
== 흐름 제어 ==
* nil, false는 false. 그외 모든 값은 true.
* not
(not (= :hello :hello))
;축약 표현
(not= :hello :hello)
=> false
=== 논리식 ===
==== and/or ====
===== and =====
* and 조건에 만족하면(모두 참)? 마지막값 반환
* and 조건에 만족하지 못하면? 처음 나온 False 값 반환
;조건 만족
(and "hello" 111 :aaa)
=> :aaa
;조건 불만족
(and "hello" nil 123)
=> nil
===== or =====
* or 조건에 만족하면(하나라도 참)? 처음 나온 True 값 반환
* or 조건에 만족하지 못하면? 마지막 값 반환
;조건 만족
(or false "hello" 111)
=> "hello"
;조건 불만족
(or false nil)
=> nil
(or false)
=> false
==== Collections에 사용되는 논리 검사 ====
;;Collections가 비어있는 지 확인
(empty? [])
=> true
;(not (empty? x))보다 관용적.
(seq [])
=> nil
;;모든 요소에 대한 조건 검사
;every : 모든 요소에 대해 검사 결과가 True?
(every? even? [2 4 6])
=> true
(every? (fn [x] (= x :apple)) [:apple :banana])
(every? #(= % :apple) [:apple :banana])
=> false
;not-any : 모든 요소에 대해 검사 결과가 False?
(not-any? #(= % :apple) [:banana :kiwi])
=> true
;some : 요소 중 일부라도 True?
;진위 함수가 평가한 값이 반환되며, 이 때 진위 함수 값이 여러개이면 첫 번째로 True인 값이 반환된다.
;진리 함수가 논리적으로 false이면 nil(논리적 false).
(some #{0} [1 2 3 4 5])
=> nil
(some #{3 5} [1 2 3 4 5])
=> 3
(some #{nil} [1 2 3 4 5])
=> nil
(some #{false} [1 2 3 4 5])
=> nil
=== 제어문 ===
==== if ====
(if true "TRUE!" "FALSE!")
=>"TRUE!"
(if false "TRUE!")
=> nil
;false에 해당하는 값이 존재하지 않으므로
===== if-let =====
(let [result (> 1 3)]
(if result "TRUE" "FALSE"))
(if-let [result (> 1 3)] "TRUE" "FALSE")
==== do ====
if문에서 **여러** Form을 괄호로 묶어 실행할 때 사용.
(if true
(do (println "TRUE!")
"TRUE return value!")
(do (println "FALSE!")
"FALSE reutrn value!"))
;결과
TRUE!
=> "TRUE return value!"
==== when ====
True이면 본문(함수 내용)을 평가하고, **False이면 nil 반환**
* 언제 쓰는가?
* if처럼 참/거짓 두 경우 모두 처리할 필요 없을 때
* 검사가 참일 때만 처리할 때
(when true (println "TRUE!")
"TRUE return value")
;결과
TRUE!
=> "TRUE return value"
(defn test-when [arg]
(when arg "return value"))
(test-when 0)
(#(when % "return value") 0)
=> "return value"
(test-when false)
=> nil
===== when-let =====
(let [result (> 3 1)]
(when result "TRUE"))
=> "TRUE"
(when-let [result (> 3 1)] "TRUE")
=> "TRUE"
==== cond ====
* switch문과 비슷.
* 조건식을 여러 개 쓰고 싶을 때 사용.
* 단, 나열 순서가 중요하다. **맨 처음 만족하는 조건 평가하고 종료됨**.
* default절은 :else ...이지만 나열 순서상 맨 마지막에 위치하면서 저 키워드 자체가 "참"으로 평가되기 때문에 "nil, false" 제외한 어떤 값이 와도 상관없다. 그러나 관용적으로나 가독성으로 :else가 낫다.
(let [fruit "apple"]
(cond
(= fruit "banana") "yellow"
(= fruit "kiwi") "brown"
(= fruit "melon") "green"
:else "nothing"))
=> "nothing"
;아래 조건식이 모두 만족하지만 첫 번째 조건 평가하고 종료.
(let [x 10]
(cond
(> x 1) "first"
(> x 2) "second"
(> x 3) "third"))
=> "first"
;default절의 원리. 만약, 기술안하면 nil.
(let [x 10]
(cond
(> x 111) "first"
(> x 222) "second"
(> x 333) "third"
0 "DEFAULT!"))
==== condp ====
* 하위에 서술된 식이 한 조건만 검사하는 경우
(defn test-condp [num]
(condp > num
10 "A"
20 "B"
30 "C"
"X"))
(test-condp 11)
;=> "B"
==== case ====
* switch문과 같은 역할.
* 검사할 심볼이 같고, Equal 조건만 검사할 경우 사용
* 단, True가 없는 경우 Exception를 던진다. (cond는 nil을 반환)
* 맨 마지막에 식 하나를 기술하면 Exception을 던지지 않고, 그 식을 반환.
(let [fruit "apple"]
(case fruit
"banana" "yellow"
"kiwi" "brown"
"melon" "green"
"nothing"))
=> "nothing"
== Binding ==
def(전역) vs. let(지역)
=== def ===
* **def** SYMBOL VALUE
* Global binding
* Immutable global value. (Java의 static final과 비슷한 개념)
* 전역적으로 사용할 수 있기에 "/"를 이용하여 namespace를 지정할 수 있다. 그리고 그 namespace로 참조 가능.
* def company/employee "luke"
=== let ===
* (**let** [SYMBOL VALUE ...] SYMBOLS)
* Local binding
* Lexical Scope. let 안에서 일어나는 일은 let 안에서만 유효.
* 함수 내부적으로 공통된 계산 값을 이용할 때 사용.
(let [sym1 val1
sym2 val2
sym3 val3
... ]
)
;symN은 valN이 Binding 된다.
;은 let의 결과인 symN을 재이용할 수 있다. (Lexical Scope)
;마지막에 기술된 는 Return value.
;let에서 계산된 결과 재활용
(defn square-corners [bottom left size]
(let [top (+ bottom size)
right (+ left size)]
[[bottom left] [top left] [top right] [bottom right]]))
(square-corners 111 222 333)
=> [[111 222] [444 222] [444 555] [111 555]]
(def my-vector ["A" "B" "C" "D"])
; 1. sym1에 my-vector의 첫 번째 원소와 binding
; 2. elements에 my-vector의 나머지 원소들과 binding
(let [[sym1 & elements] my-vector]
[sym1 elements])
=> ["A" ("B" "C" "D")]
== 반복문 ==
=== loop & recur ===
* 재귀를 이용한 반복.
* 단순 재귀를 사용하면 Stack overflow 발생!
* loop & recur는 Stack을 소모하지 않음.
* Java의 "label"과 비슷한 문법.
* loop는 recursion point이고, recur를 만나면 다시 loop문으로 돌아가서 반복.
;단순 재귀
;;StackOverFlow
(defn factorial1 [n]
(if (= n 1)
n
(*' n (factorial (dec n)))))
(factorial1 99999)
;loop & recur
(defn factorial2 [n]
(loop [x n, result 1]
(if (= x 1)
result
(recur (dec x) (*' result x)))))
(factorial2 10000)
(defn fibonacci [n]
(loop [x 0, result [1 1]]
(if (= x n)
result
(recur (inc x) (conj result (+' (get result x) (get result (inc x))))))))
(fibonacci 20)
=> [1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711]
(defn multiplicationTable [n]
(loop [x 1]
(println n "x" x "=" (* n x))
(if (= x 9)
:end!
(recur (inc x)))))
(multiplicationTable 3)
;결과
3 x 1 = 3
3 x 2 = 6
3 x 3 = 9
3 x 4 = 12
3 x 5 = 15
3 x 6 = 18
3 x 7 = 21
3 x 8 = 24
3 x 9 = 27
=> :end!
(loop [iteration 0]
(println (str "Iteration : " iteration))
(if (> iteration 3)
(println "End!")
(recur (inc iteration))))
(defn recursive-printer
([]
(recursive-printer 0))
([iteration]
(println (str "Iteration : " iteration))
(if (> iteration 3)
(println "End!")
(recursive-printer (inc iteration)))))
(recursive-printer)
;위 둘다 같은 결과
Iteration : 0
Iteration : 1
Iteration : 2
Iteration : 3
Iteration : 4
End!
=> nil
=== for ===
Sequence 순회. (foreach)
(for [i (range 5)]
i)
=> (0 1 2 3 4)
;name : symbol이나 키워드의 이름을 가져온다.
(for [alphabet [:a :b :c]
number [:1 :2]]
(str (name number) (name alphabet)))
=> ("1a" "2a" "1b" "2b" "1c" "2c")
;:let을 이용한 binding
(for [alphabet [:a :b :c]
number [:1 :2]
:let [n (name number)
a (name alphabet)
all (str n a)]]
all)
=> ("1a" "2a" "1b" "2b" "1c" "2c")
;:when 조건을 만족할 때만 반복
(for [alphabet [:a :b :c]
number [:1 :2]
:let [n (name number)
a (name alphabet)
all (str n a)]
:when (= number :1)]
all)
=> ("1a" "1b" "1c")
== namespace ==
var에 대한 접근을 조직하고 제어하는 방법. java의 package와 같은 개념.
;생성/전환
(ns luke.favfoods)
;현재 namespace 조회
*ns*
;호출
(def fav-food "noodle")
fav-food
first-clojure.core/fav-food
=> "noodle"
== 함수 ==
* defn
; 가장 마지막에 계산한 값을 반환한다!
(defn func-name
"The docstring(comment) is optional."
[arg1 arg2 arg3]
(println arg1 arg2 arg3)
"This text is garbage."
"The return value is optional.")
(func-name "hello" "world" "!!!")
;결과
hello world !
=> "The return value is optional."
=== arity overloading ===
OOP의 Method Overloading과 같은 개념
(defn func-name
"The docstring(comment) is optional."
([] (println "Hello Korea!"))
([arg1 arg2 arg3] (println arg1 arg2 arg3)))
(func-name)
;결과
Hello Korea!
=> nil
=== variable-arity ===
가변 인자
* &
(defn varargs [& args]
(str args))
(varargs "hello" "world" "!")
=> "(\"hello\" \"world\" \"!\")"
=== Destructuring ===
Function의 arguments에서 Collections를 **추려 뽑기**. Collections 일부만 Binding 하기.
* Vectors
(let [[color size] ["blue" "small"]]
(str color " " size))
=> "blue small"
;별칭을 지정하여 전체 출력
(let [[color size :as all] ["blue" "small"]]
(str color " " size " | " all))
=> "blue small | [\"blue\" \"small\"]"
(defn destructuring-vector1 [[arg1 arg2]]
arg2)
(destructuring-vector1 [1 2 3 4 5])
;결과
=> 2
(defn destructuring-vector2 [[arg1 arg2 & args]]
(println (str "arg1 : " arg1))
(println (str "arg2 : " arg2))
(println (str "args : " (clojure.string/join args))))
(destructuring-vector2 ["Hello " "World" "Korea" "China" "Japan"])
;결과
arg1 : Hello
arg2 : World
args : KoreaChinaJapan
=> nil
;키워드 ":as"를 쓰면 Collections 전체 Binding
(defn destructuring-vector3 [[val1 val2 :as nickName]]
(str val1 " " val2 " " (count nickName)))
(destructuring-vector3 [1 2 3 4])
=> "1 2 4"
* Map
(defn destructuring-map1 [{val1 :key1 val2 :key2}]
(println (str "val1 : " val1))
(println (str "val2 : " val2)))
(destructuring-map1 {:key1 28.22 :key2 81.33})
; 결과
val1 : 28.22
val2 : 81.33
=> nil
(defn destructuring-map2 [arg]
(println (:key2 arg)))
(destructuring-map2 {:key1 "Hello" :key2 "World"})
;결과
World
=> nil
;:or를 쓰면 해당 요소가 없을 때를 위한 default 값 설정 가능.
(let [{fruit1 :fruit1, fruit2 :fruit2 :or {fruit2 "banana"}}
{:fruit1 "apple"}]
(str fruit1 " " fruit2))
=> "apple banana"
;:as를 쓰면 Collections 전체 binding 가능.
(let [{fruit :fruit :as all}
{:fruit "apple"}]
(str fruit " | " all))
=> "apple | {:fruit \"apple\"}"
;;:keys를 이용한 간략화(추상화).
;;;보통 심볼 이름과 key 이름을 갖게 하므로 아래와 같은 식 사용 가능.
;;;Destructuring의 가장 흔한 방식
;변경전
(let [{fruit1 :fruit1, fruit2 :fruit2}
{:fruit1 "apple", :fruit2 "banana"}]
(str fruit1 " " fruit2))
;변경후
(let [{:keys [fruit1 fruit2]}
{:fruit1 "apple", :fruit2 "banana"}]
(str fruit1 " " fruit2))
=> "apple banana"
* etc
(let [[x y] [1 2 3]]
[x y])
=> [1 2]
;Underscore(_)의 의미는 "이 Binding은 Skip!"
(let [[_ _ z] [1 2 3]]
z)
=> 3
;반환값이 Underscore인 경우 가장 마지막 Underscore에 대응되는 값 반환.
;관용적이지 않다!
(let [[_ _ z] [1 2 3]]
_)
=> 2
;함수에서 :keys를 이용한 간략화
(defn fruit-kind [{:keys [fruit1 fruit2]}]
(str fruit1 " " fruit2))
(fruit-kind {:fruit1 "apple", :fruit2 "banana"})
=== Anonymous Functions ===
고차 함수로 이용.
* 표기법
* fn [arg] (arg)
* #(%)
* 매개 변수가 여러개인 경우 : #(%1 %2 %3)
((fn [a] (* a 3)) 8)
(#(* % 3) 8)
=> 24
(map (fn [name] (str "Hi, " name))
["Luke", "James"])
(map #(str "Hi, " %)
["Luke", "James"])
=> ("Hi, Luke" "Hi, James")
;매개 변수가 여러개인 경우
(#(* %1 %2 %3) 2 3 4)
=> 24
==== 함께 구현되는 Interface ====
참고로 익명 함수에는 4개의 Java Interface가 Implement 된다.
* java.util.concurrent.Callable
* java.lang.Runnbale
* java.io.Serializable
* java.util.Comparator
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/AFunction.java
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/IFn.java
==== Returing Functions ====
* Closures : 함수 범위 안에 속하는 모든 변수에 접근할 수 있는 영역
(defn adder-builder [arg]
#(+ % arg))
;함수를 인자로 넣는다.
;이 때 adder는 함수를 반환값으로 받으므로 함수가 아닌 값! 그래서 def
(def adder (adder-builder 1))
(adder 10)
;결과
=> 11
=== 분해 및 합성 ===
==== partial ====
* Clojure에서 Currying하는 방법.
* Currying?
* 다중 인수를 갖는 함수를 여러 개의 단일 인수 함수들로 연결(chain)하는 방식으로 변환하는 기법.
* 인수를 부분적으로 적용해서 새로운 함수를 만들어내는 기법.
(defn grow [name direction]
(if (= direction :small)
(str name " is growing smaller")
(str name " is growing bigger")))
((partial grow "hyuk") :small)
=> "hyuk is growing smaller"
==== comp ====
여러 함수들을 합성하여 하나의 새로운 함수 생성.
* 단, argument 개수가 같아야 한다.
* comp의 argument로 받은 함수들은 **오른쪽부터 왼쪽으로** 실행한다.
(defn toggle-grow [direction]
(if (= direction :small) :small :big))
(defn show-direction [direction]
(str "Direction : " direction))
(defn surprise [direction]
((comp show-direction toggle-grow) direction))
(surprise :small)
=> "Direction : :small"
=== Lazy Sequences ===
* 지연(lazy)?
* 결과가 필요하기 전에는 실행되지 않음. 최종 연산을 수행하기 전까지 중간 연산을 수행하여 미룸.
* **실제 실행이 될때**까지 최종 처리를 미루는 작업.
* 무거운 작업(응답이 느린 작업)을 수행할 때, 그 결과를 나누거나 걸러내어 최종 처리 결과를 받을 때 사용한다. (Java의 Stream API, Rx와 같은 개념)
* 처리 결과가 clojure.lang.LazySeq인 경우.
;;가장 안쪽에 있는 함수들이 무한 처리 작업
(take 5 (range))
=> (0 1 2 3 4)
(take 5 (repeat "x"))
=> ("x" "x" "x" "x" "x")
;repeat는 중간처리 결과를 한번만 실행하고 반복
;repeatedly는 인수로 받은 함수를 반복 실행
(take 5 (repeatedly #(rand-int 10)))
=> (0 8 8 0 0)
;cycle은 Collections를 인수로 받는다
(take 5 (rest (cycle ["a" "b" "c"])))
=> ("b" "c" "a" "b" "c")
==== map ====
결과값은 입력 Collections의 각 요소에 특정 기능의 함수가 적용된 새로운 Collections.
* 만약 여러 개의 Collections에 적용하면 **가장 짧은 Collections**에 맞춰 종료.
(def data [:a :b :c :d :e])
(map #(str %) data)
=> (":a" ":b" ":c" ":d" ":e") ;각각의 요소에 대해 문자열로 변경된 Collections.
(defn get-text [alphabet number]
(str alphabet number))
(def data1 ["a" "b" "c" "d" "e"])
(def data2 [1 2 3])
(map get-text data1 data2)
=> ("a1" "b2" "c3")
==== reduce ====
* 점화적으로 연산되기 때문에 유한. 즉, 무한 Sequence를 다룰 수 없다.
* "누적" 연산
;;factorial
;range 1 6 : 1~5
(reduce #(* %1 %2) (range 1 6))
(reduce #(* %1 %2) (map #(inc %) (take 5 (range))))
=> 120
== Concurrency ==
* future : fork와 같은 개념. 다른 Thread 생성.
^ Type ^ Communication ^ Coordination(≒ Thread-safe) ^
| atom | Synchronous | Uncoordnated |
| ref | Synchronous | Coordnated |
| agent | Asynchronous | Uncoordnated |
=== atom ===
독립적인 동기적 변경
* reset! : argument가 값
* swap! : argument가 함수
(def who-atom (atom :before-change))
;값으로 변경
(reset! who-atom :after-change)
@who-atom
=> :after-change
;함수로 변경
(swap! who-atom
#(case %
:before-change "before!"
:else))
@who-atom
=> :else
=== ref & dosync ===
Transaction이 필요한 동기적 변경 (Critical Section에 접근하는 경우)
* alter : Transaction동안 **재시도**. (성능 고려 필요)
* commute : Transaction 동안 **재시도를 하지 않음**. 그리고 **반드시** commute를 적용할 함수는 가환적(commutative)(즉, 더하기처럼 연산 순서에 따라 결과가 바뀌지 않는다)이거나 마지막 Thread의 결과를 사용하는 성질이 있어야 한다.
(def acc1 (ref 100))
(def acc2 (ref 200))
(println @acc1 @acc2)
;100 200
(defn transfer-money [a1 a2 amount]
(dosync
(alter a1 - amount)
(alter a2 + amount)
amount))
(let [n 2]
(future (dotimes [_ n] (transfer-money acc1 acc2 10)))
(future (dotimes [_ n] (transfer-money acc1 acc2 10)))
(future (dotimes [_ n] (transfer-money acc1 acc2 10))))
(println @acc1 @acc2)
;40 260
=== agent ===
독립적인 비동기적 변경. 아래 변경하는 함수들은 **argument로 함수**를 받는다.
* send : cpu 작업이 많은 연산에 적합한 고정 Thread-pool 사용.
* send-off : I/O 작업이 많은 Thread-pool이 대기 상태에 빠지지 않도록 확장 Thread-pool 사용.
(def who-agent (agent :before-change))
(send who-agent
#(case %
:before-change "before!"
:else))
@who-agent
=> "before!"
==== Transaction 처리 ====
(def who-agent (agent :before-change))
(send who-agent #(throw (Exception. "Exception!"))) ;의도적인 Exception
(send who-agent #(case %
:before-change "before!"
:else))
;CompilerException java.lang.RuntimeException: Agent is failed, needs restart, compiling:(...)
@who-agent
;값이 변경되지 않음.
;=> :before-change
;;이 경우 restart-agent를 사용해야 한다.
;;restart-agent 함수로 재시작하기전까지 실패한 상태 유지.
;;;restart-agent는 Error를 제거하고 agent의 초기값을 재설정한다.
(restart-agent who-agent :before-change)
(send who-agent #(case %
:before-change "before!"
:else)) ;성공!
@who-agent
;=> "before!"
== Java interop ==
Java 라이브러리 가져다 쓰기
https://clojure.org/reference/java_interop
* method 호출 : .METHOD
* Instance 생성 : INSTANCE. 혹은 new INSTANCE
* static method 호출 : CLASS/METHOD
=== Polymorphism ===
==== multimethods ====
한개의 함수를 대상으로 다형성 구현. Java에서 Abstract Method를 정의하고, 상속된 Class에서 이를 구현하는 것과 비슷함.
* 다양하게 유연한 방법으로 분기가 가능하지만 protocol에 비해 성능 저하가 따른다.
;① 분기(dispatch)를 결정하는 함수. 마지막 argument가 분기하는 기준.
(defmulti polymorphism1 class)
;② 구현체
(defmethod polymorphism1 String [input]
(type input))
(defmethod polymorphism1 Keyword [input]
(type input))
(defmethod polymorphism1 Long [input]
(type input))
(defmethod polymorphism1 :default [input]
"NONE!")
;;실행
(polymorphism1 213)
;=> java.lang.Long
(polymorphism1 :hello)
;=> clojure.lang.Keyword
;① 분기(dispatch)를 결정하는 함수. 마지막 argument가 분기하는 기준.
;;이 때 인자는 Map
(defmulti greeting #(% :language))
;② 구현체
(defmethod greeting "english" [parmas]
"Hello!")
(defmethod greeting "french" [parmas]
"Bonjour!")
(defmethod greeting :default [params]
(throw (IllegalArgumentException. (str "Unknown language : " (params :language)))))
;실행
(greeting {:id 1, :language "english"})
;=> "Hello!"
(greeting {:id 2, :language "french"})
;=> "Bonjour!"
(greeting {:id 3, :language "korean"})
;CompilerException java.lang.IllegalArgumentException: Unknown language : korean, compiling:(...)
;① 분기(dispatch)를 결정하는 함수 (≒ Interface)
;; 함수로도 argument를 받을 수 있다.
(defmulti polymorphism2 #(if (< % 3) :fn1 :fn2))
;② 구현체
(defmethod polymorphism2 :fn1 [_]
"Function 1")
(defmethod polymorphism2 :fn2 [_]
"Function 2")
;실행
(polymorphism2 5)
;=> "Function 2"
==== Protocol ====
다수의 함수를 대상으로 다형성 구현.
* Type으로 분기
* 자주 사용하지 않는 것이 좋다. 대부분의 상황에서 순수 함수나 Multi Method로 대신 사용할 수 있다.
(defprotocol abstraction (impl [this]))
(extend-protocol abstraction
String
(impl [this]
(type this))
Keyword
(impl [this]
(type this))
Long
(impl [this]
(type this)))
(impl "test")
;=> java.lang.String
(impl :test)
;=> clojure.lang.Keyword
(impl 1)
;=> java.lang.Long
(defprotocol Fly (fly [this] "Method to fly"))
(defrecord Bird [name species] Fly ;상속
(fly [this]
(str (:name this) " flies...")))
(extends? Fly Bird)
;=> true
(def crow (Bird. "Crow" "Black!"))
(fly crow)
;=> "Crow flies..."
==== 새로운 Type 정의 ====
* defrecord vs. deftype
* 구조화된 데이터가 필요하면 defrecord
* 단순히 Type만 필요한 경우 deftype. (Java의 argument 없는 생성자의 객체의 Type이 필요할 때)
===== defrecord =====
(defrecord Person [name age])
(def hyuk (Person. "hyuk" 30))
(.-name hyuk)
=> "hyuk"
(.-age hyuk)
=> 30
(defprotocol Action
(eat [this])
(said [this]))
(defrecord Person [name age]
Action
(eat [this]
(str name " eat somthing..."))
(said [this]
(str "My age is " age)))
(def hyuk (Person. "hyuk" 30))
(.-name hyuk)
;=> "hyuk"
(.-age hyuk)
;=> 30
(eat hyuk)
;=> "hyuk eat somthing..."
(said hyuk)
;=> "My age is 30"
===== deftype =====
(defprotocol Action
(eat [this])
(said [this]))
(deftype Person []
Action
(eat [this]
(str "Somebody eat somthing..."))
(said [this]
(str "My age is secret.")))
(def somebody (Person.))
(eat somebody)
;=> "Somebody eat somthing..."
(said somebody)
;=> "My age is secret."
== Macro ==
=== Threading Macro ===
코드 재배열을 하여 가독성을 높이는 매크로
* https://clojure.org/guides/threading_macros
==== Thread-first Macro ====
https://clojuredocs.org/clojure.core/-%3E
* (-> x forms)
* forms에 x를 연쇄적으로 맨 처음 아이템으로써 뒤에 삽입.
* 함수를 여러번 중첩하는 경우 유용.
(get (.split (.toUpperCase "a b c d") " ") 1)
(-> "a b c d" .toUpperCase (.split " ") (get 1))
=> "B"
(second (next [1 2 3 4 5]))
(-> [1 2 3 4 5] next second)
=> 3
==== Thread-last Macro ====
https://clojuredocs.org/clojure.core/-%3E%3E
* (->> x forms)
* forms에 x를 연쇄적으로 맨 마지막 아이템으로써 앞에 삽입.
* Collections를 마지막 argument로 받는 함수들에 유용
(take 3 (filter odd? (range)))
(->> (range) (filter odd?) (take 3))
=> (1 3 5)
== var 삭제 방법 ==
;현재 namespace 사용
(ns-unmap *ns* 'var)
;직접 "user"라는 namespace 지정
(ns-unmap (find-ns 'user) 'var)
= ClojureScript =
== DOM의 Value를 가져오는 방법 ==
(-> % .-target .-value)