조건문, 재귀문, 구조분해

클로저

별거없다

  • 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값은 마지막 요소에 참으로 평가되는 값을 써주면 해당 값이 반환된다.

title: 커링 및 함수 합성
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")