(loop (print (eval (read))))

;;닭집을 차리기 위한 여정

quil 가지고 놀기

클로져 브릿지 행사를 마치고

귀차니즘의 영향으로 분명 늦은 포스팅이지만 상당히 인상깊은 행사였다고 생각하며 꼭 블로그에 글 써야지 하고 생각했던 행사였다.
사람들이 정말 전통적인 소프트웨어 강의를 얼마나 익숙치 않아하고 반대로 눈에 보이는 그래픽-한 과제에는 흥미를 보이는구나.
생각해보면 이런 종류의 교육용 커리큘럼은 plt-racket에 이미 있었고 꽤 흥미로웠던 기억이 난다.
그런데 plt-racket에 있는 http2/image 패키지랑은 다르게 quil은 좀 더 낮은 api만 제공하므로 이 글을 쓰는 계기가 됨.

quil로 도형 그리기

1
lein new quil foo

정도의 명령어로 quil 라이브러리가 포함되고 실행만 하면 되는 얼개가 구현된 상태의 quil 프로젝트가 만들어진다.

기본적으로 생성되는 프로젝트는 사실 너무 복잡하므로 그냥 도형만 그려보는 것으로 수정..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(ns foo.core
(:require [quil.core :refer :all]
[quil.middleware :as m]))
(defn setup []
(frame-rate 30)
(color-mode :rgb))
(defn draw-state [state]
(background 255 255 255)
(fill 255 0 0) ;; red
(rect 75 75 50 50))
(defsketch foo
:title "foo"
:size [200 200]
:setup setup
:draw draw-state
:features [:keep-on-top]
:middleware [m/fun-mode])
red_rect.png

일단은 여기서 draw-state 함수만 고치면 뭔가를 그려볼 수 있는 상태가 됨.
라켓의 http2/image 패키지에는 여러 레이아웃 함수들이 있어 편리하다. 그래서 그런걸 만들어보려는 마음이 생겼다.

처음 만들어 볼 것은 여러 모양을 가로로 늘어놓는 모양을 만드는 beside를 만들어보자.
그럴려면 모든 모양들은 아직 그려지지 않은 상태에서 그려지기 위한 데이터를 가지고 있어야 하며, 또한 한 가지 원칙으로 그려져야 괜찮을 것 같다-예를들어 삼각형은 각 꼭지점의 위치를 인자로 줘야 하는데 사각형과 마찬가지로 그냥 좌-상단위치와 가로길이,세로길이 를 인자로 받도록 하는게 더 좋을듯.

그럼 그리기! 함수를 한번 생각해보면 그리기!는 어떤 모양을 그리든지 나머지는 모양이 가진 데이터에 기초해서 그리되 함께 넘겨받은 left 위치와 top 위치 인자를 가지고 그리도록 하는게 좋겠다.

1
2
3
4
5
6
7
(defn 그리기!
"좌-상단 위치를 기준으로 모양을 그린다."
[left top 모양]
(rect left
top
(:width 모양)
(:heigth 모양)))

그리고 다음과 같이 draw-state 함수를 수정하면

1
2
3
4
(defn draw-state [state]
(background 255 255 255)
(fill 255 0 0) ;; red
(그리기! 75 75 {:width 50 :heigth 50}))

이전과 동일하게 동작하게 된다.
그리기! 함수는 무조건 rect 를 그리게 구현되었다. 이는 우리가 원한게 아니므로 그리기! 함수가 어떤 모양인지를 따져보고 알맞은 함수를 호출하도록 바꿔보자. 이럴때 단순히 조건분기하는 ifcond를 쓸 수도 있지만 어떤 모양인지 따져보고 알맞은 함수를 호출한다는 것은 clojure 에서 지원하는 문법인 defmulti 를 쓰기에 알맞아 보인다. defmulti는 실제 함수 구현부인 defmethod 와 짝을 이루며 인자를 살펴보거나 따져보아서 어떤 값을 기준으로 method를 호출할지 결정할 수 있고, defmethod 에서는 어떤 값에 따라 호출될지 구현부 앞에 밝혀적음으로써 누구나 의도를 파악하기 쉽도록 할 수 있다.

하지만 한가지 문제가 있는데 defmulti 는 repl 이 다시 실행될때까지 덮어 써지지 않는다;;
수정하려면 뭔가 특별한 조치를 취하지 않는이상 ide 에서 프로젝트 커넥션등을 다시 실행하거나 해야한다는것..

1
2
3
4
5
6
7
8
9
10
11
12
(defmulti 그리기!
"좌-상단 위치를 기준으로 모양을 그린다."
(fn [_ _ 모양] ;; _ 로 관심없는 인자를 표시..
(:type 모양))) ;; 어떤 값을 기준으로 할지 골라내는 방법
(defmethod 그리기!
::rect ;; 골라낸 값이 일치하는 경우 실행될 메서드임을 밝힘.
[left top 모양]
(rect left
top
(:width 모양)
(:heigth 모양)))

여기서는 모양 맵의 :type 을 보고 ::rect 인 경우에만 사각형을 그리도록 함수가 구현되어 있다. 이는 모양 맵이 :type 이 있음을 가정하고 있으므로 draw-state 함수는 다음과 같이 고쳐야 한다.

1
2
3
4
5
6
(defn draw-state [state]
(background 255 255 255)
(fill 255 0 0) ;; red
(그리기! 75 75 {:type ::rect
:width 50
:heigth 50}))

어떤 모양을 그릴때 이 모양은 하나가 아닐 수 있다. 특히나 우리가 구현하려고 하는 beside는 각 모양들의 집합이다. 따라서 이 모양들의 좌-상단위치가 같아서는 안 된다. 이를 나타내는 offset모양맵에 넣도록 하자. 덤으로 색깔도..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
;; 그리기! 함수를 이렇게..
(defmethod 그리기!
::rect
[left top 모양]
(when (:stroke 모양)
(apply stroke (:stroke 모양))) ;; apply 를 썼다.
(when (:fill 모양)
(apply fill (:fill 모양))) ;; apply 를 썼다.
(rect (+ left (:offset-left 모양))
(+ top (:offset-top 모양))
(:width 모양)
(:heigth 모양)))
;; draw-state 함수를 이렇게...
(defn draw-state [state]
(background 255 255 255)
(그리기! 0 0 {:type ::rect
:fill [255 0 0]
:stroke [0 0 0]
:offset-left 75
:offset-top 75
:width 50
:heigth 50}))

strokefill 함수는 인자들을 여러개를 받아서 색을 결정한다. 따라서 우리는 데이터를 벡터 형으로 기록하도록 하고 나중에 함수를 실행할때 벡터를 여러 인자를 넣어 실행한 것처럼 호출 해야한다. 이때 apply 라는 함수를 쓸 수 있다. 또, 어떤 경우에만 실행한다 라는 의미로 when 을 쓸 수 있다.
그리기! 메서드가 너무 길어져서 번잡해졌다. 한번 개선해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(defmethod 그리기!
::rect
[left top 모양]
(let [{:keys [offset-left
offset-top
width
height
stroke
fill]} 모양]
(when stroke
(apply quil.core/stroke stroke))
(when fill
(apply quil.core/fill fill))
(rect (+ left offset-left)
(+ top offset-top)
width
height)))

let은 내부에 밝혀적은 식대로 destructuring(구조분해) 해서 각 심볼로 그 값을 쓸 수 있게 해준다. 여기서는 모양맵을 구조분해 해서 각 키워드에 해당하는 값을 그대로 심볼로 쓸 수 있게 한 것.
그런데 여기서 stroke는 quil.core/stroke 함수를 가려버리게 되므로 stroke가 quil의 함수임을 밝혀적었다.

이제 beside 를 만들어보자.

옆으로 늘어놓을때, 각각의 세로길이가 다른 모양을 늘어놓을 것이므로 이 모양들은 세로 기준 중앙 정렬 되어야 할 것이다.

그러므로 먼저 모양들 중 가장 긴 세로길이 를 구해야 하고,

1
(apply max (map :height 모양들)) ;; 모양들의 가장-긴-세로길이 를 구한다.

가장 긴 세로길이를 기준으로 offset-top 을 바꿔줘야 한다.

1
(quot (- 가장-긴-세로길이 height) 2) ;; 각 모양의 (가장-긴-세로길이 - 세로길이) / 2

또한 모든 모양들에 대해서 앞선 모양의 offset-left 에 현재 모양의 가로길이를 더한것이 다음번 모양의 offset-left 가 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(defn beside
"모양들을 옆으로 늘어놓는다. 이때 세로가 가장 긴 모양의 세로축 중심을 기준으로 중앙정렬한다."
[& 모양들]
(let [가장-긴-세로길이 (apply max (map :height 모양들))
자식요소 (loop [모양들 모양들
left 0
result []]
(if 모양들
(let [[이번-모양 & 남은-모양] 모양들
{:keys [width height]} 이번-모양
top (quot (- 가장-긴-세로길이 height) 2)
조정된-모양 (assoc 이번-모양
:offset-left left
:offset-top top)] ;; 중앙정렬
(recur 남은-모양
(+ left width) ;; 다음 모양은 현재 가로길이를 더한 값으로 offset-left 가 계산되도록 더해준다.
(conj result 조정된-모양)))
result))]
{:type ::grouped
:width (reduce + (map :width 모양들)) ;; 모양들의 가로길이 합이 합쳐진 모양의 가로길이가 된다.
:height 가장-긴-세로길이
:offset-left 0
:offset-top 0
:children 자식요소}))

& 모양들은 이 함수에 여러개의 인자를 넣어서 호출 할 수 있음을 밝히는 구문이다.
loop 는 초기값을 선언하고 몸체내의 식을 recur 로 반복할 수 있도록 만든 설탕문법이다. loop는 다른 함수나 letfn 등으로 바꿔 쓸 수 있다. assoc은 해당 키의 값을 덮어 쓰는데 사용한다.

즉, 모양들 을 순회하며 offset-leftoffset-top을 갱신하는 코드가 된다.
마지막으로 이 결과를 가지고 :type::grouped모양맵을 만들어낸다.

::grouped그리기!하는 method는 아직 없으므로 하나 만들어주자.

1
2
3
4
5
6
7
8
9
10
11
12
13
(defmethod 그리기!
::grouped
[left top 모양]
(let [{:keys [type
offset-left
offset-top
width
height
children]} 모양]
(doseq [child children]
(그리기! (+ offset-left left)
(+ offset-top top)
child))))

deseq 는 다른 플랫폼의 for-each 구문과 비슷하게 동작한다. child of children을 모두 그리기!하는 함수이되 ::grouped 모양도 결국은 offset 을 가지므로 이를 좌-상단 위치에 반영해준다.

이를 draw-state 함수를 고쳐 한번 테스트해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(defn draw-state [state]
(background 255 255 255)
(그리기! 25 25 (beside
{:type ::rect
:fill [255 0 0]
:stroke [0 0 0]
:offset-left 0
:offset-top 0
:width 25
:height 25}
{:type ::rect
:fill [255 255 255]
:stroke [0 0 0]
:offset-left 0
:offset-top 0
:width 25
:height 25}
{:type ::rect
:fill [255 0 0]
:stroke [0 0 0]
:offset-left 0
:offset-top 0
:width 25
:height 25})))
red_3_rect.png

clojure에는 cycle이라는 함수가 있어서 반복되는 값을 표현하기에 알맞다. 빨간색 사각형과 흰 사각형이 교대로 늘어서는 모양을 생각해보자. 이는 이렇게 표현할 수 있을 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(clojure.pprint/pprint
(apply beside
(take 10
(cycle
[{:type ::rect
:fill [255 0 0]
:stroke [0 0 0]
:offset-left 0
:offset-top 0
:width 50
:height 50}
{:type ::rect
:fill [255 0 0]
:stroke [0 0 0]
:offset-left 0
:offset-top 0
:width 50
:height 50}]))))

이를 repl 에서 실행시켜 보면 offset-left 차이나는 10개의 모양맵들이 보일 것이다.
clojure.pprint/pprint 함수는 데이터를 예쁘게 출력해주는 함수다.
draw-state를 고치면 실제로 그려지는 모양을 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(defn draw-state [state]
(background 255 255 255)
(그리기! 25 25
(apply beside
(take 10
(cycle
[{:type ::rect
:fill [255 0 0]
:stroke [0 0 0]
:offset-left 0
:offset-top 0
:width 15
:height 15}
{:type ::rect
:fill [255 255 255]
:stroke [0 0 0]
:offset-left 0
:offset-top 0
:width 15
:height 15}])))))

beside와 비슷한 above 함수를 만들어보자. 이 함수는 가로가 아닌 세로로 늘어놓는 함수다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(defn above
"모양들을 위아래로 늘어놓는다. 이때 가로가 가장 긴 모양의 가로축 중심을 기준으로 중앙정렬한다."
[& 모양들]
(let [가장-긴-가로길이 (apply max (map :width 모양들))
자식요소 (loop [모양들 모양들
top 0
result []]
(if 모양들
(let [[지금-모양 & 남은-모양] 모양들
{:keys [width height]} 지금-모양
left (quot (- 가장-긴-가로길이 width) 2)
조정된-모양 (assoc 지금-모양
:offset-left left
:offset-top top)] ;; 중앙정렬
(recur 남은-모양
(+ top height)
(conj result 조정된-모양)))
result))]
{:type ::grouped
:width 가장-긴-가로길이
:height (reduce + (map :height 모양들))
:offset-left 0
:offset-top 0
:children 자식요소}))

이제 이 모양들로 체스판을 만들어보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
(defn draw-state [state]
(background 255 255 255)
(그리기! 25 25
(apply above
(take 10
(cycle
[(apply beside
(take 10
(cycle
[{:type ::rect
:fill [255 0 0]
:stroke [0 0 0]
:offset-left 0
:offset-top 0
:width 15
:height 15}
{:type ::rect
:fill [255 255 255]
:stroke [0 0 0]
:offset-left 0
:offset-top 0
:width 15
:height 15}])))
(apply beside
(take 10
(cycle
[{:type ::rect
:fill [255 255 255]
:stroke [0 0 0]
:offset-left 0
:offset-top 0
:width 15
:height 15}
{:type ::rect
:fill [255 0 0]
:stroke [0 0 0]
:offset-left 0
:offset-top 0
:width 15
:height 15}])))])))))
red_chess.png

일단 ::grouped 모양을 만들면 이 모양을 그리기!하는 함수는 이미 있으므로 이 모양을 다시 cycle을 하든 그걸 다시 above로 묶던 내부적으로 이를 그리는 방법은 신경 쓸 필요가 없다.

이제 이 함수를 가지고 plt-racket의 홈페이지 대문 예제 중 하나인 시어핀스키의 삼각형을 그려보자.

먼저 삼각형을 그리기!하는 함수를 만들어보면,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(defmethod 그리기!
::triangle
[left top 모양]
(let [{:keys [offset-left
offset-top
width
height
stroke
fill]} 모양]
(when stroke
(apply quil.core/stroke stroke))
(when (:fill 모양)
(apply quil.core/fill fill))
(let [left (+ left offset-left)
top (+ top offset-top)]
(triangle (+ left (quot width 2)) top
left (+ top height)
(+ left width) (+ top height)))))

이렇게 하면 사실 정삼각형을 그리기 힘들다 (가로 세로만 입력가능하니까..)

또, 시어핀스키의 삼각형을 만드는 함수를 만들어보자. above, beside 면 간단히 만들 수 있다.

1
2
3
4
5
6
7
8
(defn sierpinski [n]
(if (zero? n)
{:type ::triangle
:width 10
:height 8.66}
(let [result (sierpinski (dec n))]
(above result
(beside result result)))))

draw-state를 고쳐서 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
(defn draw-state [state]
(background 255 255 255)
(no-stroke)
(그리기! 40 40 (sierpinski 5)))
;; 윈도우 사이즈를 400으로 늘림.
(defsketch foo
:title "foo"
:size [400 400]
:setup setup
:draw draw-state
:features [:keep-on-top]
:middleware [m/fun-mode])
sierpinski.png

구조분해

클로져 시작하기 책을 보다가

번역이 구조분해라구 된 Destructuring 를 같이 토의도 해보고 했다. 이거 꽤 많이 씀.

전형적인 되도는 프로세스를 만드는 함수를 짜보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
;; 구조분해 없이
(defn foldr [f init coll]
(if (empty? coll)
init
(f (first coll)
(foldr f init (rest coll)))))
;; 구조분해 써서
(defn foldr* [f init coll]
(if (empty? coll)
init
(let [[h & t] coll]
(f h (foldr f init t)))))

reduce 함수와 조금 다른 옛 이름의 foldr 함수를 구현해 보았다. (let [[h & t] coll] ... 부분이 구조 분해가 일어나는 부분.

기본적으로는 패턴매칭을 이용하고 있으므러 다른 언어로도 구현해보자

스킴의 하나인 racket은 정신나간 매크로확장을 많이 하는 동네라서 찾아보면 온갖 매크로가 다 튀어나온다.

스킴으로는 클로져의 경우와 반대로 이름이 reducefoldl 을 구현해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
;; #lang racket
;; 걍 안쓰고 했을때
(define (reduce f init coll)
(if (null? coll)
init
(f (reduce f init (cdr coll))
(car coll))))
;; match-let 매크로 사용
(define (reduce* f init coll)
(if (null? coll)
init
(match-let ([(list h t ...) coll])
(f (reduce* f init t) h))))
;; match 매크로사용
(define (reduce** f init coll)
(match coll
['() init]
[(list h t ...)
(f (reduce** f init t) h)]))
;; define/match 매크로 사용 - 함수구현부 패턴매칭
(define/match (reduce*** f init coll)
[(f init '()) init]
[(f init (list h t ...))
(f (reduce*** f init t) h)])

결국 함수선언부 패턴매칭까지 오게 되는데 라켓에는 뭔가 뭘 좋아할지 몰라서 다 준비했다는 식으러 문법확장이 너무 지나치게 많이 일어나는 것 같은 감이 있다.

덤으러 이런건 자바스크립트에서도 된다.

1
2
3
4
5
6
7
8
function foldr(f, init, coll) {
if( coll.length == 0 ) {
return init;
} else {
let [h, ...t] = coll;
return f( h, foldr(f, init, t));
}
}

생각해보면 이런거 하기 가장 좋은 플랫폼은 하스켈 아닌가?

자바스크립트로 옛날문법으로 OOP 해보기

자바스크립트로 OOP 하기

자바스크립트로 OOP 스타일의 코드를 만드는것은 다양한 방법이 있고, 또 안 건드는것도 좋은 방법이기는 한데,
여기서는 한 가지 방법으로 작성하여 데이터그리드 라는 것을 만들어 보기로 하자.
자바스크립트의 이전 OOP 스타일의 각종 기법들은 ECMA 표준 버전이 올라감에 따라 사라질 운명이라 아예 시작을 class 문법으로 시작하는것도 좋아보이지만
일단 javascript 에 익숙하고 백엔드에 대한 경험이 없는 사람들이 class 구문 없이 OOP 스타일로 코드를 작성해보며 얻는 경험을 목적임.

Component 클래스의 작성

자, 어디서부터 시작해볼까? Animal - Cat 으로 이어지는 많이 알려진 OOP 스타일 코드의 예제부터 시작해 볼까?
그러지 말고 여기서는 약간 더 재미를 더해 유명한 프레임워크인 React 의 Component 클래스를 흉내내 보는것으로 한다. 물론 React 프레임워크를 쓰는게 훨씬 좋겠지만 배우는게 목적이므로.

1
2
3
function Component(state) {
this.state = state;
}

간단한 Component 클래스가 만들어졌다. 이제 이 컴포넌트를 상속받아 가장 그리기 쉬운 Cell 이라는 클래스를 만들어보자. 이 클래스는 최종적으로는 <td></td> 를 그리는 클래스이다.

1
2
3
4
5
6
7
function Cell(state) {
Component.call(this, state);
}
Cell.prototype = new Component();
Cell.prototype.render = function () {
return $('<td></td>')
};

이로써 컴포넌트를 상속받은 Cell 클래스가 만들어졌다. 이 클래스는 render 메서드가 호출될 때 jquery 오브젝트를 생성하여 돌려준다.
이번에는 이 클래스를 자식요소로 갖는 Row 클래스를 만들어보자.

1
2
3
4
5
6
7
8
9
10
function Row(state) {
Component.call(this, state);
this.cells = _.map(state.items, function(item){
return new Cell(item);
});
}
Row.prototype = new Component();
Row.prototype.render = function () {
return $('<tr></tr>');
};

이로써 컴포넌트를 상속받은 Row 클래스가 만들어졌다. 이 클래스는 Cell 클래스와 마찬가지로 render 메서드가 호출될 때 jquery 오브젝트를 생성하여 돌려준다.
Row 클래스와 Cell 클래스에 선언된 render 메서드를 잘 생각해보면 이러한 컴포넌트가 당연히 가져야될 메서드임이 자명하다. 따라서 이 메서드는 Component 클래스에서 쓰일 수 있도록 Component 클래스에서도 render 메서드를 구현해 놓을 수 있다.

1
2
3
Component.prototype.render = function () {
throw "렌더 메서드를 구현해 주세요."
};

Row 생성자에는 주어진 state 변수로부터 Cell의 인스턴스를 얻어내어 프로퍼티로 가지게끔 하였다.
이번에는 이 두 클래스의 render 함수를 호출하여 지정된 부모 jquery 오브젝트에 얹히는 (mount) 메서드를 구현해 보자. 이 메서드가 호출되기 전에 컴포넌트는 미리 render에 의해 생성되는 jquery 오브젝트가 얹혀질 부모 오브젝트를 가지고 있어야 한다.

1
2
3
4
5
6
7
/**
* 이 컴포넌트가 마운트될 부모 DOM 요소를 지정한다.
* @param $parentNode
*/
Component.prototype.setParentNode = function ($parentNode) {
this.$parentNode = $parentNode;
};

이러면 Component 를 상속받은 CellRow 가 모두 setParentNode 라는 메서드를 가지게 된다.
이어서 setParentNode 를 이용해서 mount 메서드를 구현해보면,

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 컴포넌트를 마운트한다 (위치시킨다).
*/
Row.prototype.mount = function () {
if (!this.$parentNode) {
throw '부모 노드가 정의되지 않아서 마운트할 수 없습니다.'
}
var $el = this.render();
var $parentNode = this.$parentNode;
this.$el = $el;
$parentNode.append($el);
};

여기까지 작성하고 새로 Row 클래스의 인스턴스를 만들어서 mount 해 보자. 크롬 개발자도구 콘솔을 열어서 다음과 같이 입력하면,

1
2
3
var row = new Row({items: [1,2,3,4]});
row.setParentNode($(document.body));
row.mount();

document.body 영역에 tr 태그가 삽입된 것이 확인된다. 여기에 Row의 자식요소인 cells 는 어떻게 마운트를 할까? 우리가 이미 구현한 mount 메서드에서 cells 를 순회하며 mount 메서드를 호출하면 될 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 컴포넌트를 마운트한다 (위치시킨다).
*/
Component.prototype.mount = function () {
if (!this.$parentNode) {
throw '부모 노드가 정의되지 않아서 마운트할 수 없습니다.'
}
// 이 순간 렌더링.
var $el = this.render();
var $parentNode = this.$parentNode;
this.$el = $el;
$parentNode.append($el);
// 자식컴포넌트들도 마운트한다.
_.each(this.children, function (child) {
// 마운트된 이 컴포넌트의 DOM 요소를 자식 컴포넌트가 렌더링 될 부모 DOM 요소로 지정한다.
child.setParentNode($el);
child.mount();
});
};

반대 메서드인 unmount 메서드도 구현할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 컴포넌트를 언마운트한다 (제거한다).
*/
Component.prototype.unmount = function () {
// 자식 컴포넌트들 부터 언마운트 한다.
_.each(this.children, function (child) {
child.unmount();
});
this.children = undefined;
if(this.$el) {
this.$el.remove(); // 날리고
this.$el = undefined; // 없애고
}
};

그런데 이상의 코드에서 this.children 이라는 자식요소도 반복되는 요소이므로 Component 의 메서드로 정의될 필요가 있어보인다.

1
2
3
4
5
6
7
8
/**
* 자식 요소들을 state 로 부터 다시 생성한다.
* @param state
* @returns {*}
*/
Component.prototype.refreshChildren = function (state) {
return [];
};

최종적으로 mount 메서드는 다음과 같이 변경하면 state 에 따라 자식요소를 업데이트하고 다시 마운트하게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 컴포넌트를 마운트한다 (위치시킨다).
*/
Component.prototype.mount = function () {
if (!this.$parentNode) {
throw '부모 노드가 정의되지 않아서 마운트할 수 없습니다.'
}
this.children = this.refreshChildren(this.state); // 여기
// 이 순간 렌더링.
var $el = this.render();
var $parentNode = this.$parentNode;
this.$el = $el;
// 자식컴포넌트들도 마운트한다.
$parentNode.append($el);
_.each(this.children, function (child) {
// 마운트된 이 컴포넌트의 DOM 요소를 자식 컴포넌트가 렌더링 될 부모 DOM 요소로 지정한다.
child.setParentNode($el);
child.mount();
});
};

이제 Row 클래스에 refreshChildren 메서드를 오버라이드하자.

1
2
3
4
5
6
7
8
9
10
Row.prototype.refreshChildren = function (state) {
return _.map(this.state.columns, function (column) {
return new Cell({
key: column.key,
value: this.getCellValueByKeys(column.key, state.rowData),
rowData: this.state.rowData,
labelFunction: column.labelFunction
});
}, this);
};

그리고 Row 클래스의 생성자에서는 자식요소를 빼버리자.

1
2
3
4
5
6
7
8
/**
* 데이터그리드의 행.
* @param state
* @constructor
*/
function Row(state) {
Component.call(this, state);
}

급귀찮다…..

루프의 컨벤션

SICP 2장을 죽 읽다보면 map을 이용한 리스트 조작이 나오고, 또 이를 재귀적으로 적용하여 우리가 흔히 얘기하는 이중 루프 (혹은 겹친 루프) 를 만들어 내는 예제나 연습문제가 많이 나온다.

은근하게도, 하나의 컨벤션이 제시되는데 바로 flatmap 이다.

아래는 바로 대표적인 예를 보여주는 연습문제 n-queens problem 이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(define (n-queens n)
(define (iter m)
(if (zero? m)
'(())
(filter (λ (queens)
(let* ([current-queen (car queens)]
[rest-queens (cdr queens)]
[indexed-rest-queens (map cons rest-queens (range 1 (length queens)))])
(not
(ormap (λ (indexed-queen)
(ormap (λ (current-queen-attack-range)
(= (car indexed-queen) current-queen-attack-range))
(list current-queen
(+ current-queen (cdr indexed-queen)) ;; current-queen 과의 거리 +
(- current-queen (cdr indexed-queen))))) ;; current-queen 과의 거리 -
indexed-rest-queens))))
(append-map (λ (result)
(map (λ (current)
(cons current result))
(range 1 (add1 n))))
(iter (sub1 m))))))
(iter n))

plt-racket 에서의 flatmapappend-map이다. 참고로, clojure 에서는 mapcat

처음 SICP 를 볼때는 아무 생각없이 flatmap 을 썼었는데, 사실 이 flatmap 이 이런 쓰임새로 활용될 때에는 머릿속에 잘 그려지지 않는다고 생각한다. 해서, flatmap 의 동작을 설명하는 코드를 짜 보았다.

먼저 flatmapapply-append-map 과 같다. 위에서 append-map 으로 시작하는 코드를 다음과 같이 바꿔도 동작할 것이다.

1
2
3
4
5
6
7
(apply append
(map
(λ (result)
(map (λ (current)
(cons current result))
(range 1 (add1 n))))
(iter (sub1 m))))

이는 사실 모든 원소에 함수롤 적용한 결과로부터 얻은 리스트들을 다시 하나의 리스트들로 모은다는 뜻이다. 따라서 reduce-by-append 와 같다.

1
2
3
4
5
6
7
8
(foldr append
'()
(map
(λ (result)
(map (λ (current)
(cons current result))
(range 1 (add1 n))))
(iter (sub1 m))))

이렇게 해놓고 나니, flatmap 이 어떻게 동작할 지, 머릿속에 분명하게 그려진다. 다시 flatmap 을 써야지.

요즘 SICP 스터디를 다시 하면서 요런코드들을 자바나 자바스크립트로 써보고 있는데, 특히 자바의 경우는 java8 이후로 람다와 스트림을 지원해서 어느정도 용이하게 작성하는게 가능해졌다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class Main {
public static void main(String[] args) {
System.out.println(nQueens(5));
}
public static List<List<Integer>> nQueens(Integer n) {
return nQueens(n, n);
}
public static List<List<Integer>> nQueens(Integer n, Integer m) {
if (m.equals(0)) {
List<List<Integer>> lst = new LinkedList<>();
lst.add(new LinkedList<>());
return lst;
} else {
return nQueens(n, m - 1).stream()
.flatMap(result ->
IntStream.range(1, n + 1)
.boxed()
.map(current -> {
List<Integer> lst = new LinkedList<>(result);
lst.add(0, current);
return lst;
})
)
.filter(queens -> {
int currentQueen = queens.get(0);
boolean isSafe = true;
for (int i = 1; i < queens.size(); i++) {
Integer queen = queens.get(i);
if (currentQueen == queen ||
currentQueen + i == queen ||
currentQueen - i == queen)
isSafe = false;
}
return isSafe;
})
.collect(Collectors.toList());
}
}
}

요즘엔 자바를 쓰면서도 그다지 불편하지 않아서 참 다행이라는 생각이 든다.
하지만 점점 돈은 안되는 느낌이다. 슬프다..

Schema , SqlKorma 그리고 defmulti

타입이 쓸만한가?

예전에 c로 struct 같은 키워드로 타입을 구현하던 기억을 떠올려보자. 그때의 타입은 메모리의 위치를 참조하기 편하게 하기 위해 타입을 썼던것 같다.

뭐, 그때는 메모리는 당연히 고정되어야 하는것이고 원시타입의 크기에 따라 크기를 가늠할 수 있었기 때문이라고 생각.

그렇기 때문에 요즘에는, 그러니까 데이터의 사이즈가 막 변하고, 어떻게 조합될지도 예측하기 어려운 상황에서 그런용도로 타입을 쓰는 사람은 드물지 않을까?

오히려 다음 두 가지 이유때문에 타입을 쓰는 사람이 많을 거라 생각한다.

  • 문서화
  • 멤버변수의 모양새 제한

그렇다면, 동일한 가치를 제공하는 다른 개념이 있으면 타입은 안써도 되지 않을까?

schema

https://github.com/plumatic/schema

스키마의 핵심적인 역할을 하는 함수는 validate 이다. 요걸로 확인하고자 하는 값이 미리 정의한 스키마에 부합하는지 판별한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(def Data
"A schema for a nested data type"
{:a {:b s/Str
:c s/Int}
:d [{:e s/Keyword
:f [s/Num]}]})
(s/validate
Data
{:a {:b 123
:c "ABC"}})
;; Exception -- Value does not match schema:
;; {:a {:b (not (instance? java.lang.String 123)),
;; :c (not (integer? "ABC"))},
;; :d missing-required-key}

공홈의 저 예제를 보면 저 스키마의 형태가 상당히 직관적임을 알 수 있다. 물론 함수를 정의하면서 인자의 스키마를 지정할 수도 있고, 해당 함수 실행시에 인자가 스키마를 따르는지 확인 할 수도 있다.

그러나 매번 validate 를 하기엔 귀찮으므로 이러한 스키마를 지켜야 하는 namespace 안에서는 기본 clojure 기능들을 override 해버리자.

1
2
3
4
5
6
(ns korma-run.core
(:refer-clojure :exclude [atom fn defn defmethod letfn defrecord])
(:require [korma.core :refer :all]
[schema-tools.core :as st]
[medley.core :as md]
[schema.core :refer :all]))

그리고 나서 전역 설정을 먹이면 매 함수 호출마다 스키마를 검사하게 된다.

1
(set-fn-validation! true)

이제 이것과 SqlKorma 를 엮으면 데이터베이스 조회시 조금은 더 신뢰할 수 있는 결과가 나오겠지?

1
2
3
4
5
6
7
8
9
10
11
12
(defentity discounts (table :discount))
(defschema Discount
{(optional-key :id) Num
(required-key :type) Str
(required-key :sale?) Bool
(required-key :amount) Num})
(defentity items (table :item))
(defschema Item
{(optional-key :id) Num
(required-key :price) Num
(required-key :name) Str})

이제 defmulti 를 써서 자바에서 주로 내 밥벌이로 쓰던 mybatisdiscriminator 같은 기능을 훨씬 쉽고 더 좋게 쓸 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(defmulti promote
(fn
[_
discount :- Discount]
(:type discount)))
(defmethod promote
"fixed"
[item :- Item
discount :- Discount]
(- (:price item)
(:amount discount)))
(defmethod promote
"rate"
[item :- Item
discount :- Discount]
(* (:price item)
(- 1 (:amount discount))))

회사걱정

입사한지 약 3개월쯤 안되어 받은 설문 조사에서 답변한 글.

갑작스레 이런 메일을 받게되어 놀랐습니다;

입사한지 얼마 안되어 이런 질문에 답변을 드리는것이 외람되다고 여겨집니다.
때문에 짧은의견, 사실과 거리가 먼 소견을 말하게 되더라도 양해 부탁드립니다..

사실 저는 불만인 점을 몇몇 이유를 들어 말씀드리고 싶습니다.

첫째는 회사의 분위기가 기술중심의 회사로는 사실 느껴지지 않습니다.
직급이 없다고하나 팀의 프로젝트를 사실상 정하는 사람들이 있고 이 사람들의 기술적 고려가 부족한듯 합니다.

둘째는 프로젝트 팀 그 자체입니다.
좋은 팀을 만드는 것의 가치가 훼손되고 있다고 느껴집니다.
프로젝트 팀의 구성이 바뀌거나 혹은 구성원이 원하지 않는 팀 구성, 프로젝트 진행중 간섭 등이 없었으면 좋겠습니다..

그리고 마지막으로,
XX 프로젝트의 기안 및 추진하는 과정에서 의사결정의 실패가 있었다고 생각하고 있습니다.
제가 불만을 가질만한 영역은 아닙니다만 회사가 잘못된 의사결정을 하게되면 결국 다른 모든 영역들로 영향을 미칠 것이므로..
걱정스럽게 생각하고 있습니다..

이상은 제가 입사전의 내역을 몰라서 적은 글일수도 있습니다. 그렇다면 바로잡아 주시면 감사하겠습니다.

기한에 늦은데다가 또 늦은 새벽에 메일을보내 죄송합니다 ㅎ;; 이만 줄이겠습니다.

Spring aop example

guice가 참 좋은데.. 정말 좋은데

이런 어노테이션이 있다고 하자.

1
2
3
4
5
6
7
8
9
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Authorized {
Type to();
enum Type {
PUBLIC, ADMIN
}
}

이런 쥬스 모듈에 의해 바인딩되고,

1
2
3
4
5
6
7
8
public class AuthorizeModule extends AbstractModule {
@Override
protected void configure() {
AuthorizeInterceptor authorizeInterceptor = new AuthorizeInterceptor();
requestInjection(authorizeInterceptor);
bindInterceptor(Matchers.inSubpackage("com.foo.example"), Matchers.any(), authorizeInterceptor);
}
}

이런 인터셉터를 호출한다 ㅎㅎ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AuthorizeInterceptor implements MethodInterceptor {
private static final Logger logger = LoggerFactory.getLogger(AuthorizeInterceptor.class);
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Authorized authorized = invocation.getMethod().getAnnotation(Authorized.class);
if(authorized != null && authorized.to() == Authorized.Type.PUBLIC)
return invocation.proceed();
// FIXME: authorize! and delete below line.
logger.info("authorizing . . . ");
return invocation.proceed();
}
}

놀랍게도, 이게 다다. 아오.. 너무 좋다..

하지만 쥬스는 돈이 안 된다. 따라서 이걸 스프링으로 해 보자.

스프링에서의 aop

먼저 advice 를 만든다.

1
2
3
4
5
6
7
8
public class FooAdvice implements MethodBeforeAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(FooAdvice.class);
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
LOGGER.warn("Capitalism, ho!");
}
}

그 다음 pointcut 을 만든다.

1
2
3
4
5
6
7
8
9
@Bean
Pointcut pointcut(){
return new ComposablePointcut().union(
clazz -> clazz.getPackage().getName().equals("com.foo.example.controller") &&
clazz.getAnnotation(Controller.class) != null
).intersection(
new AnnotationMethodMatcher(FooAnnotation.class)
);
}

위에서 ComposablePointcut 이 쥬스에서 하던 바인딩과 유사하여 꽤나 쓸만하다. jdk8 과 만나 드디어 사람이 읽을 수 있는 표현이 되었다!!

다음 pointcut advisor 를 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Bean
PointcutAdvisor pointcutAdvisor(){
return new DefaultPointcutAdvisor(pointcut(),advice());
}
```
위에서 만든 `pointcut` , `adivce` , `pointcut advisor` 는 반드시 다음 `DefaultAdvisorAutoProxyCreator` 를 통해야만 프록시가 생성된다.
```java
@Bean
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}

이렇게 하면 쥬스로 했던거랑 딱히 다를것도 없다. 우왕!

클로져 + 스마트폰(2)

clojure + 스마트폰 = <3

lein 설정및 noir 웹 프로젝트 생성하기

~/.lein/profiles.clj 를 편집한다.

1
2
3
4
{:user {:plugins [[cider/cider-nrepl "0.10.0-SNAPSHOT"]
[lein-localrepo "0.5.2"]
[noir "1.3.0"]]
:dependencies [[org.clojure/tools.nrepl "0.2.7"]]}}

다음 프로젝트 폴더 (예를들어 ~/Project ) 에 방문하여 다음을 입력하여 noir 프로젝트를 생성한다.

1
$ lein new noir foo-web

디펜던시들을 다운받고 다음의 메시지가 출력되며 프로젝트가 생성된다.

1
Generating a lovely new Noir project named foo-web...

emacs 로 다음의 파일을 연다.

1
emacs ~/Project/foo-web/project.clj

:dependenciesclojure 정도만 1.7.0 정도로 바꿔준다..

C-c, M-jclojure-jack-in 함수를 호출한다. 좀 오래 기다리면 REPL 버퍼가 열린다.

1
2
CIDER 0.10.0snapshot (package: 20150901.1605) (Java 1.8.0_45, Clojure 1.7.0, nREPL 0.2.7)
foo-web.server>

C-c, C-z 를 눌러 project.clj 버퍼가 열리도록 한 뒤에,

C-x, C-f 를 눌러 ~/Project/foo-web/src/foo_web/server.clj 파일을 연다.

C-c, C-k 를 눌러 컴파일한다.

C-c, C-z 를 눌러 REPL 버퍼로 다시 이동한다.

1
2
3
4
5
6
foo-web.server> (def srv (-main))
Starting server...
Server started on port [8080].
You can view the site at http://localhost:8080
#'foo-web.server/srv
foo-web.server>

이것으로 최초로 서버를 기동했다. aws ip 에 8080 포트로 접근해보자.

웹 페이지 만들기

src/foo_web/views/welcome.clj 파일을 연다.

C-c, M-n 를 눌러서 REPL 버퍼의 namespace 를 현재 열린 버퍼의 namespace 로 전환한다.

1
2
3
4
5
6
7
8
(ns foo-web.views.welcome
(:require [foo-web.views.common :as common]
[noir.content.getting-started])
(:use [noir.core :only [defpage]]))
(defpage "/welcome" []
(common/layout
[:p "Welcome to foo-web"]))

defpage 매크로로 선언된 부분을 보면 이후 따라오는 주소로 다음과 같은 html 을 렌더링했음을 알 수 있다.

1
<p>Welcome to foo-web</p>

html5 매크로에 의해 [:p]hiccup 이라는 라이브러리의 hiccup.core/html 매크로를 호출하여 html로 치환된다. 다음의 표현이 가능하다

1
2
3
4
5
6
7
8
9
10
foo-web.views.welcome> (println (hiccup.core/html [:h1.foo-class "bar"]))
;; <h1 class="foo-class">bar</h1>
;; => nil
foo-web.views.welcome> (println (hiccup.core/html [:h1#an-id.foo-class "bar"]))
;; <h1 class="foo-class" id="an-id">bar</h1>
;; => nil
foo-web.views.welcome> (println (hiccup.core/html [:div [:h1#an-id.foo-class "bar"]]))
;; <div><h1 class="foo-class" id="an-id">bar</h1></div>
;; => nil
foo-web.views.welcome>

다음과 같은 페이지를 만들어 보자.

1
2
3
4
5
6
(defpage "/foo" []
(common/layout
[:div.wrapper
[:input {:type "text"}]
[:button "OK"]
]))

common/layout 이란 매크로를 주목하자. 여기에 커서를 위치시킨뒤 M-. 를 이용하여 이동할 수 있다.

defpartial 매크로는 결국 hiccup 매크로로 작성된 블럭들을 조립할 수 있음을 알 수 있다.

따라서 좀더 활용가능하도록 바꿔보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(defpartial layout [ & {:keys [style
script
header
content
footer]}]
(html5
[:head
[:title "Macadamia!"]
`(~@script)]
[:body
[:div#header `(~@header)]
[:div#wrapper
[:div#content `(~@content)]]
[:div#footer `(~@footer)]
]))

foo 페이지는 다음과 같이 변경한다.

1
2
3
4
5
6
7
(defpage "/foo" []
(common/layout
:content
[[:div.wrapper
[:input {:type "text"}]
[:button "OK"]
]]))

이렇게 해서 템플릿 레이아웃을 쓸 수 있게 되었다.

이제 이 템플릿 레이아웃에 bootstrap 을 적용하도록 하자.

css,js 등 정적파일은 resources/public/ 에 추가하면 된다. 여기서는 그냥 cdn을 이용하는것으로.

common.clj:use 함수에 include-js 를 추가하고 layout 을 수정하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(ns my-website.views.common
(:use [noir.core :only [defpartial]]
[hiccup.page :only [include-css include-js html5]]))
(defpartial layout [ & {:keys [style
script
header
content
footer]}]
(html5
[:head
[:title "Macadamia!"]
(include-css "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css")
(include-js "//code.jquery.com/jquery-1.11.3.min.js")
`(~@script)]
[:body
[:div#header `(~@header)]
[:div#wrapper.container {:style "display: table; height: 100vh;"}
[:div#content {:style "display: table-cell; vertical-align: middle;"} `(~@content)]]
[:div#footer `(~@footer)]
(include-js "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js")
]))

자 이제 bootstrap 으로 스타일링을 하자.

1
2
3
4
5
6
7
8
9
(defpage "/foo" []
(common/layout
:content
[[:div.col-md-12
[:div.row.col-md-6.col-md-offset-3
[:div.input-group
[:input.form-control {:type "text"}]
[:span.input-group-btn
[:button.btn.btn-primary "OK"]]]]]]))

잘 나온다. ㅎㅎ

클로져 + 스마트폰(1)

clojure + 스마트폰 = <3

보기전에

스스로를 생각하면 나는 웹사이트나 통신서버등 고만고만한 걸 개발하며 먹고살고는 있지만 개발자는 아니지 않나 하고 생각하게 됨.

애초에 전공도 아니고 로우레벨에 (회로나 어셈같은) 대한 지식도 전무하며 무엇보다 주위 개발자들이 좋아하는 서버응답속도 개선이나 나 코드 줄여쓰기 같은 욕구가 거의 없음..

따라서 이 글도 장난감만들기라는 본연의 목적에만 충실할 뿐 근저에 깔린 사상이나 작동원리는 나도 모르거나 생략할 거라는..

또한 이 장난감은 1년쯤 전부터 해오던거라 지금의 트렌드와는 많이 다른 legacy 들이 많이 나오므로 진지한 개발자는 안 읽으시는것이 좋겠다. 실제로 좋은말을 들어본적이 없다.

차라리 개발을 전혀 모르는 사람이 보는것이 더 좋겠지않나..

AWS 에 가입하기

https://aws.amazon.com 열루 가서…
aws_join.png

개발자라면 AWS 계정이 없는것이 이상할정도로
거의 무제한 공짜(1년간 마이크로 인스턴스 무료. 그러나 메일계정만 있으면 계속 생성가능)
이지만 그래도 아직 가입하지 않았다면 가입하는것이 좋다.

가입중 신용카드 정보를 입력하는 부분이 등장한다. 외화결제가 가능해야 하므로 준비해놓아야 한다.
aws_join2.png

가입하고 tokyo 리젼 콘솔로 접근해서 ec 인스턴스를 생성하고 pem 파일을 다운로드 받아 놓는다. 이때 free tier eligible을 선택해야 공짜가 됨. 공짜 조건은 micro 급 ec 인스턴스 한개이므로 다른 스토리지, rds, 로드밸런서 따위를 선택해서도 안된다.

aws_main.png aws_ec_launch_create.png aws_ec_launch_ami.png aws_ec_launch_instance_type.png aws_ec_launch_tag.png aws_ec_launch_security.png aws_ec_launch_done.png

해당키로 ssh로 접근해보면 잘 된다. ㅎㅎ

스마트폰 용 ssh 클라이언트 다운받기

나는 https://serverauditor.com 라는 앱을 씀.

다운받고 설정을 해야한다.

server_auditor_menu.png server_auditor_add_key.png server_auditor_add_key_2.png server_auditor_add_key_3.png server_auditor_add_identity.png server_auditor_add_host.png server_auditor_done.png

잘된다. ㅎㅎ

필요한 환경 구축하기

먼저 zsh 를 설치하자.

1
2
3
4
5
sudo apt-get update
sudo apt-get install git
sudo apt-get install zsh
sudo curl -L http://install.ohmyz.sh | sh
sudo chsh -s $(which zsh) ubuntu

중간에 zsh 이 에러를 내는데 걍 쉘 변경하다 나는 에러니까 무시하고 다음 라인 입력하면 된다.

tmux 로 세션을 만들어 둔다. 이렇게 만든 세션은 항상 살려둔 채로 끄지 않을 예정이다.

C-b stmux세션을 선택할 수 있고 C-b $로 이름을 바꿀수 있다. clojure 정도로 이름을 바꾸자. 세션 하나를 더 추가하여 관리용으로 써도 좋다.

자바와 이맥스를 설치한다.

1
2
3
4
sudo add-apt-repository ppa:openjdk-r/ppa
sudo apt-get update
sudo apt-get install openjdk-8-jdk
sudo apt-get install emacs

lein 을 설치한다.

1
2
3
4
wget https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein
sudo chmod 755 lein
./lein
cp lein ~/bin

$PATH 에 위치해야 하므로 .zshrc 파일을 편집하여 ~/bin$PATH에 포함되도록 한다.

https://melpa.org/#/getting-started 여기에 방문해서 제시하는 내용을 ~/.emacs.d/init.el 파일을 생성하고 붙여넣는다.

emacs를 실행하자.

M-xpackage-list-packages를 입력하여 필요한 패키지를 다운받는다.

  • clojure-mode
  • cider
  • auto-complete
  • ac-cider
  • golden-ratio

해당 패키지 로 커서를 옮겨서(C-p, C-n, C-v, M-v) i 키로 선택 x키를 눌러 설치한다.

init.el 파일에 ac-cider 관련 설정을 추가해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(require 'ac-cider)
(add-hook 'cider-mode-hook 'ac-flyspell-workaround)
(add-hook 'cider-mode-hook 'ac-cider-setup)
(add-hook 'cider-repl-mode-hook 'ac-cider-setup)
(eval-after-load "auto-complete"
'(progn
(add-to-list 'ac-modes 'cider-mode)
(add-to-list 'ac-modes 'cider-repl-mode)))
(defun set-auto-complete-as-completion-at-point-function ()
(setq completion-at-point-functions '(auto-complete)))
(add-hook 'auto-complete-mode-hook 'set-auto-complete-as-completion-at-point-function)
(add-hook 'cider-mode-hook 'set-auto-complete-as-completion-at-point-function)

왜 프로젝트가 망할까?

빙 둘러 말하기로 프로젝트 위키에다가 써 보았다.
물론 프로젝트 위키에다가 썼으므로 팀원만 보겠지 ㅎㅎ…


스크럼 이걸 왜 하나요?

원활한 개발을 방해하는 요소들은 셀 수 없이 많습니다. 하지만, 그런 방해 요소들을 적극적으로 해결하고자 하는 노력은 그에 비해 턱없이 부족합니다. 늦어지는 기획, 너무 잦은 회의, 본부장의 잦은 간섭, 협업 요청에 대한 동료의 무응답, 작업을 더디게하는 수많은 요소들은 오늘도 우리 주위에 산재해 있습니다. 하지만, 이런 것들에 문제를 제기하면 왠지 분위기가 어색해질 것 같고 눈치가 보여서 포기하고 맙니다. 결국 시간이 갈수록 즐겁게 개발 할 수 있는 환경은 사라져갑니다. 스크럼은 목표를 달성하는데 방해가 되는 요소들을 그 즉시 해결해야 한다고 이야기합니다.

  • http://resoneit.blogspot.kr/2012/10/blog-post_30.html
  • 현재 상황에서 우리 mcc-backoffice 개발팀은 온전한 프로젝트 매니징을 기대할 수 없는 상황 (내가 대충 땜빵하고 있지만.. @yh.jang )
  • 또한 언제나 그렇듯이 깊은 이해가 없는 방법론 도입은 그다지 효과가 없다.
  • 따라서 앞서 인용한 개발저해요소 를 제거하는 데 촛점을 맞추고 나머지를 천천히 도입해야 할 것.
    • 프로젝트 팀이 아닌이상 프로젝트에 관여할 수 없어야 한다.
      • 누구라도 프로젝트에 관여하려면, 팀의 일원이 되어야 한다.
      • 팀의 일원이 되었으면, 협업 방법을 익히고 커뮤니케이션 하라.
      • 협업 방법을 익혔으면, 자신이 하는 일을 기록하고 그 일을 책임져라.
    • 프로젝트 리더가 프로젝트 팀을 구성한다.
      • 생각보다 협업은 어렵고 팀은 성장할 시간이 필요하다.
      • 모든 프로젝트는 팀의 성장도에 따라 다른 퍼포먼스를 보일 것이다.
    • 스프린트가 끝나기 전에 제품 사양이 변경되는 경우가 없어야 한다.
      • 제품이 나오기 전에, 제품의 유효성은 알 수 없다.
      • 프로젝트 팀은 매 스프린트 마다 피드백 가능한 작동하는 프로토타입을 만드는것을 목표로 한다.
      • 제품 사양 변경은 프로토타입을 검토하고 나서 해도 절대 늦지 않다.
    • 프로젝트의 모든 부분이 테스트 가능한 상태를 유지해야 한다
      • 급격한 사양변경 혹은 제품 기반이 변경되었을때 이를 견디게 해주는 필수요소다.
      • 급격한 변경에도 견딜수 있으므로 파괴적인 구조개선을 통해 단순하고 견고한 체제를 구축하는것이 가능.