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

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

구조분해

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

번역이 구조분해라구 된 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);
}

급귀찮다…..