forked from indongyoo/Kakao-functional-js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
5.html
239 lines (207 loc) · 7.27 KB
/
5.html
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>4</title>
<script>const {log, clear} = console;</script>
<script src="fx.js"></script>
<script>
class Model {
constructor(attrs = {}) {
this._attrs = attrs;
}
set(k, v) {
this._attrs[k] = v;
return this;
}
get(k) {
return this._attrs[k];
}
}
class Collection {
constructor(models = []) {
this._models = models;
}
at(idx) {
return this._models[idx];
}
add(model) {
this._models.push(model);
return this;
}
[Symbol.iterator]() {
return this._models[Symbol.iterator]();
}
}
</script>
</head>
<body>
13. 이터러블 프로그래밍을 익혀야 하는 이유
# ES6+의 표준 프로토콜
- for...of 문은 이터러블을 순회합니다.
<script>
for (const a of [1, 2, 3]) log(a);
// 1
// 2
// 3
</script>
- 이터러블은 전개 연산자를 통해 펼칠 수 있습니다.
<script>
log( [...[1, 2], ...[3, 4]] );
// [1, 2, 3, 4]
</script>
- [] 를 통한 구조분해는 이터러블에 사용합니다.
<script>
const [first, second] = [1, 2];
log(first); // 1
log(second); // 2
</script>
- 나머지 연산자도 이터러블에 사용합니다.
<script>
const [head, ...tail] = [1, 2, 3];
log(head); // 1
log(tail); // [2, 3]
</script>
- 이터러블은 [Symbol.iterator]()를 통해 이터레이터를 반환합니다.
- 제너레이터는 이터레이터를 반환합니다.
- 이터레이터는 곧 이터러블입니다. (well-formed)
- 이터레이터/제너레이터는 자바스크립트의 연산자, 구문 등과 함께 동작합니다.
<script>
function* generator() {
yield 10;
yield 20;
yield 30;
}
const iterator = generator();
log(iterator.next()); // {value: 10, done: false}
log(iterator.next()); // {value: 20, done: false}
log(iterator.next()); // {value: 30, done: false}
log(iterator.next()); // {done: true}
const iter = generator();
log(iter[Symbol.iterator]); // f [Symbol.iterator]() { [native code] }
const [h, ...t] = iter;
log(h); // 10
log(t); // [20, 30]
</script>
- 이런 규약을 통칭해 이터러블 프로토콜이라고 하며 이것은 ES6에서 매우 중요한 위치에 있습니다.
- ES6+ 에서도 이런 규약을 사용하거나 컨셉을 함께하는 규약들이 만들어지고 있습니다.
# 프로그래밍 패러다임
- 이터러블/이터레이터/제너레이터는 코드를 값으로 다룰 수 있도록 하는 도구입니다.
<script>
function myCode1(l) {
if (l > 0) log('a');
if (l > 1) log('b'.toUpperCase());
if (l > 2) log('c');
if (l > 3) log('d'.toUpperCase());
}
myCode1(2);
// a
// b
function* myCode2() {
yield 'a';
yield 'b'.toUpperCase();
yield 'c';
yield 'd'.toUpperCase();
}
each(log, take(1, myCode2()));
// a
each(log, take(3, myCode2()));
// a
// B
// c
function* take(l, iter) {
for (const a of iter) {
yield a;
if (--l == 0) break;
}
}
function each(f, iter) {
for (const a of iter) f(a);
}
</script>
- 코드를 값으로 다루는 것은 리습/메타프로그래밍의 핵심입니다.
- 자바스크립트는 이제 공식적으로 코드, 로직, 상태, 에러를 '값'으로 다룰 수 있게 되었습니다.
- 비동기/동시성/병렬성을 값으로 잘 다룰 수 있습니다.
- 함수와 코드를 값으로 다루는 것은 일종의 프로그래밍 패러다임입니다.
- 이터러블 프로토콜을 잘 다룬다는 것은 새로운 프로그래밍 패러다임을 익히는 것과 같습니다.
- 새로운 프로그래밍 패러다임을 익히는 것은 언어에 대한 관점과 응용력을 확장하는 것입니다.
- 언어에 대한 응용력이 좋아지면 문제 해결력, 생산성, 안정성이 좋아집니다.
- 문제 해결, 생산성, 안정성은 성공적인 프로그래밍의 핵심적인 지표입니다.
- 현대 프로그래밍에서 함수형 패러다임은 언어 제약적이지 않습니다.
# 조합성, 재사용성
- 코드가 값으로 다뤄 질수 있다는 것은 그 코드가 어떤 '규약'을 지키고 있다는 것입니다.
- 값으로 다룰 수 있도록 규약이 지켜진 코드는 재사용성이 높습니다.
- 규약이 지켜진 코드는 다른 코드와의 조합성이 높습니다.
- 규약이 지켜진 코드는 협업을 용이하게 합니다.
- 이 규약이 언어의 규약과 일치할 수록 언어와 여러 도구의 도움을 받기 용이합니다.
# 응용력
- 추상화가 잘 된 규약은 응용력이 높습니다.
- 이터러블은 값을 계산하여 세분화해나간 후에도 이터러블인 상태를 유지하도록 하여
추상화 수준을 동일하게 유지하는 아이디어를 가지고 있습니다.
<script>
const iter1 = myCode2();
log(iter1.next()); // {value: "a", done: false}
const iter2 = take(1, iter1);
log(iter2.next()); // {value: "B", done: false}
log(iter2.next()); // {done: true}
</script>
- 추상화 단계가 유지되면 여전히 동일한 응용력을 가질 수 있습니다.
<script>
function* map(f, iter) {
for (const a of iter) yield f(a);
}
const iter3 = map(a => a.toLowerCase(), myCode2());
log(iter3.next()); // {value: "a", done: false}
log(iter3.next()); // {value: "b", done: false}
log(iter3.next()); // {value: "c", done: false}
log(iter3.next()); // {value: "d", done: false}
const iter4 = map(a => a.toLowerCase(), myCode2());
[...take(2, iter4)].join(' and '); // a and b
</script>
- 일반화가 많이 된 규약은 특정 상황에만 잘 대응될 가능성이 높습니다.
- 소프트웨어의 복잡성이 증가할 수록 특정 상황에 특화된 패러다임보다는
유연성과 생산성을 함께 높일 수 있는 패러다임이 유리합니다.
# 명령형 vs 함수형
- 명령형 코드는 서로의 코드를 이해하기 어렵습니다.
<script type="module">
function sumOdds(arr) {
let acc = 0;
for (let i = 0; i < arr.length; i++) {
const a = arr[i];
if (a % 2 == 1) {
acc += a;
}
}
log(acc);
}
sumOdds([1, 2, 3, 4, 5]);
</script>
- 함수형 코드는 이해하기 쉬울 뿐 아니라 '코드를 응용(Applicative)' 할 수 있습니다.
<script type="module">
const sum = reduce(add);
const sumMap = curry(pipe(L.map, sum));
const totalPrice = sumMap(p => p.price);
const totalQuantity = sumMap(p => p.quantity);
</script>
- 함수형 코드는 어디에서든 쓰일 수 있습니다.
<script>
class Product extends Model {}
class Products extends Collection {
totalPrice() {
return go(this,
L.map(p => p.get('price')),
reduce(add));
}
}
const products = new Products();
products.add(new Product({ name: 'aa', price: 10000 }));
products.add(new Product({ name: 'bb', price: 15000 }));
log(products.totalPrice());
</script>
# 결론
- '이터러블 프로그래밍'이라는 규약을 중심에 두고 팀 작업을 하게 되면,
구성원 간에 같은 관점을 갖게하여 소통이 용이하며,
생산성, 조합성, 안정성, 코드 품질 관리의 객관성을 높일 수 있습니다.
- 팀에서 공유하고 있는 패러다임이 성숙해질수록 높을 효율을 얻을 수 있습니다.
</body>
</html>