- 함수형 프로그래밍은 소프트웨어 개발의 복잡성을 최소화하는 개발 방식을 추구한다.
- 상태 변화를 최소화 하거나 아예 없애는 것은 복잡성을 줄일 수 있는 방법 중 하나다.
7.1 순수성
function randString(len) {
var ascii = repeatedly(len, partial1(rand, 26));
return _.map(ascii, function(n) {
return n.toString(36); // 책에 빠져 있음 오타...;;;
}).join('');
}
- randString은 함수에 다른 함수를 이용해 고수준 기능을 구현한 함수이다. 하지만 이전의 다른 차이점이 있다.
- 'randString'은 테스트를 할 수 있는 가?
7.1.1 순수성과 테스트의 관계
describe("randString", function() { //jasmine 툴
it("message", function() {
expect(randString()).to???(???); // ?? 안에 과연 무엇이 들어갈 것인가?
});
}
- ?? 안에 문자열을 넣으려고 시도할 수 있지만 생성되는 값이 매번 달라지므로 소용없다.
순수한 함수
* 오직 인자만을 이용해서 계산 결과가 만들어진다.
* 외적 요소에 영향을 받는 데이터에 의존하지 않는다.
* 자신의 바디 외부의 상태를 변화시킬 수 없는 구조다.
- randString은 인자를 이용해서 계산하지 않으므로 첫번째 규칙을 어긴다.
- 또한 randString은 자바스크립트에서 생성하는 임의의 문자열에 의존한다.
PI = 3.14;
function areaOfACircle(radius) {
return PI * sqr(radius);
}
areaOfACircle(3);
//=> 28.26
- 첫 번째 규칙을 어기는 또 다른 예
- PI = ""; 이렇게 수정할 수 있다.
- 자신의 제어권 밖에 있는 데이터에 의존하는 함수를 구현하는 것은 혼돈으로 빠지는 지름길이다.
7.1.2 순수성과 비순수성 구별하기
- 순수한 함수는 주어진 입력값과 예상된 출력값 테이블을 이용해서 검증할 수 있다.
- Date.now, console.log, this, 일부 전역 변수 등은 자바스크립트에서 비순수성을 야기하는 메서드와 함수다.
- 자바 스크립트는 객체 레버런스를 전달하므로 객체나 배열을 인자로 받는 모든 함수는 잠재적으로 비순수성을 일으킬 수 있다.
function generateRandomCharacter() { // 비순수
return rand(26).toString(36);
}
function generateString(charGen, len) { //순수한 부분
return repeatedly(len, charGen).join('');
}
function partial1(fun, arg1) {
return function(/* args */) {
var args = construct(arg1, arguments);
return fun.apply(fun, args);
};
}
var composedRandomString = partial1(generateString, generateRandomCharacter);
composedRandomString(10);
//=> "j18obij1jc"
- 위와 같이 순수한 부분과 비순수한 부분을 나눌 수 있다.
- 위과 같이 나누면 순수한 부분만 따로 테스트 할 수 있다.
- generateString은 고차원 함수 이므로 partial1을 이용하여 두 함수를 조립할 수 있다.
7.1.4 순수성
- 자바스크립트는 변수, 객체, 배열 슬롯의 자유로운 변환을 자바스크립트의 원동력으로 생각한다.
- 그러나 상태 변이를 자유롭게 남발한다면 조립 과정에 문제가 생길 수 있으며, 특정 코드가 어떤 영향을 미칠지 예측하기 힘들어지므로 결과적으로 코드를 테스트하기 어렵게 된다.
- 반면 순수한 함수를 이용하면 함수를 쉽게 조립할 수 있으며 코드에 함수를 같은 기능을 수행하는 다른 함수로 쉽게 교체하거나 원하는 결과값을 얻도록 바꿀 수 있다.
function second(a) { //nth를 이용해서 새로운 함수를 생성
return nth(a, 1);
}
nth([1,2,3], 1); //nth는 순수한 함수이다.//=> 2
function second(a) {
return a[1];
}
- nth는 일정한 결과 값을 반환한다.
- 자신이 제공한 배열을 변경하지 않는 다.
- second의 경우 기능은 그대로 유지하면서 nth의 정의를 다른 구현으로 대체할 수 있다.
7.1.5 순수성과 멱등의 관계
- 멱등이란 어떤 동작을 여러 번 실행한 결과와 한 번 실행한 결과가 같은 상황을 가리킨다.
var a = [1, [10, 20, 30], 3];
var secondTwice = _.compose(second, second);
second(a) === secondTwice(a);
//=> false
- second함수는 멱등이 아니다.
- 프로그램에서 상태 변화가 전혀 없을 수는 없지만 가능한 한 상태 변화를 줄이는 것이 바람직하다.
7.2 불변성
- 자바스크립트는 일부 데이터 형식만 불변성을 갖는 다. 문자열은 변환이 불가능한 데이터 형식 중 하나다.
var key = "lemongrab";
var obj = {lemongrab: "Earl"};
obj[key] === "Eark"//=> true
doSomethingThatMutatesStrings(key); // 이 함수는 책 전체 어디에도 없음..
obj[key];
//=> undefined
obj["lemonjon"];
//=> "Earl"
- 누가 저 위에 소스 좀 해석해주세요...;;;;
- 위와 같이 문자열을 바꿀 수 있으면 생기는 문제라는 데.. 'lemonjon' 이 오타인가?
- 밑에 예제에 객체는 바꿀 수 있다는 게 나오는 거 보면 객체를 수정한 건 아님...
- 각각의 변화가 어떤 상태 그리고 상태에 의존하는 다른 상태 사이의 미묘한 관계에 영향을 미친다면 결국 조그만 변화가 전체 상태에 영향을 미칠 수 있다.
- 함수형 프로그래밍에서는 변이가 일어나지 않는 상황이 가장 이상적이다.
'자신이 소유하지 않은 객체는 고치지 마라'
7.2.1 숲에서 나무가 쓰러질 때 소리가 나는 가?
function skipTake(n, coll) { // n번째 요소를 포함하는 배열을 반환하는 함수
var ret = [];
var sz = _.size(coll);
for(var index = 0; index < sz; index += n) {
ret.push(coll[index]); // index가 변이인가?
}
return ret;
}
- Array#push를 사용한 부분에 주목하자. 변이가 발생하지 않는 함수형 기법을 이용해서 skipTake를 구현할 수도 있다. 하지만 for 루프 구현은 작고 직관적이며 빠르다.
- skipTake의 사용자는 이와 같은 강제적인 접근 방식을 전혀 눈치 채지 못한다.
- 함수에서 어떤 일이 일어나는 외부로 '누출'되지 않는 다면 함수 내부의 문제일 뿐이라는 것이 함수를 추상화의 기본 단위로 삼으면서 얻는 장점이다.
- 함수를 지역 상태 변이가 외부로 누출되지 않도록 보호하는 경계로 활용할 수 있다.
- 사용자는 skipTake를 호출했을 때 넘겨준 배열이 그대로 유지되는 지 그리고 새로운 배열을 성공적으로 반환하는 지에만 관심을 가질 뿐이다.
7.2.2 불변성과 재귀의 관계
- 재귀는 순수성과 관련이 있다.
function summ(ary) {
var result = 0;
var sz = ary.length;
for (var i = 0; i < sz; i++)
result += ary[i]; //i, result가 변한다.
return result;
}
summ(_.range(1,11));
//=> 55
- 전통적인 함수형 언어의 지역 변수는 실제 변수가 아니라 변할 수 없는 값이다.
function summRec(ary, seed) {
if (_.isEmpty(ary))
return seed;
else
return summRec(_.rest(ary), _.first(ary) + seed);
}
summRec([], 0);
//=> 0
summRec(_.range(1,11), 0);
//=> 55
- 재귀를 사용하여 함수의 인자를 통해 상태와 변화가 관리된다.
- 더 빠른 속도로 동작하는 코드를 사용하지 않는 이유는 지역 상태를 바꿀 때 위험이 따르기 때문이다.
7.2.3 방어적인 얼리기와 복제
- 자바스크립트는 배열과 객체를 레퍼런스로 전달하므로 진정한 불변성을 갖지 못한다.
- 자바스크립트에서는 Object#freeze 메서드를 제공한다. 해당 메서드에 배열이나 객체를 인자로 제공해 호출하면 이후에 변이는 일어나지 않는 다.
- Object#isFrozen 메서드를 이용해서 객체나 배열이 정말로 얼었는 지 확인할 수 있다.
- Object#freeze의 문제점
- 전체 코드를 완벽하게 제어하지 못한다면 알아채기 힘든 에러가 발생할 수 있다.
- Object#freeze는 얕은 방식으로 동작하는 메서드다.
var x = [{a : [1,2,3], b : 4}, {c: {}}];
Object.freeze(x);
x[0]= ""; // 변하지 않음, 경고나 에러는 없음
x[0]['a'] = 5; // 변한다.
x = {}; // 역시 변함
function deepFreeze(obj) { //재귀적으로 깊은 복사를 하는 함수
if (!Object.isFrozen(obj))
Object.freeze(obj);
for (var key in obj) {
if (!obj.hasOwnProperty(key) || !_.isObject(obj[key]))
continue;
deepFreeze(obj[key]);
}
}
- deepFreeze(x) 로재귀적으로 탐색해 들어가면서 깊은 복사를 할 수 있다.
- 객체를 얼리기 전에 다음을 먼저 고려한다.
- 앝은 복사가 적절하다고 판달될 때는 _.clone을 사용한다.
- 구조체가 복제할 때는 deepClone을 사용한다.
- 순수한 함수로만 코드를 구현한다.
7.2.4 함수 수준에서 불변성 유지하기
- 함수의 순수성 정책을 준수한다면 둘 이상의 함수를 이용해서 새로운 동작을 구현해도 여전히 순수한 함수임을 확신할 수 있다.
var freq = curry2(_.countBy)(_.identity); //_.countBy는 입력배열을 변화시키지 않는 비파괴 동작 함수이다.
- 하지만 순수성을 꼭 유지할 필요는 없으면 객체의 내용을 바꿔야 할 때도 있다.
var person = {fname : "가"};
_.extend(person, {lname : "나"}, {age : 10}, {age : 20});
//=> {age: 20, fname: "가", lname: "나"}
person //=> {age: 20, fname: "가", lname: "나"}
- 위 함수의 문제는 person을 변이 시킨다.
function merge() {
return _.extend.apply(null, construct({}, arguments)); // {} 빈객체는 넘긴다.
}
- _.extend 함수가 순수한 함수는 아니지만 조금 고쳐 새로운 추상화를 만든 다.
- 첫번째 인자로 빈 객체를 넘겨 변이를 방지한다.
7.2.5 객체의 불변성 관찰
function Queue(elems) {
this._q = elems;
}
Queue.prototype = {
enqueue: function(thing) {
return new Queue(cat(this._q, [thing]));
}
};
var a = [1,2,3];
var q = new Queue(a);
q; //=> {_q :[1,2,3]}
var q2 = q.enqueue(4); //=> q2 = {_q :[1,2,3, 4]}
q; //=> {_q :[1,2,3]}
- 불변성 객체를 생성할 때는 값을 받으며 그 이후로는 값을 바꿀 수 없어야 한다.
- 불변성 객체에 수행하는 모든 동작의 결과로 새로운 인스턴스가 반환되어야 한다.
- 하지만 모든 문제가 해결된 거는 아니다.
a.push(5);
q; //=> {_q :[1,2,3, 5]}
var SaferQueue = function(elems) {
this._q = _.clone(elems); // 값을 복사한다.
}
SaferQueue.prototype = {
enqueue: function(thing) {
return new SaferQueue(cat(this._q, [thing]));
}
};
- 요소 집합 수준에서 불변성을 유지하는 것이 가장 바람직하다.
- q 인스턴스의 공개 필드 _q는 고칠 수 있게 외부로 노출되어 있다.
- 각자의 프로그래밍 기법이 최대한 안전성을 우지할 수 있도록 일정한 규칙을 지키는 것이 좋다.
7.2.6 객체는 대체로 저수준 동작이다.
var q = SaferQueue({}); //new 사용을 잊어먹었다.
function queue() { // 큐를 대신 생성하는 함수
return new SaferQueue(_.toArray(arguments));
}
var q = queue(1,2,3);
var enqueue = invoker('enqueue', SaferQueue.prototype.enqueue);
enqueue(q, 42); // 메서드 대신 호출하는 함수//=> {_q: [1, 2, 3, 42]}
- 직접 메서드를 호출하기보다는 함수를 이용함으로써 많은 유연성을 얻을 수 있다.
- 실제형식이 무엇인지 크게 걱정할 필요가 없다.
- 형식이나 메서드가 바뀌면 이들이 사용된 모든 곳을 바꾸는 것이 아니라 함수만 바꾸면 된다.
- 필요하면 함수에 선행조건이나 후행조건을 추가할 수 있다.
- 함수를 조립할 수 있다.
- 상황에 따라 적절한 형식을 반환할 수 있다.
7.3 변화 제어 정책
- 변이를 한 곳으로 고립시키는 방법이 있다.
- 객체를 직접 고치는 것보다 컨테이너에 객체를 담아서 객체가 아닌 컨테이너를 고치는 방법이 바람직하다.
var container = contain({name : "aa"})
container.set({name : "bbb"});
var before = {name : "aaa"};
before.name = "ccc";
- 컨테이너를 사용하면 간단한 접근 방법을 활용했을 뿐이지만 주어진 값이 어디서 바뀌는 지 쉽게 찾을 수 있다.
- 간접 접근 개념을 좀 더 확장할 수 있다.
컨테이너 개선
function Container(init) {
this._value = init;
};
Container.prototype = {
update: function(fun) { // 함수를 넘겨 새로운 값을 설정한다.
var args = _.rest(arguments);
var oldValue = this._value;
this._value = fun.apply(this, construct(oldValue, args));
return this._value;
}
};
var aNumber = new Container(42);
aNumber.update(function(n) { return n + 1 });
//=> 43
aNumber;
//=> {_value: 43}
오타
- p.182 소스코드 [return n.toString(36);] ->없음
- p.183 임의로 생성하는 숫자[문자열]
- p.191 소스....이해가 안감
'책정리 > 자바스크립트' 카테고리의 다른 글
함수형 자바스크립트 - 4장 고차원 함수 (0) | 2017.02.03 |
---|---|
함수형 자바스크립트 - 5장 함수로 함수 만들기 (0) | 2017.01.23 |
함수형 자바스크립트 - 3장 변수 스코프와 클로저 (0) | 2017.01.14 |
함수형 자바스크립트 - 2장 일급 함수와 응용형 프로그래밍 (0) | 2017.01.09 |
함수형 자바스크립트 - 1장 함수형 자바스크립트 소개 (0) | 2017.01.09 |
댓글