개요
성공적인 프로그래밍?
모든 프로그래밍 패러다임은 성공적인 프로그래밍을 위해 존재한다. 성공적인 프로그래밍은 좋은 프로그램을 만드는 일이다. 좋은 프로그램은 사용성, 성능, 확장성, 기획 변경에 대한 대응력 등이 좋다. 이것들을 효율적이고 생산적으로 이루는 일이 성공적인 프로그래밍이다.
함수형 프로그래밍
함수형 프로그래밍은 성공적인 프로그래밍을 위해 부수 효과(side-effect)를 최소화하고 조합성을 강조하는 프로그래밍 패러다임이다.
⇒ 순수함수를 통해 부수 효과를 줄임으로써 오류를 줄이고 안정성을 높인다.
⇒ 모듈화 수준을 높임을 통해서 생산성을 높인다.
⇒ 함수형 사고방식은 문제의 해결 방법을 동사(함수)들로 구성(조합)하는 것
순수함수
순수 함수는 수학의 함수를 프로그래밍의 세계로 가져온 모델이다. 프로그래밍의 세계에는 무언가를 저장하고 변경하고 불러올 수 있는 상태라는 개념이 존재하지만, 수학의 세계에는 그런 개념이 없기 때문에 모든 함수는 함수 외부의 무언가에 절대 영향을 받지 않고 독립적으로 존재한다. 때문에 순수함수는 항상 동일한 함수를 반환한다. 항상 동일한 결과를 만들기 위해서 부수효과를 발생시킬 수 있는 요인이 없는 상태의 함수를 의미한다. 외부 변수를 포함하거나 외부의 상태를 변화시키지 않는(부수 효과를 발생시키지 않는) 함수이다.
function add (a, b) { // pure function
return a + b;
}
var obj1 = { val: 10 };
function add2 (obj, x) { // not pure
obj.val += b;
}
add2(obj1, 2);
var obj2 = { val: 20 };
function add3 (obj, x) { // pure function
return { val: obj.val + b };
}
JavaScript
복사
순수함수가 아닌 함수의 경우는 함수를 호출하는 시점에 따라서 결과가 다르기 때문에 시점을 파악하는 것이 중요하다. 예를들어 add2 라는 함수에서는 obj 의 상태에 따라서 결과가 다르기 때문에 obj의 상태를 개발자가 파악하는 것이 중요해진다. 이는 호출 시점 뿐만이 아니라 개발 시점에서도 마찬가지다.
반면 순수함수는 주어진 함수의 인자에 따라 항상 동일한 결과를 반환하기 때문에 다른 부수 효과에 집중하지 않고 함수의 로직 그 자체에만 집중할 수 있기 때문에 부수 효과를 걱정하며 사용하지 않아도 된다.
일급 객체
쉽게 말하면 함수를 값으로 다룰 수 있다는 의미이다. (변수에 담을 수 있고, 인자로 넘길 수 있거나, 변수, 인자를 통해 받은 함수를 또한 실행할 수 있다.) 자바스크립트에서는 함수는 일급 함수다.
function add_maker(a) {
return function(b) { // closure
return a + b;
};
}
const add10 = add_maker(10);
console.log(add10(20)) .// 30
JavaScript
복사
중요 규칙
•
함수 외부의 변수에 접근, 재할당해서는 안된다
•
함수의 인자를 변경하면 안 된다
객체지향프로그래밍 vs 함수형프로그래밍
duck.moveLeft();
duck.moveRight();
dog.moveLeft();
dog.moveRight();
moveLeft(dog);
moveRight(duck);
moveLeft({x:5,y:2});
moveRight(dog);
JavaScript
복사
var users = [
{ id: 10, name: 'ID', age: 36 },
{ id: 20, name: 'BJ', age: 32 },
{ id: 30, name: 'JM', age: 32 },
{ id: 40, name: 'PJ', age: 27 },
{ id: 50, name: 'HA', age: 25 },
{ id: 60, name: 'JE', age: 26 },
{ id: 70, name: 'JI', age: 31 },
{ id: 80, name: 'MP', age: 23 },
{ id: 90, name: 'FP', age: 13 },
];
JavaScript
복사
다형성
메소드는 함수가 아니다 ⇒ 객체에 종속적이다. 해당 클래스의 인스턴스에서만 사용 가능하다. ⇒ 다형성을 지원하기 어렵다
Javascript 에서는 array 만 존재하는 것이 아니라 array like 객체가 존재한다 (ex - jquery 검색 결과)
함수가 먼저 만들어 진 후 데이터가 나옴. 데이터가 존재하지 않더라도 함수는 존재한다? 평가 시점이 더 유연해진다.
외부의 다형성은 함수가 책임지지만(ex - filter) 내부의 다형성은 보조함수 (iterate)가 책임진다
커링 (curry)
_curry
function _curry (fn) {
return function (a) {
return function (b) {
return fn(a, b);
}
}
}
function _curry (fn) {
return function (a, b) {
return arguments.length === 2 ? fn (a, b) : function (b) { return fn(a,b); };
}
}
function add = _curry(function (a, b) { return a + b; });
add(5)(3); // 8
const add10 = add(10);
add10(20); // 30
add10(10); // 20
JavaScript
복사
본체함수를 인자로 받고 최종적으로 본체함수의 실행을 지연 실행한다
_curryR
function _curryR (fn) {
return function (a, b) {
return arguments.length === 2 ? fn (a, b) : function (b) { return fn(b, a); };
}
}
var _sub = _curryR(function (a, b) {
return a - b;
})
console.log(sub(10, 5)) // 5
var sub10 = sub(10);
console.log(sub10(5)); // -5
JavaScript
복사
_get
function _get (obj, key) {
return obj === null ? undefined : obj[key];
}
users[10].name // error
_get(users[10], 'name') // undefined
var _getR = _curryR(_get);
_getR('name')(users[1]);
var getName = _getR('name');
getName(users[1]);
JavaScript
복사
_each 함수
function _each (list, iter) {
for(var i = 0; i < list.length; i++) {
iter(list[i]);
}
}
//////
function _each (list, iter) {
var keys = _keys(list);
for(var i = 0; i < keys.length; i++) {
iter(list[keys[i]], keys[i]);
}
}
JavaScript
복사
_map함수
function _map (list, mapper) {
var newList = [];
_each(list, function (val, key) {
newList.push(mapper(val, key));
});
return newList;
}
JavaScript
복사
_filter
function _filter (list, predi) {
var newList = [];
_each(list, function (val, key) {
if (predi(val, key)) newList.push(val);
});
return newList;
}
JavaScript
복사
_reduce 함수
function _reduce (list, iter, memo) {
if(memo === undefined){
memo = list[0];
for(let i = 1; i < list.length; i++){
memo = iter(memo, list[i]);
}
} else {
for(let i = 0; i < list.length; i++){
memo = iter(memo, list[i]);
}
}
return memo;
}
JavaScript
복사
_pipe 함수
좀 더 추상화된 reduce 함수
function _pipe() {
var fns = arguments;
return function (arg) {
return _reduce(fns, function (memo, fn) {
return fn(memo);
}, arg);
}
}
var f1 = _pipe(
function (a) { return a + 1; },
function (a) { return a * 2 },
);
console.log( f1(1) ); // 4
JavaScript
복사
_go 함수
바로 실행하는 pipe 함수
function _go (arg) {
var fns = _rest(arguments);
return _pipe.apply(null, fns)(arg);
}
_go(1,
function (a) { return a + 1; },
function (a) { return a * 2 },
function (a) { return a * a; },
console.log
); // 16
JavaScript
복사
// 응용
console.log(
_map(
_filter(users, function (user) { return user.age < 30 }),
_get('age')
)
);
=>
_go(users,
function (users) {
return _filter(users, function (user) { return user.age < 30 });
},
function (users) {
return _map(users, _get('age'));
},
console.log
)
JavaScript
복사
다형성 및 에러
_each 수정
// 기존 each 에는 null 을 넣으면 에러 발생. null, undefined 를 넣어도 에러가 나지 않도록 만들기
var _length = _get('length'); // 커링된 함수
function _each(list, iter) {
for(var i = 0, len = _length(list); i < len; i++) {
iter(list[i]);
}
}
// each 를 수정함으로써 map, filter 에 null, undefined 에 대해서 에러를 내지 않고 실행 가능함.
// map, filter 등이 each를 사용하기 때문.
JavaScript
복사
_keys
// Object.keys 함수는 null 넣으면 에러가 발생 ex - Object.keys(null) - error
// _keys 함수를 만들자
function _is_object(obj) {
return typeof obj === 'object' && !!obj;
}
function _keys(obj) {
return _is_object(obj) ? Object.keys(obj) : [];
}
JavaScript
복사
_each 함수 외부 다형성 높이기
// _each 에 object 타입을 넣어도 순차적으로 실행 가능하도록
function _each(list, iter) {
var keys = _keys(list);
for(var i = 0, len = keys.length; i < len; i++) {
iter(list[keys[i]]);
}
}
JavaScript
복사
컬렉션 중심 프로그래밍의 4가지 유형과 함수
수집하기
map, values, pluck 등
_values
function _values (data) {
return _map(data, _identity)
}
console.log(_keys(users[0]));
console.log(_values(users[0]));
// with curryR
var _mapr = _curryR(_map);
console.log(_mapr(_identity)(users[0])) // _values 와 동일한 결과 반환
// var _values = _mapr(_identity); // 이렇게도 만들 수 있음
JavaScript
복사
_identity
function _identity (val) {
return val;
}
JavaScript
복사
_pluck
function _plcuk (data, key) {
return _.map(data, function (obj) {
return obj[key];
});
}
console.log(_pluck(users, 'ages')) // [32, 33, 33, .....]
JavaScript
복사
거르기
filter, reject, compact, without 등
_reject
// filter 의 반대
function _reject (data, predi) {
return _filter(data, function (val) {
return !predi(val);
});
}
function _negate(func) {
return function (val) {
return !func(val);
}
}
function _reject (data, predi) {
return _filter(data, _negate(predi));
}
JavaScript
복사
_compact
var _filterR = _curryR(_filter);
var _compact = _filterR(_identity); // curryR 이 적용된 filter
JavaScript
복사
찾아내기
find, some, every 등
_find
function _find (list, predi) {
var keys = _keys(list);
for(var i = 0; i < keys.length; i++) {
var val = list[keys[i]];
if (predi(val)) return list[keys[i]];
}
}
JavaScript
복사
_findIndex
function _findIndex (list, predi) {
var keys = _keys(list);
for(var i = 0; i < keys.length; i++) {
if (predi(list[keys[i])]) return i;
}
return -1;
}
JavaScript
복사
_some
function _some(list, predi) {
return (_findIndex(list, predi || _identity) !== -1);
}
// 만약 predi 함수가 안들어오면 identity 함수를 자동으로
JavaScript
복사
_every
function _every(list, predi) {
return _findIndex(data, _negate(predi || _identity)) === -1;
}
JavaScript
복사
접기
reduce, min, max, group_by, count_by 등
_min
function _min (data) {
return _reduce (data, function (a, b) {
return a < b ? a : b;
});
}
JavaScript
복사
_max
function _max (data) {
return _reduce (data, function (a, b) {
return a > b ? a : b;
});
}
JavaScript
복사
_minBy
function _minBy (data, iter) {
return _reduce (data, function (a, b) {
return iter(a) < iter(b) ? a : b;
});
}
JavaScript
복사
_maxBy
function _maxBy (data, iter) {
return _reduce (data, function (a, b) {
return iter(a) > iter(b) ? a : b;
});
}
_go(users,
_filterR(user => user.age >= 30),
_mapR(_get('age')),
_minR,
console.log,
)
_go(users,
_rejectR(user => user.age >= 30),
_maxBy(_get('age')),
_get('name'),
console.log,
)
JavaScript
복사
_groupBy
function _groupBy(data, iter) {
return _reduce (data, function (grouped, val) {
var key = iter(val);
(grouped[key] = grouped[key] || []).push(val);
return grouped;
}, {})
}
_go(
users,
(user) => user.age - (user.age % 10),
console.log
)
JavaScript
복사
_countBy
function _countBy (data, iter) {
return _reduce(data, function (count, val) {
var key = iter(val);
count[key] = count[key]++ : 1;
return count;
});
}
JavaScript
복사
기타 함수
_isObject
function _isObject(obj) {
return typeof obj === 'object' && !!obj;
}
JavaScript
복사
_first
function _first(list) {
return _get(list, 0);
}
JavaScript
복사
_pairs
function _pairs (data) {
return _.map(data, (val, key) => [key, val]);
}
JavaScript
복사
지연평가
지연평가 시작시ㅣ고 유지시키는 함수
1. map
2. filter, reject
끝을 내는 함수
1. take
2. some, every, find
var mi = 0;
var fi = 0;
_.go(
_.range(100)
_.map((val) => {
mi++;
return val * val;
}),
_.filter((val) => {
fi++;
return val % 2;
}),
_.take(5),
console.log
)
console.log(mi, fi) // 100, 100
=>
_.go(
_.range(100)
L.map((val) => {
mi++;
return val * val;
}),
L.filter((val) => {
fi++;
return val % 2;
}),
L.take(5),
console.log
)
console.log(mi, fi) // 10, 10
// map 에 1개 넣어서 실행 후 다음에 넣어서 filter 에넣고,
// take 에 실행, 그 다음 데이터 똑같은 루프를 돌림
JavaScript
복사
비동기
function square (a) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(a * a);
}, 500);
});
}
console.log(1)
square(10).then(function (res) {
console.log(2);
console.log(res); // 100
});
console.log(3);
// 1 => 3 => 2
square(10) // 실행됨. 그러나 Promise 일 때만 실행 가능
.then(square)
.then(square)
.then(console.log);
_.go(square(10), // 자동으로 Promise 를 받으면 지연 실행
square,
square,
console.log
)
var list = [2, 3, 4]
new Promise(function (resolve) {
(function recur(res) {
if (list.length === res.length) return resolve(res);
square(list[[res.length]]).then(function (val) {
res.push(val);
recur(res);
})
})([]);
}).then(console.log);
///////
const then = curry(function (fn, arg) { return arg instanceof Promise ? arg.then(fn) : fn(arg); });
function num(a) {
return a;
}
function numP(a) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(a);
}, 1000);
});
}
// then 함수를 pipe 안에 사용하면 비동기도 사용 가능할 듯
const then = _curry((f, a) => a instanceof Promise ? a.then(f) : f(a));
const add10 = then(a => a + 10);
const add100 = then(a => a + 100);
const log = then(console.log);
log(add100(add10(num(5)))); // 즉시 115
log(add100(add10(numP(6)))); // 1초 뒤 116
JavaScript
복사
요약
1.
함수를 되도록 작게 만들기
2.
다형성 높은 함수 만들기
3.
상태를 변경하지 않거나 정확히 다루어 부수 효과를 최소화하기
부수효과를 없앨 수는 없지만 부수효과가 최종 결과(dom 변경이라던가 등등)가 되도록, 그 전에는 부수효과가 없도록
4.
동일한 인자를 받으면 항상 동일한 결과를 리턴하는 순수 함수 만들기
5.
복잡한 객체 하나를 인자로 사용하기보다 되도록 일반적인 값 여러 개를 인자로 사용하기
6.
큰 로직을 고차 함수로 만들고 세부 로직을 보조 함수로 완성하기
7.
어느 곳에서든 바로 혹은 미뤄서 실행할 수 있도록 일반 함수이자 순수 함수로 선언하기
8.
모델이나 컬렉션 등의 커스텀 객체보다는 기본 객체를 이용하기
9.
로직의 흐름을 최대한 단방향으로 흐르게 하기