목차
• js es6+에서 달라진 점
• 리스트 순회
• 이터러블/이터레이터 프로토콜
• 제네레이터
• 함수형 프로그래밍
• 명령형, 함수형 코드 흐름
• 명령형을 함수형으로 리팩토링
• map, filter, take, reduce, go, curry
3.
리스트 순회
• Es5
•for(var i=0; i<length; i++){
• Items.foreach(function(item){...
• for(var key in iterable){ …
• ES6
• for(const value of iterable){…
Symbol.iterator 속성을 가지고 있는가?
4.
iterable
• 이터레이터를 리턴하는[Symbol.iterator]() 메소드를 가진 값
let arr = [1,2,3,4];
let iterator = arr[symbol.iterator]();
arr[symbol.iterator]
ƒ [Symbol.iterator]() { [native code] }
• Iterator 를 리턴하는 메소드
• arr는 iterable 이다.
5.
iterator
• { value: v, done: Boolean } 객체를 리턴하는 next() 를 가진 값
const arr = [1,2];
const iter = arr[Symbol.iterator]();
log(iter.next()) // {value: 1, done: false}
log(iter.next()) // {value: 2, done: false}
log(iter.next()) // {value: undefined, done: true}
for(const value of iter) 는 done 이 true 일 때까지 iter 를 순회한다.
for(const value in iter) 은 그런거 없음…
6.
iterable/iterater protocol
• iterable을for..of, 전개 연산자 등과 함께 동작하도록 한 규약
• Array, Set, Map 은 iterable/iterater protocol 을 따른다고 할 수 있다.
const arr = [1,2,3];
const arrIter = arr[Symbol.iterator]();
const map = new Map([ ['a', 1], ['b',2], ['c',3] ]);
const mapIter = map[Symbol.iterator]();
const set = new Set([1,2,3]);
const setIter = set[Symbol.iterator]();
세상 흔한 명령형코드
// 리스트에서 홀수를 length 만큼 뽑아서 제곱한 후 모두 더하기
function f(list, length) {
let i = 0;
let acc = 0; // 누적값
for (const a of list) { // 반복문
if (a % 2) { // 조건문
acc = acc + a * a; // 연산
if (++i == length) break; // 시간복잡도를 위한 탈출
}
}
console.log(acc); // 외부 API 호출
}
10.
함수형에서 if 를추상화 하는 filter
function f(list, length) {
let i = 0;
let acc = 0;
for (const a of list) {
if (a % 2) {
acc = acc + a * a;
if (++i == length)
break;
}
}
log(acc);
}
function *filter(f, list){
for(const a of list){
if(f(a)) yield a;
}
}
function f(list, length) {
let i = 0;
let acc = 0;
for (const a of filter(a => a % 2, list))
{
acc = acc + a * a;
if (++i == length) break;
}
log(acc);
}
11.
함수형에서 연산을 추상화하는map
function f(list, length) {
let i = 0;
let acc = 0;
for (const a of filter(a
=> a % 2, list)) {
acc = acc + a * a;
if (++i == length)
break;
}
log(acc);
}
function *map(f, list){
for(const a of list){
yield f(a);
}
}
function f(list, length) {
let i = 0;
let acc = 0;
for (const a of map(a => a * a ,
filter(a => a % 2, list)))
{
acc = acc + a;
if (++i == length) break;
}
log(acc);
}
12.
take
function f(list, length){
let i = 0;
let acc = 0;
for (const a of map(a => a * a ,
filter(a => a % 2, list)))
{
acc = acc + a;
if (++i == length) break;
}
log(acc);
}
function take(length, iter){
let res = [];
for(const a of iter){
res.push(a);
if(res.length == length) return res;
}
return res;
}
function f(list, length) {
let i = 0;
let acc = 0;
for (const a of take(length ,map(a => a * a ,
filter(a => a % 2, list))))
{
acc = acc + a;
}
log(acc);
}
13.
reduce1
function f(list, length){
let i = 0;
let acc = 0;
for (const a of take(length ,map(a => a * a ,
filter(a => a % 2, list))))
{
acc = acc + a;
}
log(acc);
}
function reduce(f, acc, iter){
for(const a of iter){
acc = f(acc, a);
}
return acc;
}
function f(list, length) {
return reduce(
(acc, a) => acc + a,
0,
take(length ,map(a => a * a ,filter(a => a % 2, list)))
)
}
14.
reduce2function f(list, length){
return reduce(
(acc, a) => acc + a,
0,
take(length ,map(a => a * a ,filter(a => a % 2, list)))
)
}
const add = (a,b) => a + b;
const f = (list, length) =>
reduce(add, 0,
take(length ,
map(a => a * a,
filter(a => a % 2, list))));
오른쪽에서 왼쪽으로 읽는다.
List 를 a % 2 로 filter 하고
a * a 로 곱해서 mapping 하고
Length 만큼 take 해서
초깃값 0 에다 add 한다.
15.
go1
function reduce(f, acc,iter){
if(arguments.length == 2){
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
for(const a of iter){
acc = f(acc, a);
}
return acc;
}
const go = (...as) => reduce((a, f) => f(a), as);
go( 10, a => a + 10, a => a + 1, console.log);
전달되는 함수를 차례대로 실행하는 함수.
처음 10을 다음 함수 a => a + 10 에 전달하고
그 결과를(10 + 10 = 20) 다음 함수에 전달한다.
그 결과 (20 + 1 = 21) 을 다음 함수 console.log 에 전달하여 프린트 한다.
16.
go2
const f2 =(list, length) => go(
list,
list => filter(a => a % 2, list),
list => map(a => a * a, list),
list => take(length, list),
list => reduce(add, 0, list)
);
const add = (a,b) => a + b;
const f = (list, length) =>
reduce(add, 0,
take(length ,
map(a => a * a,
filter(a => a % 2, list))));
거꾸로 읽어야 해서 불편했던 부분이 위에서 아래로 읽을 수 있게 되었다.
또한 함수의 중첩이 심해서 읽기 불편했던 부분이 없어졌다.
curry2 const filter= curry(
function *(f, list){
for(const a of list){
if(f(a)) yield a;
}
});
const reduce = curry(
function (f, acc, iter){
if(arguments.length == 2){
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
for(const a of iter){
acc = f(acc, a);
}
return acc;
});
const map = curry(
function *(f, list){
for(const a of list){
yield f(a);
}
});
const take = curry(
function (length, iter){
let res = [];
for(const a of iter){
res.push(a);
if(res.length == length) return res;
}
return res;
});
19.
curry3
const add =(a,b) => a + b;
const f = (list, length) =>
go(list,
filter(a => a % 2),
map(a => a * a),
take(length),
reduce(add));
function f(list, length) {
let i = 0;
let acc = 0;
for (const a of list) {
if (a % 2) {
acc = acc + a * a;
if (++i == length) break;
}
}
log(acc);
}
BEFORE AFTER
20.
Javascript es6에서 지원하는
map,filter, reduce
Array.prototype.take = function (length) {
return this.slice(0, length);
}
const add = (a,b) => a + b;
const f = (list, length) =>
list.filter(a => a % 2).
map(a => a * a).
take(length).
reduce(add);
take 는 js 에서는 없다.
const add = (a,b) => a + b;
const f = (list, length) =>
go(list,
filter(a => a % 2),
map(a => a * a),
take(length),
reduce(add));
21.
다른 FP 지원언어 에서의
map, filter, reduce
fun f(list: MutableList<Int>, length: Int) =
list.filter { it % 2 != 0 }
.take(length)
.map { it * it }
.reduce { acc, i -> acc + i}
def f = (list: List[Int], length: Int) =>
list.filter((i: Int) => i % 2 != 0)
.take(length)
.map((i: Int) => i * i)
.reduce((acc: Int, a: Int) => acc + a)
코틀린 스칼라