-
Notifications
You must be signed in to change notification settings - Fork 1
[프로그래밍 클로저] 2장 클로저 둘러보기
Joony edited this page Aug 13, 2014
·
18 revisions
- 리스트 타입은 기본적으로 첫번째 심볼을 가지고 함수를 호출 한다.
(concat [1 2] [3 4])
-> (1 2 3 4)
(+ 1 2)
-> 3
(+ 1 2 3)
-> 6
(+)
-> 0
(= 5 5)
-> true
- 나누기 연산자의 결과가 정수가 아닌 경우에는 clojure.lang.Ratio 형태로 나온다. 소수로 나오게 하려면 소수 형태의 값을 사용해야한다.
(/ 22 7)
-> 22/7
(/ 22.0 7)
-> 3.142857...
- 숫자 뒤에 M을 붙이면 BigDecimal 타입(더 정확한)으로 나타낼 수 있다. 하지만 연산 속도가 느리다.
(+ 1 (/ 0.0000001M 100000000000000000000000))
-> 1.000000000000000000000000001M
- 클로저에서 기본적인 숫자는 java.lang.Long으로 표현되며 큰 수는 clojure.lang.BigInt로 처리된다. BigInt는 화면에 표시될때 숫자 뒤에 N이 붙는다.
(class 10)
-> java.lang.Long
(def a 1000000000000000000000000000000000000000)
a
-> 1000000000000000000000000000000000000000N
(class a)
-> clojure.lang.BigInt
(class (* 1000 1000 1000 1000 1000 1000 1000 1000))
- 계산 결과가 커지면 ArithmeticException integer overflow clojure.lang.Numbers.throwIntOverflow 예외가 발생한다.
(class (* 1000 1000 1000 1000 1000 1000 1000 1000))
-> ArithmeticException integer overflow clojure.lang.Numbers.throwIntOverflow (Numbers.java:1424)
- 계산 값중에 BigInt 값이 있다면 overflow가 나지 않는다.
(class (* 1000 1000 1000 1000 1000 1000 1000 1000N))
-> 1000000000000000000000N
- str, concat, +, -, java.lang.String, 등등 모든 이름을 나타내는 것을 심벌이라한다.
- 심벌은 숫자로 시작할 수 없다.
- .과 /는 네임스페이스를 위해 특별한 기호로 취급된다.
- 문자열은 ""로 감싼다.
- println으로 화면에 출력할 수 있다.
(println "multiline\nstring")
-> multiline
string
- 문자열은 자바의 String 객체이기 때문에 자바의 String 메서드를 그대로 사용할 수 있다.
(.toUpperCase "hello")
-> HELLO
- str 함수에 여러 인자를 사용하면 하나의 문자열로 만들어 준다. 인자에 nil이 있어도 상관 없다. 인자가 숫자라면 문자열로 변환 해준다.
- 문자는 자바의 Character이다.
- 조건문에서 nil과 false외에는 모두 true로 평가된다.
(if () "We are in Clojure!" "We are in Common Lisp")
-> "We are in Clojure!"
(if 0 "Zero is true" "Zero is false")
-> "Zero is true"
- true, false를 반환하는 함수는 보통 ?로 끝나는 이름을 가진다.
(true? expr)
(false? expr)
(nil? expr)
(zero? expr)
- 맵은 키/값을 가지는 데이터 구조이다. { 키 값 키 값 키 값 ... } 순서로 정의하거나 키/값 후에 콤마를 사용해서 구분해도된다.
(def inventors {"Lisp" "McCarthy" "Clojure" "Hickey"})
(def inventors {"Lisp" "McCarthy" , "Clojure" "Hickey"})
- get 함수로 맵의 값을 가져오거나 맵은 자체가 함수이기 때문에 맵의 인자로 키를 넣으면 값을 가져올 수 있다.
(get inventors "Lisp")
-> "McCarthy"
(inventors "Lisp")
-> "McCarthy"
- 맵의 키로 키워드(:으로 시작하는 심벌)를 사용하는 경우가 많다. 키워드를 사용하면 키워드 자체도 함수이기 때문에 인자로 맵을 넣으면 값이 나온다.
(def inventors { :Lisp "McCarthy" :Clojure "Hickey" })
(:Lisp inventors)
-> "McCarthy"
- defstruct 매크로로 구조체를 만들 수 있다. 구조체는 맵과 같이 사용되며 맵과 다른점은 안에 있는 데이터를 명시할 수 있다는 점이다.
(defstruct book :title :author)
(def b (struct book "Anathem" "Neal Stephenson“))
(:title b)
-> "Anathem"
- struct가 안의 값들을 명확하게 정의하지만 고정적인 것은 아니다. struct-map을 이용해 정의되지 않은 새로운 항목을 추가할 수 있다.
(struct-map book :copyright 2008 :title "Anathem")
-> { :title "Anathem " , :author nil , :copyright 2008 }
- 리더 매크로는 클로저 리더가 코드를 읽을 때 다른 구문들과는 특별하게 취급하는 기호들을 말한다.
-
'
는 이 후에 나오는 리스트를 평가하지 않는다. (기본적으로 리스트는 함수로 평가된다.)
'(1 2)
-> (1 2)
- 여러가지 종류의 리더 매크로가 있다.
익명함수 : #()
주석 : ;
deref : @
meta : ^
메타데이터 : #^
인용 : '
정규식 : #""
구문 따옴표 : `
평가 기호 : ~
이음 평가 기호 : ~@
var-quot : #'
- 함수호출은 첫번째 원소가 함수 이름인 리스트를 평가함으로써 이루어짐
(str "hello" " " "world")
-> "hello world"
- 함수 이름은 하이픈으로 단어를 구분 (예.clear-agent-errors)
- 서술식은 물음표로 끝남
(string? "hello")
-> true
(keyword? :hello)
-> true
(symbol? 'hello)
-> true
- 새로운 함수를 정의하려면
defn
을 사용 -
attr-map
은 함수의 변수와 메타데이터 연결 짓는 것으로 2.7절(메타데이터)에서.. - 정확한 개수의 인자가 넘겨지길 기대(개수 안맞으면 exception)
- 이 것을 피하고 싶으면,
인자리스트/함수몸체
로 정의할 수 있음 - 이름이 같은 함수라도 인자 개수가 다르면, 서로 호출 가능
- 이 것을 피하고 싶으면,
- 인자리스트에 &을 사용하면 가변인자를 받는 함수를 만들 수 있다.
- 매개변수에 일대일로 바인딩 되지 못한, 나머지 인자들의 시퀀스가 &뒤의 매개변수에 바인딩 된다.
- 가변 인자는 재귀함수를 작성하는데 매우 유용 (5장에서 배움)
- 다형성을 원할지도 모르지만 클로저는 그 이상을 지원하고 (8장에서 배운다)
-
defn
은 최상위 수준에서 함수 정의할 때 사용 - 함수 내부에서 다른 함수를 만들고자 한다면, 익명함수를 사용해야함
(defn name doc-string? attr-map? [params*] body)
; 이건 그냥 형식을 보여주는 건가?
(defn greeting
"'Hello username'이라는 형식에 대해 인사를 반환합니다"
[username]
(str "Hello, " username))
(greeting "world")
-> "Hello, world"
(doc greeting)
-> -------------------------
user/greeting
([username])
'Hello username'이라는 형식에 대해 인사를 반환합니다
nil
; 인자 없을 때
(greeting)
-> ArityException Wrong number of args (0) passed to: user/greeting clojure.lang.AFn.throwArity (AFn.java:429)
; 인자가 가변적일 때
(defn name doc-string? attr-map?
([params*] body)+)
(defn greeting
"Return a greeting of the form 'Hello, username.'
Default username is 'world'."
([] (greeting "world"))
([username] (str "Hello, " username)))
; !todo 여기서 왜 username에 world를 넘기지????
(greeting)
(greeting "Test")
; &을 쓰는 코드
(defn date [person-1 person-2 & chaperones]
(println person-1 "and" person-2
"went out with" (count chaperones) "chaperones."))
(date "Romeo" "Juliet" "Friar Larwrence" "Nurse")
-> Romeo and Juliet went out with 2 chaperones.
-
defn
으로 만들 수 있는 것은fn
으로 익명함수 가능 - 언제필요할까?
- 함수가 너무 간단하고 자명해서, 함수 이름을 붙이는게 코드를 더 읽기 어렵게 만들 때
(
필터함수
) - 다른 함수의 내부에서만 쓰이는 함수라서 top level에서 이름을 가질 필요가 없는 경우
- 함수 내부에서 데이터를 이용해 동적으로 함수를 만들어내는 경우 (!todo)
- 함수가 너무 간단하고 자명해서, 함수 이름을 붙이는게 코드를 더 읽기 어렵게 만들 때
(
; 필터함수 - 단어 색인을 만드는데, 세글자 미만인 단어는 신경 안쓸 때
; re-split은 이제 없음 다음으로 대체
(use '[clojure.string :only (join split)])
(filter indexable-word? (re-split #"\W+" "A fine day it is"))
(filter (fn [w] (> (count w) 2)) (split "a fine day" #"\W+"))
;w는 콜렉션
(filter #(> (count %)) (split "A fine day" #"\W+"))
; #의 위치가 급 헷갈리네.. 여튼
;다른 함수 내부에서 사용되는 경우
(defn indexable-word [text]
(let [indexable-word? (fn [w](> (count w) 2))]
(filter indexable-word? (split text #"\W+"))))
(indexable-word "a fine day is")
;let을 indexable-word라는 이름으로 렉시컬 범위에서 바인딩 함.
;즉 idnexable-world2는 이름을 붙이긴 했지만 idnexable-word안에서만 붙인 것
-> (fine day)
(defn make-greeter [greeting-prefix]
(fn [username] (str greeting-prefix ", " username)))
(def hello-greeting (make-greeter "Hello"))
(def aloha-greeting (make-greeter "aloha"))
(hello-greeting "world")
(aloha-greeting "world")
;
make-greeter가 만들어내는 함수는 생성될 때 넘겨받은 greeting-prefix값을 기억하고 있다. 좀더 형식적으로 말하면 make-greeter가 반환하는 함수는 Greeting-prefix값에 대한 closure라고 라고 할수있다.
- 모든 곳에 적합하지는 않다
- 여러번 호출할 함수는 명시적으로 선언하는 것이 옳다.
- 선택일 뿐 모든 곳에 사용해야하는 것은 아니다
- 써서 쉬워질 때만 사용할 것
- def나 defn을 사용해 새 객체를 정의할 때 마다,
그 객체는 클로저
var
속에 저장
(def foo 10)
foo
-> 10
-
var의 초기값을 var의 '루트 바인딩'dlfkgka.
-
각 스레드에 한정된 var를 가지는 것이 유용할 때가 있다. (나중에)
-
var 를 직접 참조하기도 함
(var a-symbol)
,(var foo)
- 하지만 위처럼 그대로 사용되는 경우는 드물고 보통은 동일한 일을 하는 리더 매크로인
#
을 사용 #'foo
- 하지만 위처럼 그대로 사용되는 경우는 드물고 보통은 동일한 일을 하는 리더 매크로인
-
하지만 직접 참조하는 경우는 잘 없고, 심벌과 var의 차이도 대개 무시해도 됨.
-
단순히 값 저장 이외 다른 기능들
- 같은 이름의 var가 여러 네임스페이스에서 사용가능 -> 짧은 이름 편히 사용가능
- var는 메타 데이터 가질수 있음 (추후 2.7)
- var는 스레드 별로 동적 바인딩 가능 (추후 6.5)
-
var가 이름에 바인딩
-
함수 호출 시 인자 값은 함수 파라미터 이름에 바인딩
-
함수의 파라미터 바인딩은
렉시컬
범위 가짐
(defn triple [number] (* 3 number))
(triple 10)
-> 30
; 10은 triple함수의 number에 바인딩
- 함수 이외 렉시컬 바인딩 갖는 것은
let
- 바인딩은 exprs안에서만 효과가 있고, Exprs의 마지막 표현식의 값이 let구문의 전체 값으로 반환됨
(let [bindings*] exprs*)
; top과 left에 바인딩 하는 예
(defn square-coners [bottom left size]
(let [top (+ bottom size)
right (+ left size)]
[[bottom left] [top left] [top left] [bottom right]]))
(square-coners 10 10 10)
- 일부 바인딩
- !todo 50 쪽 궁금 (아래 예제에서 밑에서 두개)
(defn greeting-author-1 [author]
(println "Hello," (:first-name author)))
(greeting-author-1 {:last-name "Vinfe" :first-name "Vernor"})
(let [[x y] [1 2 3]]
[x y])
-> [1 2]
(let [[_ _ z] [1 2 3]]
z)
-> 3
(let [[_ _ z] [1 2 3]]
_)
-> 2
(let [[_ y _] [1 2 3]]
_)
-> 3
resolve sym