함수로 함수 만들기
'왜 함수를 만들며 어떻게 만들 것인가?
5.1 함수 조립의 핵심
- 대상이 존재하는 지 확인한다.
- 네이티브 버전이 있는 지 확인하여 있다면 그것을 사용한다.
- 네이티브 버전이 없다면 필요한 동작을 수행할 태스크를 구현한다.
function invoker (NAME, METHOD) {
return function(target /* args ... */) {
if (!existy(target)) fail("Must provide a target");
var targetMethod = target[NAME];
var args = _.rest(arguments);
return doWhen((existy(targetMethod) && METHOD === targetMethod), function() {
return targetMethod.apply(target, args);
});
};
};
------------------------------------------------------------------------------------
function dispatch(/* funs */) {
var funs = _.toArray(arguments);
var size = funs.length;
return function(target /*, args */) {
var ret = undefined;
var args = _.rest(arguments);
for (var funIndex = 0; funIndex < size; funIndex++) {
var fun = funs[funIndex];
ret = fun.apply(fun, construct(target, args)); // 메서드 실행을 다른 함수에 위임한다.
if (existy(ret)) return ret; // 결과값이 있을 경우 반환한다.
}
return ret;
};
}
var str = dispatch(invoker('toString', Array.prototype.toString),
invoker('toString', String.prototype.toString));
str("a");
//=> "a"
str(_.range(10));
//=> "0,1,2,3,4,5,6,7,8,9"
function stringReverse(s) {
if (!_.isString(s)) return undefined; // 대상이 있는 지 확인
return s.split('').reverse().join(""); // 필요한 동작을 구현
}
stringReverse("abc");
//=> "cba"
stringReverse(1);
//=> undefined
var rev = dispatch(invoker('reverse', Array.prototype.reverse),
stringReverse);
rev([1,2,3]); // Array.prototype.reverse
//=> [3, 2, 1]
rev("abc"); // stringReverse
//=> "cba"
- switch 문을 사용하는 함수를 개선해보자
- 함수의 기능을 확장하려면 switch문을 고쳐야 한다.
- 함수형 프로그래밍에서는 dispatch함수를 래핑하면 된다.
function performCommandHardcoded(command) { // 파라미터에 따라 실행이 달라지는 함수
var result;
switch (command.type) //수정이 발생 시 case문이 추가되는 소스 수정이 필요하다.
{
case 'notify':
result = notify(command.message);
break;
case 'join':
result = changeView(command.target);
break;
default:
alert(command.type);
}
return result;
}
function isa(type, action) {
return function(obj) {
if (type === obj.type)
return action(obj);
}
}
var performCommand = dispatch(
isa('notify', function(obj) { return notify(obj.message) }),
isa('join', function(obj) { return changeView(obj.target) }),
function(obj) { alert(obj.type) });
var performAdminCommand = dispatch( // 기능추가시 추가되는 기능 dispatch함수로 래핑한다.
isa('kill', function(obj) { return shutdown(obj.hostname) }),
performCommand);
5.1.1 변이는 저수준 동작이다.
- 명령행보다는 함수형으로 코드를 구현하는 것이 가장 이상적이지만 때로는 속도나 편의를 고려해서 라이브러리 기능을 명령행으로 구현한다.
- 함수 내부에서 변수 변이가 발생했고 외부로 영향을 주지 않는 다면 아무도 개의치 않는 다.
- 때로는 변이가 필요하다. 하지만 변이는 항상 멀리해야 할 저수준 동작으로 간주한다.
- 애플리케이션을 만들 때는 실행 파라미터가 무언인지 먼저 확인해 봄으로써 어떤 구현 기법이 가장 적절할지 결정하는 것이 바람직하다.
5.2 커링
- 각각의 논리적 인자에 대응하는 새로운 함수를 반환하는 함수를 커리함수라고 한다.
- 각각의 논리 파라미터를 이용해서 점차 세부적으로 설정된 함수를 반환한다.
function rightAwayInvoker() { //일반적인 함수, 함수를 반환하지 않는 다.
var args = _.toArray(arguments);
var method = args.shift();
var target = args.shift();
return method.apply(target, args);
}
rightAwayInvoker(Array.prototype.reverse, [1,2,3])
//=> [3, 2, 1]
5.2.1 우향 커리, 좌향 커리
- 좌향 커리 : 가장 오른쪽 인자에서 커리가 시작해서 왼쪽으로 이동한다.
function leftCurryDiv(n) {
return function(d) {
return n/d;
};
}
function rightCurryDiv(d) {
return function(n) {
return n/d;
};
}
5.2.2 자동 커링 파라미터
- 함수를 인자로 받은 다음에 한 개의 파라미터를 갖는 고정된 함수를 반환하는 고차원 함수를 curry라고 부른다.
- 함수를 직접 사용하면 되는 데 굳이 curry를 사용하는 가?
- 자바스크립트에서는 인자의 개수와 부가적인 '특화'인자의 개수가 정해져 있을 때가 많다.
parseInt('11');
parseInt('11',2);
['11','11'].map(parseInt) // [11, NaN] 내부적으로 배열, 배열인덱스가 넘어간다.
function curry(fun) {
return function(arg) {
return fun(arg);
};
}
['11','11'].map(curry(parseInt));
커링으로 새로운 함수 만들기
function curry2(fun) {
return function(secondArg) {
return function(firstArg) {
return fun(firstArg, secondArg);
};
};
}
var plays = [{artist: "Burial", track: "Archangel"},
{artist: "Ben Frost", track: "Stomp"},
{artist: "Ben Frost", track: "Stomp"},
{artist: "Burial", track: "Archangel"},
{artist: "Emeralds", track: "Snores"},
{artist: "Burial", track: "Archangel"}];
_.countBy(plays, function(song) {
return [song.artist, song.track].join(" - ");
});
//=> {"Ben Frost - Stomp": 2,
// "Burial - Archangel": 3,
// "Emeralds - Snores": 1}
function songToString(song) {
return [song.artist, song.track].join(" - ");
}
var songCount = curry2(_.countBy)(songToString); //함수이름을 직관적으로 구현
songCount(plays);
//=> {"Ben Frost - Stomp": 2,
// "Burial - Archangel": 3,
// "Emeralds - Snores": 1}
세 개의 파라미터를 커링해서 HTML 16진 색상 생성기 구현하기
function curry3(fun) {
return function(last) {
return function(middle) {
return function(first) {
return fun(first, middle, last);
};
};
};
};
var songsPlayed = curry3(_.uniq)(false)(songToString);
songsPlayed(plays);
//=> [{artist: "Burial", track: "Archangel"},
// {artist: "Ben Frost", track: "Stomp"},
// {artist: "Emeralds", track: "Snores"}]
function toHex(n) {
var hex = n.toString(16);
return (hex.length < 2) ? [0, hex].join(''): hex;
}
function rgbToHexString(r, g, b) {
return ["#", toHex(r), toHex(g), toHex(b)].join('');
}
rgbToHexString(255, 255, 255);
//=> "#ffffff"
var blueGreenish = curry3(rgbToHexString)(255)(200);
blueGreenish(0);
//=> "#00c8ff"
5.2.3 커링을 이용한 플루언트 API
??
5.2.4 자바스크립트에서 커링의 단점
- 함수의 인자가 다양하므로 커링을 적용했을 때 혼란스러울 때가 많다.
5.3 부분 적용
- 부분적으로 실행을 마친 다음에 나머지 인자와 함께 즉시 실행한 상태가 되는 함수
function divPart(n) {
return function(d) {
return n / d;
};
}
var over10Part = divPart(10);
over10Part(2);
//=> 5
- 커리 함수는 사실상 한 개의 인자만 남겨 둔 부분 적용 함수와 같다. 그러나 부분 적용에서는 한 번에 한 개의 인자를 처리하지 않으며 한 번에 필요한 만큼의 인자를 적용한 다음에 이후 실행 시 나머지 인자를 활용할 수 있는 상태로 저장된다.
5.3.1 한두 개의 알려진 인자를 부분 적용
function partial1(fun, arg1) {
return function(/* args */) {
var args = construct(arg1/*한개의 인자를 부분적용*/, arguments);
return fun.apply(fun, args);
};
}
var over10Part1 = partial1(div, 10);
over10Part1(5) //=>2
function partial2(fun, arg1, arg2) { // 두개의 인자를 부분 적용하는 함수
return function(/* args */) {
var args = cat([arg1, arg2], arguments);
return fun.apply(fun, args);
};
}
var div10By2 = partial2(div, 10, 2)
div10By2()
//=> 5
5.3.2 임의의 수의 인자를 부분 적용
function partial(fun /*, pargs */) {
var pargs = _.rest(arguments);
return function(/* arguments */) {
var args = cat(pargs, _.toArray(arguments));
return fun.apply(fun, args);
};
}
5.3.3 부분 적용 사례: 선행조건
- 선행조건 : 호출하는 함수에서 보장하는 조건
- 후행조건 : 선행조건이 지켜졌다는 가정 하에 함수 호출 결과를 보장하는 조건
- 선행조건과 후행조건의 관계를 '함수가 처리할 수 있는 데이터를 제공했을 때 함수는 특정기준을 만족하는 결과를 반환할 것이다.'
var zero = validator("cannot be zero", function(n) { return 0 === n
});
var number = validator("arg must be a number", _.isNumber);
function sqr(n) { // 선행 조건 추가 시 if문을 추가해야 한다.
if (!number(n)) throw new Error(number.message);
if (zero(n)) throw new Error(zero.message);
return n * n;
}
function condition1(/* validators */) {
var validators = _.toArray(arguments);
return function(fun, arg) {
var errors = mapcat(function(isValid) {
return isValid(arg) ? [] : [isValid.message];
}, validators);
if (!_.isEmpty(errors))
throw new Error(errors.join(", "));
return fun(arg);
};
}
var sqrPre = condition1(
validator("arg must not be zero", complement(zero)),
validator("arg must be a number", _.isNumber));
5.4 함수의 끝을 서로 연결하는 함수 조립 방법
- 한 쪽으로 데이터를 넣으면 반대편으로 완전히 새로운 데이터가 나올 수 있도록 함수들이 파이프라인을 이루고 있다면 가장 이상적인 함수형 프로그램이라고 할 수 있다.
- 함수형 조립은 여러 함수가 수행하는 데이터 변형을 모아서 데이터 체인을 이용, 새로운 함수를 만든 다.
- 아무 것도 변경하지 않은 상태에서 새로운 값을 얻을 수 있다.
!_.isString(name)
/**
_.isString과 연산자 ! 사이에서 파이프 라인이 만들어 진다.
_.isString은 객체를 인자로 받아 불린 값을 반환 한다.
!는 불린 값을 평가해서 반전한 값을 반환한다.
*/
var isntString = _.compose(function(x) { return !x }, _.isString);
isntString([]);
//=> true
'책정리 > 자바스크립트' 카테고리의 다른 글
함수형 자바스크립트 - 7장 순수성, 불변성, 변경 정책 (0) | 2017.02.04 |
---|---|
함수형 자바스크립트 - 4장 고차원 함수 (0) | 2017.02.03 |
함수형 자바스크립트 - 3장 변수 스코프와 클로저 (0) | 2017.01.14 |
함수형 자바스크립트 - 2장 일급 함수와 응용형 프로그래밍 (0) | 2017.01.09 |
함수형 자바스크립트 - 1장 함수형 자바스크립트 소개 (0) | 2017.01.09 |
댓글