조건문, 재귀문, 구조분해
#클로저
별거없다
empty?로 컬렉션이 비었는지 검사한다seq로 컬렉션이 비지 않았는지 검사한다every?로 요소 전체에 대한 검사가 참인지 검사한다not-any?로 요소 전체에 대한 검사가 거짓인지 검사한다some으로 요소 중 일부에 대한 검사가 참인지 검사한다
흐름 제어(if, when)
If문
(if [조건] (참일때 결과) (거짓일때 결과)) 의 꼴을 가진다.

여기서 let 과 if를 결합시킬수있다. 예를 들어
user=> (let [need-to-grow-small (> 5 3)]
#_=> (if need-to-grow-small "drink bottle" "don't drink bottle"))
"drink bottle"
user=> (if-let [need-to-grow-small (> 5 3)] "drink bottle" "don't drink bottle")
"drink bottle"
let 내부에서 심볼을 바인딩하고 이를 if 문에서 평가를 하게 작성할수있는데, 이를 if-let으로 간략하게 표현할수있다.
그리고 if문을 사용하기 위해서는 clojure에선 반드시 참인경우와 거짓인 경우 둘다 명시해줘야하는데, 참인 경우에 대해서만 작성하고 싶을땐 when을 사용하면된다
user=> (defn drink [need-to-grow-small]
#_=> (when need-to-grow-small "drink-bottle"))
#'user/drink
user=> (drink true)
"drink-bottle"
user=> (drink false)
nil
when 또한 let을 결합하여 when-let으로 간략하게 표현할수 있다
Cond, case
cond는 쉽게 말해 if, else if 와 같고, case는 switch문과 같다
user=> (let [x 5]
#_=> (cond
#_=> (> x 6) "bigger than 6"
#_=> (> x 3) "bigger than 3"
#_=> :else "default")
#_=> )
"bigger than 3"
위와 같이 cond를 사용하여 if, else if, else 문을 구현할 수있다.
clojure에서 else 문은 따로 방법이:else 처럼 존재하는 것이 아니고 그냥 마지막 요소에 논리적으로 참으로 평가되는 값을 쓰고 디폴트값을 써줌으로써 else로 사용할수 있다.
; 2 나오는 경우
user=> (let [number 2]
#_=> (case number
#_=> 1 "number one"
#_=> 2 "number two"
#_=> 3 "number three"
#_=> "default"))
"number two"
; default 나오는 경우
user=> (let [number 5]
#_=> (case number
#_=> 1 "number one"
#_=> 2 "number two"
#_=> 3 "number three"
#_=> "default"))
"default"
위와 같이 case를 사용하여 switch 문을 구현할 수 있다.
따로 default값은 마지막 요소에 참으로 평가되는 값을 써주면 해당 값이 반환된다.
커링 및 함수 합성
clojure에는 커링을 가능케하는
partial함수 그리고 함수 함수를 가능케하는comp함수가 존재한다. partial의 경우는 다중인자를 받는 함수를 단일 함수들로 연결하게 해주고 이는 어떠한 함수에 고정적인 인수가 들어가야하는 경우 partial함수를 통해 새로운 함수를 만들어 줄수 있다.comp의 경우 함수 내부에 다른함수의 평가값이 필요하고 그런게 잦아들게 되면 두 함수를 합성하여 새로운 함수를 만들어 줄수 있다.
구조 분해
별거 없고 let내부에서 혹은 let과 유사한 구조에서 키와 쌍 구조를 띄면 해당 값들은 전부 바인딩 된다
user=> (let [[x y] [1 2]] (str x "-" y))
"1-2"
user=> (let [{x :x y :y} {:x "flower1" :y "flower2"}] (str x "-" y))
"flower1-flower2"
; :or을 사용하여 값을 없을시 기본값 세팅을 할 수있고 :as를 사용하여 전체 값을 변수에 담을 수 있다.
user=> (let [{x :x y :y :or {x "missing1" y "missing2"} :as original}
#_=> {:x "flower1"}] (str x "-" y " " original))
"flower1-missing2 {:x \"flower1\"}"
; keys를 사용한 바인딩 방식을 가장 많이 사용함
user=> (let [{:keys [flower1 flower2]} {:flower1 "blue" :flower2 "red"}]
#_=> (str flower1 " and " flower2))
"blue and red"
; keys를 사용하여 더 직관적이고 편하게 함수에서 바인딩을 형성할 수 있다.
user=> (defn binding-test [{:keys [flower1 flower2]}]
#_=> (str flower1 " - " flower2))
#'user/binding-test
user=> (binding-test {:flower1 "red" :flower2 "blue"})
"red - blue"
title: 지연 평가
**지연 평가는 무한 리스트를 다룰 수 있게 해준다**
지연 시퀀스를 반환하는 함수들이 있다
예를 들어 `range`, `repeat`, `repeatedly`, `cycle`, `rest`, `map` 등등 많은 함수들이 존재하고
해당 함수들을 그대로 사용하면 무한시퀀스가 발생하여 REPL혹은 프로그램이 멈춘다.
따라서 `take` 함수로 지연시퀀스를 안전하게 처리할 수있다.
```clojure
user=> (take 10 (range))
(0 1 2 3 4 5 6 7 8 9)
user=> (take 5 (repeat "rabbit"))
("rabbit" "rabbit" "rabbit" "rabbit" "rabbit")
user=> (take 5 (repeatedly #(rand-int 10)))
(9 5 6 7 6)
user=> (take 3 (cycle ["big" "small"]))
("big" "small" "big")
user=> (take 3 (rest (cycle ["big" "small"])))
("small" "big" "small")
```
재귀문
일단 우리가 아는 방식대로 재귀문을 만들어보자
user=> (defn alice-is [in out]
#_=> (if (empty? in)
#_=> out ; in에 더이상 요소가 남지 않으면 out 반환
#_=> (alice-is (rest in) ;in의 나머지 부분
#_=> (conj out (str "Alice is " (first in))))))
#'user/alice-is
user=> (alice-is ["normal" "too small" "too big"] [])
["Alice is normal" "Alice is too small" "Alice is too big"]
뭔가 꺼림직하지만 재귀문이 만들어졌다. 어느 부분이 꺼림직할까.. 바로 out이 따로 존재한다는점, 따로 반환 되야할 컬렉션을 명시하는 부분이 우리가 알고 있던것과 비교했을때 어색하다..
하지만 이는 함수의 순수성을 지키기 위해 존재하는 것으로 어색할 수 밖에 없다. 이를 완화할 방법은 뒤에 loop를 사용하면서 나온다.
하지만 이 방법의 치명적인 단점은 따로 존재한다.
user=> (defn countdown [n]
#_=> (if (= n 0) n (countdown (- n 1))))
#'user/countdown
; Stack Over Flow 에러
user=> (countdown 100000)
Execution error (StackOverflowError) at user/countdown (REPL:2).
null
치명적인 단점은 바로 위와 같은 방식으로 재귀문을 작성하면 함수가 스택에 계속 적재되어 스택 오버플로우가 발생한다.
따라서 재귀문을 사용할땐 loop와 recur를 사용해야한다. loop 와 recur를 사용하면 매번 호출할 때 하나의 스택만 사용된다.
; loop 없이 recur만 사용하면 함수 자체가 loop로 인식된다
user=> (defn countdown-refine [n]
#_=> (if (= n 0) n
#_=> (recur (- n 1)))
#_=> )
#'user/countdown-refine
user=> (countdown-refine 100000)
0
여기에 이어 아까 in out에서 따로 정의해줘야하는 것이 불편했던것도 loop를 사용하면서 완화할수있다.
loop를 사용하면 함수 중간에 loop시작할 지점을 지정해줄수있고, loop시 마다 사용되고 유지될 변수를 지정할수있다.
; loop 사용하여 재귀에서 사용할 변수 지정 가능 (let 처럼 사용)
user=> (defn alice-is-refine [input]
#_=> (loop [in input out []]
#_=> (if (empty? in) out
#_=> (recur (rest in) (conj out (str "Alice is " (first in)))))))
#'user/alice-is-refine
user=> (alice-is-refine ["normal" "too small" "too big"])
["Alice is normal" "Alice is too small" "Alice is too big"]
title: map 과 reduce
**map은 지연시퀀스를 반환**한다 따라서 무한 시퀀스를 다룰 수있다.
```clojure
user=> (map #(str %) ["asd" "dsa" "qwe"])
("asd" "dsa" "qwe")
user=> (take 3 (map #(str %) (range)))
("0" "1" "2")
```
map 은 또한 한개 이상의 컬렉션을 인자로 받을 수 있는데, 이때 함수의 인자로 각각의 컬렉션 요소가 사용된다.
```clojure
user=> (def animals ["mouse" "duck" "rabbit" "dog"])
#'user/animals
user=> (def colors ["blue" "red" "pink" "black"])
#'user/colors
user=> (defn animal-color [animal color]
#_=> (str animal " - " color))
user=> (map animal-color animals colors)
("mouse - blue" "duck - red" "rabbit - pink" "dog - black")
; 1대1 매칭이 아니라 더 작은 컬렉션
user=> (def two-colors ["black" "pink"])
#'user/two-colors
; 작은 컬렉션을 기준으로 map이 평가된다
user=> (map animal-color animals two-colors)
("mouse - black" "duck - pink")
```
**reduce는 무한 시퀀스르 다룰수 없다 (함수의 입력컬렉션이 모두 없어질때까지 실행하기 때문)**
```clojure
; 초기값이 없을시 컬렉션의 첫번째 값이 초기값이 된다.
user=> (reduce + [1 2 3 4 5])
15
; 2 + 3 * 3 + 4 * 4
user=> (reduce (fn [r x] (+ r (* x x))) [2 3 4])
27
; 0 + 2 * 2 + 3 * 3 + 4 * 4
user=> (reduce (fn [r x] (+ r (* x x))) 0 [2 3 4])
29
; 배열도 마찬가지 방법으로 적용 가능 (단 초기값을 잘 써줘야함)
user=> (reduce #(conj %1 (* %2 %2)) [] [1 2 3 4 5])
[1 4 9 16 25]
```
그 밖에도
많은 유용한 함수들이 있다.
진위함수의 역을 반환하는 complement
filter, remove, flatten, partition, split-with 등등 많은 함수들이 있지만
이러한 것들은 필요시에 검색하여 더 알아보는 것으로 하고 가장 기본이 되는 for 에 대해서만 간단하게 짚어보고 끝내려한다
(for [순환할 요소들] 평가부) 로 구성되어있다.
For문
; animal의 각 요소에 따라 for문을 돔
user=> (for [animal ["dog" "cat" "duck"]]
#_=> (str animal " is cute"))
("dog is cute" "cat is cute" "duck is cute")
; name 함수를 사용하면 키워드를 문자열로 평가하여 반환함
user=> (for [animal [:dog :cat :duck]]
#_=> (str (name animal) " is cute"))
("dog is cute" "cat is cute" "duck is cute")
; 이중 포문
user=> (for [animal [:dog :cat :duck]
#_=> color [:blue :red]]
#_=> (str (name animal) " is " (name color)))
("dog is blue" "dog is red" "cat is blue" "cat is red" "duck is blue" "duck is red")
; 포문 내 :let 수정자 (내부에서 효율적인 관리)
; :let을 사용해 for문을 돌 값 이외의 정보를 캐싱 및 관리해 줄 수있다
user=> (for [
#_=> animal [:dog :cat :duck]
#_=> color [:blue :red]
#_=> :let [animal-color (str (name animal) " is " (name color))]
#_=> ]
#_=> (str animal-color "-" animal-color ))
("dog is blue-dog is blue" "dog is red-dog is red" "cat is blue-cat is blue" "cat is red-cat is red" "duck is blue-duck is blue" "duck is red-duck is red")