map
과 reduce
let
으로 로컬 바인딩하기
count
,conj
,first
,rest
같은 몇 가지 함수들을 살펴봤습니다. 마찬가지로, 우리가 사용한 모든 산술 연산도 함수입니다 :+
,-
,*
,/
그렇다면 함수란 무엇일까요?
함수란 독립적이고 개별적인 코드로, (인자라고 하는) 값을 받고 값을 반환합니다.
count
, conj
, first
+
, -
, *
, /
defn
은 함수를 정의합니다.forward-right
은 이 함수의 이름입니다.- 다음 줄의 문자열은 함수에 대한 설명입니다. 이는 선택 사항입니다.
[turtle]
은 인자의 리스트입니다.turtle
이라는 한 개의 인자를 갖고 있습니다.(forward turtle 60) (right turtle 135)
은 함수의 본체입니다. 함수를 사용할 때 실행되는 부분입니다.
(defn forward-right
"Moves specified turtle forward and tilts its head"
[turtle]
(forward turtle 60)
(right turtle 135))
forward-right
함수 사용하는 법
forward-right
를 사용하기 위해선, 함수를 호출해야 합니다. 앞서 사용해본 함수들을 다뤘던 것처럼 말이죠.
(forward-right :trinity) ;=> {:trinity {:angle 135}}
(forward-right :neo) ;=> {:neo {:angle 135}}
함수는 한 개 이상의 인자를 가질 수 있습니다. turtle과 길이를 인자로 갖는
forward-right-with-len
함수를 만들어 보겠습니다.
(defn forward-right-with-len
"Given turtle and length, forward the turtle and tilts its head"
[turtle len]
(forward turtle len)
(right turtle 135))
(forward-right-with-len :trinity 90) ;=> {:trinity {:angle 135}}
(forward-right-with-len :neo 80) ;=> {:neo {:angle 135}}
walk.clj
로 이동합니다.forward-right
함수를 작성합니다. (아래쪽)walk.clj
를 저장합니다.forward-right
함수 전체를 선택하고 Eval Selection을 누릅니다.(forward-right :trinity)
를 입력합니다.(defn forward-right
"Moves specified turtle forward and tilts its head"
[turtle]
(forward turtle 60)
(right turtle 135))
walk.clj
로 이동합니다.forward-right-with-len-ang
함수를 작성합니다.(forward-right-with-len
의 확장판)forward-right-with-len-ang
함수 전체를 선택하고 Reload Selection을 누릅니다.(forward-right-with-len-ang :trinity 60 120)
를 씁니다.함수명은 심볼입니다. 값에 이름을 할당할 때
def
와 함께 사용했던 심볼들처럼 말이죠.
숫자가 아닌 문자로 시작해야 합니다. *, +, !, -, _, ?와 함께 영숫자 문자를 포함할 수 있습니다. 이러한 유연성은, 우리가 함수명에 사용하는 관례와 관련되기 떄문에 함수에서 중요합니다.
클로저는 두 가지 유형의 함수가 있습니다:
- 값을 반환하는 함수,
- 참/거짓을 반환하는 함수. 두 번째 유형의 함수를 진위 함수(predicate)라고 합니다.
클로저에서,
=
는 놀랍게도 진위 함수입니다. 그 외에도, 다른 많은 컴퓨터 언어들처럼 클로저는 크기 비교를 위한 진위 함수를 갖고 있습니다. 대부분의 진위 함수는?
로 끝납니다.
=
,not=
>
,<
,>=
,<=
true?
,false?
,empty?
,nil?
,vector?
,map?
컬렉션과 함께 사용할 수 있는 가장 강력한 함수들 중 몇몇은 함수의 인자로 다른 함수가 올 수 있습니다. 클로저의 가장 마법같은 부분입니다. –다른 많은 프로그래밍 언어들도 그러하지만, 익숙하지 않은 개념이라 처음에는 이해가 안 갈 수 있습니다. 예제를 보고 더 공부해 봅시다.
map
함수
map
은 인자로, 다른 함수와 컬렉션을 받습니다. 컬렉션의 각 요소들을 대상으로 인자로 주어진 함수를 호출합니다. 그런 다음 해당 함수 호출의 결과를 담은 새로운 컬렉션을 반환합니다. 생소한 개념이지만, 일반적인 함수형 프로그래밍과 클로저의 핵심입니다.
(map inc [1 2 3]) ;=> (2 3 4)
(map (partial + 90) [0 30 60 90]) ;=> (90 120 150 180)
참고: partial
reduce
함수함수를 인자로 받는 또 다른 함수를 살펴봅시다.
reduce
함수가 있습니다. 컬렉션을 단일 값으로 변환할 때 사용합니다.
reduce
함수는, ‘두번째 인자로 제공된 컬렉션’에서 처음 두 요소를 취한 후, ‘첫번째 인자로 제공된 함수’에 그 요소들을 인자로 주어 호출합니다. 그 후에도 ‘인자로 주어진 함수’를 계속해서 호출 하는데, 이제부터는 이 함수의 인자로 ‘이전 함수 호출의 결과’와 ‘컬렉션의 다음 요소’를 취합니다.reduce
함수는 이 과정을 컬렉션의 끝에 도달할 때 까지 반복하고 반복합니다.
(reduce str (turtle-names)) ;=> ":trinity:neo:oracle:cypher"
(reduce + [30 60 90]) ;=> 180
average
라는 이름의 함수를 만듭니다.[{:angle 30} {:angle 90} {:angle 50}]
를 입력으로 사용합니다.:angle의 평균값을 계산합니다.
map
, reduce
, count
함수를 이용하세요.여태껏 봐왔던 모든 함수들은 이름을 갖고 있었습니다.
+
,str
,reduce
처럼 말이죠. 하지만, 함수는 이름이 꼭 필요하지는 않습니다. 값이 이름을 가질 필요가 없듯이요. 이렇게 이름이 없는 함수를 무명 함수라고 합니다. 무명 함수는fn
으로 생성됩니다. 이렇게 말이죠:
(fn [s1 s2] (str s1 " " s2))
진도 나가기 전에, 함수명은 항상 자유롭게 지을 수 있다는 걸 이해해야 합니다. 그렇게 하는 것이 잘못된 것은 아닙니다. 하지만, 무명 함수로 이루어진 클로저 코드를 앞으로 볼 것입니다. 때문에 무명 함수를 알고 있어야 합니다.
(defn join-with-space
[s1 s2]
(str s1 " " s2))
왜 무명 함수를 쓸까요? 무명 함수는 꽤나 유용합니다. 바로 함수 섹션에서 배운
map
이나reduce
같은 함수들처럼, 다른 함수를 인자로 갖는 함수를 가질 때 말입니다. 무명 함수의 사용 예제를 살펴봅시다.
(map (fn [t] (forward t 45)) (turtle-names))
;=> ({:trinity {:length 45}} {:neo {:length 45}} {:oracle {:length
45}} {:cypher {:length 45}})
(reduce (fn [x y] (+ x y)) [1 2 3]) ;=> 6
(reduce (fn [a b] (str a ", " b)) (map name (turtle-names)))
;=> "trinity, neo, oracle, cypher"
let
으로 로컬 바인딩하기함수를 만들 때, 코드를 읽기 쉽게 하거나 값을 다시 사용하기 위해 값에 이름을 할당하고 싶을 수 있습니다. 하지만 함수 안에서는 함수 밖에서처럼
def
을 사용해서는 안됩니다. 대신에let
이라고 불리는 특별한 양식을 사용해야합니다.
let
으로 로컬 바인딩하기
let
을def
처럼 사용해서 값에 이름을 할당할 수 있습니다. 값에 이름을 할당하면, 그 이름을 심볼이라고 부릅니다.
Reference: Assignment let
(let [mangoes 3
oranges 5]
(+ mangoes oranges))
;=> 8
let
예제
여태껏 봐왔던 것들 중에서 가장 복잡합니다. 각 단계를 진행해봅시다. 먼저, 함수의 이름과 함수를 설명하는 문자열과 인자를 갖고 있습니다. 다른 함수들과 같죠.
다음엔,
let
이 보입니다.let
은 이름과 값이 번갈아 나오는 벡터를 첫 인자로 받습니다.t1
이 이름이고,(first names)
의 결과를t1
에 할당합니다. 또(last names)
의 결과를t2
에 할당합니다.
이름과 값의 벡터 다음에,
let
함수의 본체가 나옵니다. 여타 함수의 본체처럼 실행하고 값을 반환합니다.let
안에t1
과t2
가 정의되어있습니다.
walk.clj
로 이동해서opposite
함수를 작성합니다. 그런 다음, 함수 정의의 마지막 줄에서opposite
함수를 평가해봅 니다. 또한,opposite
함수의 사용 예제를 평가해 봅니다.
;; function definition
(defn opposite
"Given a collection of turtle names, moves two of them in different directions."
[names]
(let [t1 (first names)
t2 (last names)]
(forward t1 40)
(backward t2 30)))
;; function usage
(opposite (turtle-names))
첫번째 슬라이드로 돌아가거나, curriculum outline으로 가세요.