부수효과를 방지하고 상태변이를 감소하기 위해 데이터 제어 흐름과 연산을 함수를 통해 추상화
// interactive(명령형)
// 배열에서 length 만큼 홀수만걸러서 제곱한 후 모두 더해서 출력
function test(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);
}// declaretive(선언적)
go(
list,
filter(v => v % 2),
map(v => v * v),
take(10),
reduce(add),
console.log
)
💸 Benefit
// declaretive(선언적)
go(
list,
filter(v => v % 2),
map(v => v * v),
take(10),
reduce(add),
console.log
)
// Bad
let counter = 0;
function increment() {
return ++counter;
}
// Good
const increment = counter => counter + 1;
ex) Math.sin, pow....
런타임에 행위를 전달받아서
미리 정해진 제어흐름에 수행
(제어흐름 추상화)
const arr = [1,2,3,4,5];
// normal loop
for( let i = 0 ; i < 10 ; i ++ ) {
console.log(arr[i]);
}
// abstract loop
const forEach = (f, iter) => {
for(const a of iter) {
f(a);
}
}
forEach(arr, console.log);Closure를 활용한 State 저장
const showToggle = (element) => {
let show = false;
return () => {
show = !show;
element.style.display = `${show ? 'block' : 'none'}`
}
};
const h1 = document.querySelector('#heading');
const h1Toggle = showToggle(h1);
button.onclick = h1Toggle;
// or
// showToggle(h1)();배열에 부분집합을 반환
배열에 요소를 사상(mapping)
리스트에 결과를 축약(결과)
const filter = (f, list) => {
const arr = [];
for(let i = 0 ; i < arr.length ; i ++) {
if(f(arr[i]) arr.push(arr[i]);
}
return arr;
}const filter = (f, list) => {
const arr = [];
for(let i = 0 ; i < arr.length ; i ++) {
if(f(arr[i]) arr.push(arr[i]);
}
return arr;
}const filter = (f, iter) => {
const arr = [];
for(const a of iter) {
if(f(a)) arr.push(a);
}
return arr;
};// declaretive(선언적)
go(
range(100000000),
filter(v => v % 2),
map(v => v * v),
reduce(add),
take(10),
console.log
)
100000000 loop 배열 생성
filter -> map -> take -> reduce
"필요한 시점에 호출해서 사용"
const filter = (f, iter) => {
const arr = [];
for(const a of iter) {
if(f(a)) arr.push(a);
}
return arr;
};L.filter = function* (f, iter) => {
for(const a of iter) {
if(f(a)) {
yield a
}
}
};// declaretive(선언적)
go(
L.range(100000000),
L.filter(v => v % 2),
L.map(v => v * v),
L.reduce(add),
take(10),
console.log
)
전체적으로 10번 호출 -> 성능 향상
호출순서
// 함수를 순차적으로 적용해서 축약
const go = (...args) => reduce((a, f) => f(a), args);
go(
10,
a => a + 10,
a => a + 20,
console.log
);
// h(g(f(10)))
/**
go(
10,
f,
g,
h
)
*/
const list = [{ count: 1 }, { count: 5 }, { count: 10 }, { count: 2 }];
go(
list,
list => map(v => v.count, list),
list => filter(v => v < 5, list),
list => reduce(add, list),
console.log
);// 함수를 파이프라인으로 엮은 새로운 함수를 리턴
// 평가시점을 뒤로 미룸
const pipe = (...fs) => (v) => go(v, ...fs);
const list = [{ count: 1 }, { count: 5 }, { count: 10 }, { count: 2 }];
const sum =
//함수를 미리 조합해놓고
pipe(
list => map(v => v.count, list),
list => filter(v => v < 5, list),
list => reduce(add, list)
);
//호출하는 시점에 list를 넘겨서 평가
sum(list);f(x, y, z)
g(x)(y)(z)
함수를 재사용, 조합할때 사용
// 함수를 파이프라인으로 엮은 새로운 함수를 리턴
// 평가시점을 뒤로 미룸
const pipe = (...fs) => (v) => go(v, ...fs);
const list = [{ count: 1 }, { count: 5 }, { count: 10 }, { count: 2 }];
const filter = (f) => function*(iter) { for(const a of iter) if(f(a)) yield a }
const sum =
pipe(
map(v => v.count),
filter(v => v < 5),
reduce(add)
);
sum(list);// 함수를 파이프라인으로 엮은 새로운 함수를 리턴
// 평가시점을 뒤로 미룸
const pipe = (...fs) => (v) => go(v, ...fs);
const list = [{ count: 1 }, { count: 5 }, { count: 10 }, { count: 2 }];
const filter = curry(function*(f, iter) { for(const a of iter) if(f(a)) yield a });
// filter(f)(iter)
// filter(f, iter)
// const testfilter = filter(f)
// testfilter(iter);
const map = curry(function*(f, iter) { for(const a of iter) yield f(a) });
const sum =
pipe(
map(v => v.count),
filter(v => v < 5),
reduce(add)
);
const sumCount = sum(v => v.count);
sumCount (list);Lodash.js
Ramda.js
Validator
const userSchema = {
name: [isString, min(3), max(30), isRequired],
email: [isString, pattern(emailPattern), min(10), max(30), isRequired],
age: [isNumber, isRequired]
};
const form = {
name: "aaaa",
email: "test",
age: 33
};
// false
const result = validate(userSchema, form);const R = require("ramda");
const min = R.curry((a, b) => a < b.length);
const max = R.curry((a, b) => a > b.length);
const isTypeOf = type => val => typeof val === type;
const emailPattern = /^([a-zA-Z0-9])+([\.\+a-zA-Z0-9_-])*@([\+a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-]+)+$/;
const pattern = R.curry((pattern, val) => pattern.test(val));
const isString = isTypeOf("string");
const isNumber = isTypeOf("number");
const isRequired = val => !!val;const every = R.reduce((a, b) => a && b, true);
const validateFns = val =>
R.pipe(
R.map(testFn => testFn(val)),
every
);
const findInvalidColumn = form =>
R.pipe(
Object.entries,
R.map(([k, v]) => [k, validateFns(form[k])(v)]),
R.filter(([_, isValid]) => !isValid),
R.take(1),
R.flatten,
([k]) => k
);
const validate = R.curry((schema, form) =>
R.pipe(
findInvalidColumn(form),
R.isNil
)(schema)
);// validate middleware factory
const validateSchema = schema => (req, res, next) => {
if(validate(schema, req.body)) {
next();
}
res.status(422).send({ message: 'validate error'});
}
router.post('/register', validateSchema(userSchema), userController.register);const debug = val => {
console.debug(val);
return val;
};
R.pipe(
filter...,
log,
map...
...
)