Skip to content

[프로그래밍 클로저] 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

  • 조건문에서 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 : #'

함수 (joony)

  • 함수호출은 첫번째 원소가 함수 이름인 리스트를 평가함으로써 이루어짐
(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")

;

소스보기

https://www.dropbox.com/s/tnloyxy9cfp7sns/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202014-08-13%2008.37.45.png

make-greeter가 만들어내는 함수는 생성될 때 넘겨받은 greeting-prefix값을 기억하고 있다. 좀더 형식적으로 말하면 make-greeter가 반환하는 함수는 Greeting-prefix값에 대한 closure라고 라고 할수있다.

언제 익명함수를 사용할까

  • 모든 곳에 적합하지는 않다
  • 여러번 호출할 함수는 명시적으로 선언하는 것이 옳다.
  • 선택일 뿐 모든 곳에 사용해야하는 것은 아니다
  • 써서 쉬워질 때만 사용할 것

var, 바인딩, 이름 공간 (joony)

var

  • 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

흐름 제어 (lester)

for 루프는 언제 나올까 (lester)

메타데이터 (lester)