2015년 8월 31일 월요일

Living Clojure -- Week 2, Day 1

여전히 4Clojure 문제 풀이가 이어진다.

문제 26 Fibonacci Sequence
(= (__ 3) '(1 1 2))

첫 풀이,
(fn [n]
  (loop [i 0
         a 1
         b 1
         seq []]
    (if (< i n)
      (recur (inc i) b (+ a b) (conj seq a))
      seq)))

이번에도 lazy-seq을 이용하여 풀어보았다. n개 추출은 take로 처리할 수 있다.
#(take % ((fn r[a b] (lazy-seq (cons a (r b (+ a b))))) 1 1))

직접 lazy를 만드는 대신 iterate를 이용할 수도 있다.
#(take % (map first (iterate (fn[[a b]][b (+ a b)]) [1 1])))


문제 29 Get the Caps
(= (__ "HeLlO, WoRlD!") "HLOWRD")

첫 풀이,
(fn [s]
  (apply str (filter #(Character/isUpperCase %)  s)))

Character/isUpperCase 라고 static method를 참조할 수 있는데, 왜 함수 자리에 그대로 쓰면 안될까?


문제 48 Intro to some
(= __ (some #{2 7 6} [5 6 7 8]))

some은 bool 함수가 아니다! (some f coll) == (first (filter identity (map f coll))) 즉, f가 map함수이면서 첫번째 logical true value를 반환한다.


문제 42 Factorial Fun
(= (__ 5) 120)

첫풀이
(fn [n]
  (apply * (range 2 (inc n))))

Threading 연습
#(->> (range %) (map inc) (apply *))


문제 52 Intro to Destructuring
(= [2 4] (let [[a b c d e f g] (range)] __))

clojure.org 의 binding forms를 읽어보는 것이 좋다.



처음에 진도를 많이 뺄 것 처럼 하다가 계속 멤돌고 있는 것 같음.

2015년 8월 28일 금요일

Living Clojure -- Week 1, Day 5

이번에도 모두 2일차 진행하며 풀어봤던 문제들이다. 한번에 쭈욱~ 달릴 수 있는 것을 길게 늘여놓았구나 싶다.

문제 30, 이어진 중복 제거.
(= (apply str (__ "Leeeeeerrroyyy")) "Leroy")

loop/recur로 풀수 있는 문제지만 accumulator의 last를 비교해야 한다는 점이 조금 거슬린다. 이건 아마도 Haskell느낌? 하지만 Clojure의 vector는 last도 효율적이니까!
(fn [list]
  (loop [list list
         acc []]
    (if (empty? list)
      acc
      (if (= (last acc) (first list))
        (recur (rest list) acc)
        (recur (rest list) (conj acc (first list)))))))

이미 있는 sequence함수를 사용한다면 reduce로 쉽게 구현된다.

reduce (fn[a b](if (= (last a) b) a (conj a b))) []

그런데 만약 map/filter 등의 sequence함수들처럼 lazy sequence를 반환해야 한다면 어떻게 할 수 있을까? 단순히loop/recur + accumulator로는 안될 것이다.

Rx의 distinctUntilChanged와 같다! 직접 마지막 요소를 keep해도 되지만 drop-while을 이용하면 편리하다. 그리고 오히려 더 간단하다!

(fn duc[coll]
  (lazy-seq
   (when-let [s (seq coll)]
     (let [f (first s)]
       (cons f (duc (drop-while #(= % f) (rest s))))))))

laziness를 확인하려면 함수에 (range) 와 같은 무한시퀀스를 적용시키고 앞에서 (take 5)처럼 가져오면 된다. 첫번째 코드는 undefined이며 두번째는 잘 동작한다.


31번, 이어진 같은 값 묶기.
(= (__ [1 1 2 1 1 1 3 3]) '((1 1) (2) (1 1 1) (3 3)))

아무 생각없이 loop/recur로 작성하면 된다. 이 문제는 내가 가끔 연습문제로 소개하는 개미수열의 하위 문제이기도 하다. (take-while/drop-while)을 이용하면 간단하다. 물론 자주 등장하기 때문에 (split-with)가 이미 있다!

이문제도 lazy sequence를 반환하도록 하려면 lazy-seq을 이용해야 한다.
(fn pack[coll]
  (lazy-seq
   (when-let [s (seq coll)]
     (let [f (first s)
           [hd tl] (split-with #(= % f) s)]
       (cons hd (pack tl))))))

그런데 Code Golf를 보니 엄청 짧은 답들이 많다. 어떻게 한 것일까?

laziness 제거하고 77
(fn p[c]
  (when-let[s (seq c)]
    (let [f (first s)
          [t d](split-with #(= % f) s)]
      (cons t (p d)))))

50글자 이내의 답들이 있는 걸보면 vector를 가정한 것이 아닐까 싶다.

67글자
(fn p[c](if(seq c)(let[[t d] (split-with #(= % (c 0)) c)](cons t(p (vec d))))))

에구, 더는 안되겠다!


41번, n번째마다 버리기.
(= (__ [1 2 3 4 5 6 7 8] 3) [1 2 4 5 7 8])

이건 오히려 loop/recur가 더 어렵겠다. 이미 있는 partition(-all)을 이용하면 간단하다.
(fn[c n](apply concat (partition-all (dec n) n c)))


45번, iterate.
(= __ (take 5 (iterate #(+ 3 %) 1)))

간단하니 넘어가고..


33번, replicate.
(= (__ [1 2 3] 2) '(1 1 2 2 3 3))

두번씩 반복한 32번 문제의 일반화!
(fn[c n](mapcat #(repeat n %) c))            



이렇게 1주일이 지났다. 함수형 프로그래밍 언어들의 연습문제들이 서로 비슷하다. 비슷한 문제들이기 때문에 언어적인 부분을 비교해보기 좋다.

Living Clojure -- Week 1, Day 4

둘째날 진도 빼면서 풀었던 문제들이다!

20번, 끝에서 두번째.
(= (__ (list 1 2 3 4 5)) 4)
재귀를 작성할 수도 있겠지만 함수 합성으로 풀 수도 있다.
24번, 모두 더하기.
(= (__ [1 2 3]) 6)
apply + 가 가장 간단한 답이 아닐지.. 그런데 Code Golf에는 더 짧은 답도 많다. 어떻게 풀었을까?
25번, 홀수만.
(= (__ #{1 2 3 4 5}) '(1 3 5))
이런 문제는 그냥 스킵할까 싶기도 하다. 

27번, 팔린드롬.
(false? (__ '(1 2 3 4 5)))
(true? (__ "racecar"))
처음엔 모두 loop/recur재귀로 풀었는데, 이미 있는 reverse를 활용하면 간단한 문제다. 다만 문자열에 대해 조금 이해해야 한다. reverse는 seq에 대해 동작한다. string은 ISeq이지만 sequence는 아니다. 그래서 reverse하고난 결과는 sequence로 바뀐다. string과 sequence를 비교하면 항상 다르다고 나온다. 즉, 둘다 sequence가 되도록 해줘야!

32번, 두번씩.
(= (__ [1 2 3]) '(1 1 2 2 3 3))
flatMap의 clojure식 이름인 mapcat을 이용하면 간단하다. 이 문제에서 인상적인건, #() 함수 리터럴. 결과값이 단순히 값이 되는 경우에는 사용할 수 없다는 점이 아쉽다. #([% %]) 는 올바르지 않다. 괄호가 함수리터럴을 나타내는 동시에 application 폼으로도 해석되기 때문이다. #(identity[% %])처럼 함수를 첫 요소로 넣어줘야 하는데 identity가 너무 길다. 화살표람다식은 간단히 x->x 혹은 x=>x라고 할 수 있는데... Clojure에서는 (fn[x]x)라고 해봤자 똑같이 8글자다!


이미 풀어봤던 문제들이긴 하지만 블로그에 옮기다보니 몇가지 생각해볼거리도 추가하게 되었다. 

2015년 8월 27일 목요일

Living Clojure -- Week 1, Day 3

4Clojure 문제를 순서대로 진행하는 것이 아니라 듬성 듬성 진행한다.

37번 regular expression 문제.
(re-seq)함수는 첫번째 인자인 정규표현식으로 두번째 인자인 문자열에서 검색하여 찾은 것을 lazy sequence로 반환하는 모양이다. ***seq 인 함수들이 모두 같은 목적을 가진다고 볼 수 있겠다. (tree-seq)처럼...

어제 54번까지 풀면서  37번은 해 버렸음.


57번 단순 재귀 문제.
((fn foo [x] (when (> x 0) (conj (foo (dec x)) x))) 5) 의 결과를 물어보는데..

이 문제는 Clojure의 기초를 조금 알아야 한다.
  • (fn)폼에서도 이름(옵셔널)을 줘서 재귀함수를 작성할 수 있음 (JavaScript와 마찬가지)
  • (when)매크로는 조건을 만족하지 않을 때 nil값
  • (conj nil x)은 '(x), 즉 vector가 아닌 list. 여기서 nil은 말하자면 빈 리스트. (이부분은 앞으로도 자주 헷갈릴 것 같다. Scheme 언어들은 nil과 '()이 같다. 그런데 Clojure에서는 nli과 '()이 다르지만 conj나 into처럼 collection자리에 쓰이는 경우는 nil이 '() 의미로 사용된다.)
    • 리스트에서 (conj coll x) == (cons x coll)과 같아서 앞에 x를 붙인다.

68번 loop/recur 재귀 문제.
(loop [x 5 result []]
  (if (> x 0)
    (recur (dec x) (conj result (+ 2 x)))
    result)))

accumulator를 이용하는 일반적인 linear recursion이다.


71번 (->)
(= (__ (sort (rest (reverse [2 5 4 1 3 6]))))
   (-> [2 5 4 1 3 6] (reverse) (rest) (sort) (__))
   5)
처음엔 (sort(rest(reverse...)))의 결과를 말하라는 건줄 착각했다. 가만보니 해당 식을 (->)매크로로 바꿔쓸 수 있음을 보여주는 문제이고, 5라는 값이 나오는 함수 아무거나 넣어주면 된다.


72번 (->>)
(= (__ (map inc (take 3 (drop 2 [2 5 4 1 3 6]))))
   (->> [2 5 4 1 3 6] (drop 2) (take 3) (map inc) (__))
   11)
이번에도 11을 반환하는 아무 함수. 참, 인자는 하나 있어야 한다.  하스켈로 보자면 \_->11

reduce +를 사용할 수도 apply +를 사용할 수도 있다. (fn[_]11)


145번 (for)
(= __ (for [x (range 40)
            :when (= 1 (rem x 4))]
        x))

(for)폼의 다양한 모양을 확인해보라는 문제. 특히 세번째 코드를 만들어낸 출제자가 참 신기하다. :let, :while, :when 등이 있으나, map/filter/reduce 등의 기본에 익숙하면 그냥 이걸 쓰는게 나을지도.

(= __ (for [x (iterate #(+ 4 %) 0)
            :let [z (inc x)]
            :while (< z 40)]
        z))
(= __ (for [[x y] (partition 2 (range 20))]
        (+ x y)))


2015년 8월 26일 수요일

Living Clojure -- Week 1, Day 2

Living Clojure의 Day2 과정은 4Clojure 문제 중에서 아래의 것들을 풀어보는 것이다.
2일차니까 어려운 문제는 없이 기본적인 내용들이었다. 하지만 2일차 하루 쉬었다 한것도 있고 하여 13번부터 정주행하여 54번까지 풀어버렸다.

54번까지 푸는 중에 간혹 어려운 문제들도 있었다.
특히 28번 문제는 좀 까다로웠는데, flatten을 직접 구현하는 것. 어려웠던 이유는 문제가 요구한 것이 (fn) 익명 함수를 구현하는 것이었고, loop/recur가 아닌 일반 재귀라면 쉬울 것을 아는 것이 loop/recur뿐이라 tail recursion으로 구현하려다 보니 꽤나 힘들었다. 

하지만 (fn) 역시 이름을 가질 수 있고, 그러니 재귀가 가능하다. 연습문제 푸는 차원이라면 굳이 loop/recur안써도 좋았을걸..


Om - 기본 튜토리얼 따라하기

오늘은 Om 튜토리얼 중 기본을 따라가봤다.  (사이드바 링크에 Intermediate/Advanced도 있음!)

Om은 React.js의 ClojureScript 바인딩 라이브러리다. ClojureScript 쪽에서 워낙에 유명한 David Nolen이 만든 라이브러리이고, React.js를 래핑한 라이브러리이면서 더 나은 성능을 보이는 특이한 라이브러리다. (물론 더 빠를 수야 없겠지만 Plain JS로 개발할 때에는 기본적으로 꺼둘수 밖에 없는 최적화 옵션을 ClojureScript에선 기본으로 켜 놓을 수 있기 때문이다.)

야, 이건 꼭 해봐야겠다~ 싶었던 Figwheel을 기본으로 진행! Good!

Basic이면서 꽤나 길게 설명되어 있어서 결국 마지막 단계는 눈으로만 보고 끝내버렸는데, 내일 시간내서 코드 보지 않고 쭈욱 따라가는 연습을 더 해봐야겠다.

오늘 보면서 느낀 점, 궁금한 점 등을 남긴다.

  • Figwheel의 REPL은 Browser Connected REPL과 어떤 관계일까?
  • reify 매크로는 anonymous type & its instance 를 만들기 위한 것. 프로토콜이나 인터페이스를 구현할 목적으로 사용할 수 있다. Om에서는 reify를 주로 사용하나? 직접 타입을 만드는 것은 Clojure Idiomatic 하지 않은 것인가?
  • 아래와 같은 코드가 잠깐 나오는데,  (.. e -target -value) 부분에서 잠깐 막혔다.  (..) 매크로 는 Java(Script) Interop 중 하나. 뒤에 멤버를 계속 연결하여 값을 얻을 수 있다. 멤버는 (.method arg)나 -field 중 하나. (->) 매크로와 사용 목적이 비슷하다. 매크로가 너무 많은 것 아닌가? 몇가지 표준적인 방법을 제공하고 일관된 코드를 추구하는 것이 나을 것 같은데, 개인들의 스타일에 따라 너무 달라질 것 같다. 

(defn handle-change [e owner {:keys [text]}]
  (om/set-state! owner :text (.. e -target -value)))

  • #js 라는 tagged literal 도 뭔가 헷갈렸다. reader가 이어지는 데이터를 어떻게 처리할지 지정하기 위한 것. built-in으로는 #inst와 #UUID가 있다. ClojureScript에선 #js가 built-in인 모양이다. 이어지는 리스트/맵을 JavaScript Object로 생성하기 위함.
  • 버튼 이벤트를 처리하는 부분을 core.async로 처리했는데, 적당한 방법인지? Intermediate/Advanced 튜토리얼에 답이 있기를.  왜냐면 이건 너무 귀찮은 방법이니까! 특히 아래와 같이 직접 put!으로 데이터를 던지고 go/loop/recur로 처리하는 건 좀 아닌것 같다. 좀더 reactive한 방법이 없다면 cycle.js로 ...
 (dom/button #js {:onClick (fn [e] (put! delete @contact))} "Delete")))))


        (go (loop []
          (let [contact (<! delete)]
            (om/transact! data :contacts
              (fn [xs] (vec (remove #(= contact %) xs))))
            (recur))))))

  • mapv 가 뭘까? 코드만 보면서 유추해보려다 포기했는데, 답은 strict 버전이라는 것. map이 lazy 버전인데, Om은 Lazy와 잘 맞지 않는다고! 그래서 map대신 mapv를 여기저기 많이 쓰게 된다. lazy가 장점이라고 했다가 결국은 non-lazy를 선택적으로 사용해야 하는 상황은 어쩐지 선택지가 늘어나서 정밀하게 컨트롤할수 있다는 장점같으면서도 개발자를 더 불편하게 만드는 요소가 아닐까 싶다. 하스켈과도 마찬가지!
  • 그건 그렇고 아래의 코드는 정말 이해하기 어려웠다. map 자료형이 그 자체로 함수처럼 동작하여 key -> value 가 되기도 하고, key가 함수처럼 동작하여 map -> value 가 되기도 하는데, 그 두가지 방법이 다 적용되어 있다. 게다가 if는 else-branch가 없어서 결과에는 nil이 포함되어 있을거다. Haskell/Scala등으로 타입에 많이 중독된 입장에서 보자면 매우 불안불안한 코드.

(defn people [data]
  (->> data
    :people
    (mapv (fn [x]
            (if (:classes x)
              (update-in x [:classes]
                (fn [cs] (mapv (:classes data) cs)))
               x)))))



결론적으로,
  • 여전히 Clojure(Script)에 대해 모르는게 많고,
  • 마음에 안드는 부분들이 있는데, 
  • 이건 아직 제대로 된 방법을 잘 몰라서 그런 것일 뿐이라고 보고 일단은 Go~

2015년 8월 24일 월요일

Living Clojure -- Week 1, Day 1

Living Clojure 에서 추천하는 7주 프로그램을 시작했다. 당연히 이런 프로그램에서 가장 힘든건 시작이 너무 쉽고 지루하다는 것이다.

Week 1, Day 1의 주요 내용은 4Clojure의 몇가지 문제를 풀어보는 것이고, 그 문제들은 대부분 기본 자료형(list, vector, map, set)의 간단한 사용법을 알아보는 것.

기억에 남은 건 = 함수.

당연히 인자 2개를 예상했지만 [x], [x y], [x y & more] 이렇게 3가지 버전이 오버로딩되어 있다. (doc =) 으로 설명을 봐도 오버로딩되어 있다고 알려줄 뿐 동작을 설명해주지 않는다. 소스코드를 보면 알 수 있는데... [x] 를 기본으로 보면 [], [y], [&more] 들이 결국 하나로 설명된다. base case는 true이며 재귀적으로 (first more)에 대해 비교를 한다.

(= nil nil)
=> true
(= nil '())
=> false
(nil? '())
=> false

Scheme에서는 '()가 nil이었지만 Clojure가 아니라는 점이 특이.

2015년 8월 22일 토요일

Clojure Java Interop - 자바 호환성이 정말 도움되나?

요며칠 Clojure로 작업하면서 몇가지 라이브러리 가져다 쓰는데, 특히 Java 라이브러리르 Clojure 스럽게 래핑해놓은 라이브러리에서 불편함이 있어서 옮겨본다.

Rich Hichey가 Clojure를 소개하면서 JVM 기반으로 언어로 만든 이유는 언어와 달리 성공적인 플랫폼이고 이미 많은 라이브러리들이 나와 있기 때문에 그 이득을 누리기 위해서라고 한 걸 들은 적이 있다. 정확한지는 모르겠지만... 암튼 그래서 쉽게 Java쪽 코드를 불러 쓸 수 있게 해놓았다. 그것이 Java Interop이다.

어차피 대부분의 Java 라이브러리들은 객체 중심으로 작성되어 있는데, 그러다보니 이를 Clojure스럽게 사용할 수 있게끔 포장해놓은 라이브러리들이 있게 된다. Interop 덕분에 그냥 써도 되지만 그러면 Clojure Idiomatic하지 않으니까! 그런데 이런 포장 라이브러리가 전체 API를 다 커버하지 못하는 경우가 있고, 그러면 결국은 다시 원래의 Interop에 기댈 수 밖에 없다. 기껏 포장 라이브러리 써서 좀 Clojure스럽게 짜보려 했지만 그러기 어렵단 얘기다. 물론 대부분은 오픈소스여서 빠진 wrapper는 추가하고 Pull request 보내도 되겠지만 매번 그러기도 쉽지 않다.

예를 들어, 오늘 사용한 라이브러리 두 개는 RxClojureclj-time 이었다.  각각은 RxJavaJoda-Time 을 래핑해놓은 것이다.

RxClojure의 간단한 예를 보면 ...

(require '[rx.lang.clojure.core :as rx])

(->> my-observable
     (rx/map (comp clojure.string/lower-case :first-name))
     (rx/map clojure.string/lower-case)
     (rx/filter #{"bob"})
     (rx/distinct)
     (rx/into []))

상당히 Clojure 스럽게 바뀌어 있다. -> 대신 ->> 쓰레딩을 사용할 수 있게 되었고, namespace rx만 지정해주면 map, filter, into 등을 clojure.core의 것들처럼 사용할 수 있다.

하지만 RxJava의 Observable 클래스에는 백여개 정도의 메쏘드(static포함)가 정의되어 있는데, 이 중 하필 오늘 사용하려한 buffer메쏘드가 RxClojure에 아직 포함되지 않았다.  그래서 rx/map, rx/filter 처럼 rx/buffer 이렇게 쓰지 못하고 결국 (.buffer ...) 로 중간에 형식이 바뀌어야 했다. 이것이 별거 아닐수도 있지만 ->> 를 쓰는 중에 다시 -> 를 쓸 수도 없는 일 아닌가. 그래서 전부 .filter, .map 처럼 바꾸고 -> 를 사용하게 되었다. 그런데 이젠 또 Functional interface인 rx.functions.* 의 인터페이스들과 Clojure의 람다(fn)가 직접 호환되지 않는다는 문제가 드러났다. .map을 rx/map으로 바꾸고 인자의 순서도 바꾸면서 알게 모르게 Clojure의 람다를 사용하도록 친절하게 바꿔놓았는데, .map을 사용하려하니 문제가 된 것이다. RxClojure는 이런 경우를 대비하여 rx.lang.clojure.interop/fn 이라는 편의 매크로를 만들어놨다. Java가 람다식으로부터 해당 함수형 인터페이스로 자동 변환되는 것과는 달라서 불편함이 따른다.

(require '[rx.lang.clojure.interop :as rx])  ;; <- interop 으로 바뀌었다.

(-> my-observable
  (.map (rx/fn [x] ...))
  (.buffer 1 TimeUnit.MINUTE)
  (.subscribe (rx/action [xs] ... )))

clj-time 의 경우도 LocalDateTime과 DateTime의 자동변환이 이뤄지지 않기 때문에 비교문에서 클래스캐스팅 예외가 나오는데, 이를 확인하려면 Joda-Time의 어떤 메쏘드와 clj-time의 어떤 함수가 매핑되는지 눈대중으로 찍으면서 타입을 확인 했다. 그런 다음엔 다시 Joda-Time에서 적당한 변환 메쏘드를 찾고, 이는 다시 Java Interop을 거쳐서 변환했다. 런타임에 타입이 문제된다는 걸 확인하고서도 이를 바로잡기 위해 불편한 과정을 밟아야 한다는 점이 매우 아쉬웠다.




2015년 8월 19일 수요일

"Clojure 시작하기" 시작하기

Clojure로 뭔가를 만들어보겠다고 덤비면서 공부하는 내용들을 정리해보려고 한다. 누군가에게는 도움이 될지도 모르니까. 적어도 내게는 도움이 될거다.

최근에는 페이스북, 구글+, 트위터 등의 SNS와 미디엄, 브런치, 텀블러 등의 좀더 단순화된 블로깅 서비스들이 많아서 블로그는 한물 간것 같지만, 그래도 뭔가를 정리해보려면 블로그가 제일 만만한 것 같다.

----

새로운 언어로 뭔가를 해보자고 덤비면 좀 막막하다.

Clojure와의 첫만남을 살펴보면...  한참 Ruby, Erlang 등이 유행처럼 알려지고 책들도 한두권씩 나오던 그때 번역서를 하나 보다 만 것이 첫 만남이었다. 아마도 2010년 전후?... SICP라는 책을 공부하겠다고 덤비면서 Scheme(나중에 Racket)을 알게 되었고, Clojure는 Scheme의 JVM 버전 정도인가? 하는 느낌만 남아 있었다.

하지만 Rich Hickey (Clojure를 만든 아저씨)의 발표는 재미있어서 Clojure는 공부 안하면서 이 아저씩의 발표는 줄곧 봐 왔었다. 그 중에는 도저히 무슨 얘기인지 알아먹기 힘든 이야기들도 많았지만 Clojure에 대한 이야기는 그런데로 알아들을만 했다. Clojure 만의 설계 철학이랄까, 아니면 Lisp하는 사람들의 우쭐거림(?)이 뭔가 있어 보이기도 했고 말이지. (특히 매크로! 멋있지 않나?)

그러다가 ClojureScript 개발자인 David Nolen 도 레이더에 잡혀서 이 아저씨의 동영상도 많이 봤다. 자기가 어떻게 공부했는지, 공부하는데 그치지 않고 그것들을 어떻게 실제로 구현해보고 실험해봤는지, 그 결과가 어떻게 Core.async 혹은 Core.match 등에 녹아들었는지 등의 발표들은 보는 것만으로 즐거웠다. 아마, ClojureScript 발표는 JS컨퍼런스 어딘가의 발표에서 본것이었을거고, 원래 Clojure에 막연히 가져왔던 로망과 맞물려서 굉장히 맘에 들었던 모양이다.

SICP만 보고나서 Clojure를 접했던 처음과, 그동안 동영상도 많이 보고 Haskell/Scala 등으로 함수형 프로그래밍도 어느정도 익히고 나서 보는 지금은 분명 다른 느낌일거라 기대한다.

(이미 2~3주 작업을 해 오면서 '많이 다름'을 절실히 느끼고 있다. -.-;;)

---

이제 정말 '시작하기'를 어떻게 했나 돌아보자.

새로운 언어를 시작할 때마다 대개 비슷한데, 난 먼저 초간단 책(이미 책이 나와있다면..)을 하나 스르륵 훑어보는 것으로 시작한다. Clojure의 경우엔 비교적 최근에 나온 Living Clojure 라는 책을 골라서 말그대로 초스피드 대충대충 훑어봤다. 뭐 직접 짜보려 덤비면 기초적인 함수 하나도 몰라서 일일이 구글링 해야 하는 수준이지만, 그래도 뭐가 있구나를 빨리 파악해둬야 하니까..

그런 다음으로 고른 책은 Reactive Clojure Programming 이란 제목이다. 이건 최근에 Rx에 관심이 많았던 것이 반영된 결과다. Rx를 이용하여 Asynchronous/Event-based programming을 어떻게 하나 보여주고, 이걸 다시 Core.async로 어떻게 하는지 비교하여 보여준다. 이 책을 읽으면서는 Clojure 코드가 어떤식으로 만들어지는지, 혹은 다른 라이브러리를 가져다 쓸때 어떤 식이 되는지에 포커스를 두었다.

(팀 계정으로 safaribooksonline을 이용할 수 있으니 얼마나 다행인가!)

1, 2주 정도 지나면서 그나마 기본적인 lein 명령어나 프로젝트 구성, 프로그램 구성이 눈에 들어오고, 코드도 (구글링의 도움을 받아) 읽을 수 있게 되면서 Korean Clojure User Group (구글 그룹스)에 기웃거리기 시작했다. Korean Lisper에서 분리되어 2015년 생겨난 그룹인데, 눈팅만 해오다 관심가는 글에 답글도 달기 시작했다. 사람들이 Clojure로 뭘 하는지, 뭘 어려워 하는지 등을 아는 것도 도움이 되니까!

지난주부터 시작한 것은 Clojure 팟캐스트 듣기! 물론 Clojure 관련 동영상 시청은 줄곧 하고 있는데, 팟캐스트도 듣기 시작했다. 걸으면서 동영상 보다보니 나중에 눈이 초점도 잘 안맞고 간혹 스크린캐스트의 글자들이 너무 작아서 넥서스6 풀스크린으로 봐도 잘 안보이는 등의 문제도 있어서 팟캐스트도 도전하기로 한 것. Clojure 뒤에 있는 Cognitect라는 곳에서 팟캐스트를 하고 있길래 그거 하나 듣고, 또 오늘은 MostlyLazy라는 팟캐스트를 하나 들었다. 역시 발표나 스크린캐스트에 비해서 초대손님 불러다가 자기네들끼리 떠들고 하는 팟캐스트는 영어의 장벽이 더 느껴졌다. ㅠ.ㅠ

그리고 마지막으로... 이렇게 블로깅까지 시작하게 된 것!

시작!