쏙쏙 들어오는 함수형 코딩
함수형에 특화된 언어가 아닌 단지 함수형을 지원하는 언어에서 함수형 사고를 잘 녹여서 프로그래밍 하는 법
액션, 계산, 데이터
함수형 사고를 잘 이용하여 프로그래밍 할때의 이점이 있다. 함수형 개발자는 코드를 액션과 계산 데이터로 잘 구분하여 생각한다.
실행될때 실행시점, 순서, 횟수에 따라 결과가 크게 달라지는 부수효과가 큰(예상하기 힘든) 코드를 다른 코드와 격리하여 더 안전하게 사용하고 앱을 구성하는데 더 잘 계획할수 있게 된다.
1. 액션
함수안에 암묵적 인자가 있거나 전역변수나 공유되는 자원를 사용하여 실행시점, 순서, 횟수에 따라 결과가 크게 달라지는 코드.
2. 계산
암시적 인자가 없어서, 같은 입력에 대해 같은 결과가 나오는 순수함수
3. 데이터
함수형 프로그래밍에서 데이터는 불변성을 지닌다. 데이터가 변한다면, 그 데이터를 사용하는 다른 여러 함수에서 부수효과가 일어나므로 결과를 예상하기 힘들어지기 때문이다.
카피온 라이트
순수함수가 인자로 받은 데이터를 변이시키지 않고 부수효과 없이 안전하게 사용하기 위한 스킬
-
객체 하나를 변경할때
function objectSet(object, key, value) { var copy = Object.assign({}, object); copy[key] = value; return copy; } -
배열에서 특정 순서의 값을 변경할때
function arraySet(array, index, value) { var copy = array.slice(); copy[index] = value; return copy; } -
배열에서 특정 순서의 아이템에 속성을 변경할때
function setPropFromList(data, index, key, value) { var newList = data.slice(); var newItem = objectSet(newList, key, value); return arraySet(newList, index, newItem); }
방어적 복사
함수형으로 코딩된 부분은 데이터 변이에 대해 안전하므로 걱정없이 사용할수 있지만. 함수형으로 코딩되지 않는 모듈에서 넘어온 데이터는 항상 예상되지 못하게 변경될 여지가 있으므로 걱정의 대상이 된다.
방어적복사를 이용하면 걱정에서 해방될 수 있다.
하지만 얕은복사에비해 비용이 비싸므로(컴퓨터의 기억공간을 많이 사용)꼭 필요한 경우에만 사용해야 한다.
- 방어적 복사 코드 예제
// 외부에서 구현된 black_friday_promotion함수를 안전하게 쓰기위해 방어적복사 처리를 해줌 function black_friday_promotion_safe(cart) { var car_copy = deepCopy(cart); black_friday_promotion(cart_copy); return deepCopy(cart_copy); }
액션을 다룰때는 시퀀스 다이어그램을 이용하자
액션과 다른 코드를 잘 분리해놓으면, 액션의 흐름에 더 잘 집중하여 프로그래밍 문제를 해결할 수 있다.
이벤트 순서나, 비동기 타이밍 문제를 더 쉽게 해결할수 있다.
함수형 도구를 이용하면 코드나 데이터의 흐름 파악이 쉬어진다
간단한 개선 예
// 절차형
function proCedure(arr){
let result = [];
for(let i = 0; i<arr.length; i++){
result.push(arr[i] * 3);
}
return result;
}
// 함수형
function proCedure(arr){
return arr.map(el => el * 3);
}
약간 복잡한 개선 예
함수형에서 함수들을 연결하는 방식에는 메서드체이닝과 함수합성이 있다.
메서드 체이닝을 사용하면 객체에 특수한 자료형을 처리하는 메서드가 꼭 구현되어 있어야 한다 (예를들면map 처럼).
하지만 함수합성을 이용하면 특정한 데이터타입이나 메서드에 구애받지 않고 일반함수를 체이닝 할 수 있다.
// 절차형
function something(array = [1, 2, 3, 4, 5], window = 5) {
var answer = [];
for (var i = 0; i < array.length; i++) {
var sum = 0;
var count = 0;
for (var w = 0; w < window; w++) {
var idx = i + w;
if (idx < array.length) {
sum += array[idx];
count += 1;
}
}
answer.push(sum/count);
}
return answer;
}
// ----------------------------
// 자바스크립트를 이용한 함수형
function something1(array = [1, 2, 3, 4, 5], window = 5) {
var windows = array.map((_, index) => array.slice(index, index + window)); // [[1, 2, 3, 4, 5], [2, 3, 4, 5], [3, 4, 5], [4, 5], [5]]
return windows.map(average); // [3, 3.5, 4, 4.5, 5]
}
// 메서드 체이닝을 이용한 함수형
function something2(array = [1, 2, 3, 4, 5], window = 5) {
return array
.map((_, index) => array.slice(index, index + window))
.map(average); // [3, 3.5, 4, 4.5, 5];
}
// 함수합성 도구(pipe)와 커리(curry)를 이용한 함수형
/*
const pipe = (...funcs) => (init) => funcs.reduce((acc, item) => item(acc), init);
const curry = (fn, ...args) => args.length >= fn.length ? fn(...args) : (...nextArgs) => curry(fn, ...args, ...nextArgs);
const map = curry((func, data) => data.map(func));
*/
function something3(array = [1, 2, 3, 4, 5], window = 5) {
return pipe(
map((_, i) => array.slice(i, i + window)), // (array) => array.map(i => array.slice(i, i + window))),
map(average), // (array) => array.map(average),
)(array);
// ex> pipe의 경우는 체이닝에서 못하는 일반함수 연결이 가능
// return pipe(
// (n) => n + 1,
// (n) => n * 2,
// )(3);
}
function average(items) { // 누가봐도 명확히게 재사용할수 있는 코드도 부가적으로 생김
return items.reduce(plus, 0) / items.length;
}
function plus(a, b) {
return a + b;
}
어니언 아키텍트
계산식의 가장 위의 계층이 비지니스(또는 도메인) 코드가 있는 계층이다. 이 도메인 계층 이하는 잘 변경되지 않는 계산식들만 있으므로 크게 변경될 일이 없다.
사실 기획에 따라 기능 변경이나 기능추가가 많이 일어나는 경우는 액션층 이상에서 발생한다.
어이언 아키텍튼즌 정말 중요한 도메인 코드를 잦은 서비스 규칙 변경으로 보호할 수 있으므로 코드가 좀 더 안전해진다. 함수형으로 생각하고 코딩하다 보면 자연스럽게 얻게되는 이점이다.
- 함수 추상화 계층 예
- 5 (액션) 리스트 컴포넌트 | 상품 컴포넌트
- 4 (액션) 장바구니 리스트 이벤트 | 수량 추가 버튼 이벤트 | 수량 삭제 버튼 이벤트
- 3 (액션) 상품데이터fetch | 장바구니데이터fetch | 리스트 dom 업데이트 | 버튼 dom 업데이트 | 데이터기다리는동안 로딩이미지 그리기
- 2 (계산식) 장바구니 배열에서 아이템 추가 | 장바구니 배열의 아이템 순서 변경 | 장바구니 배열에 특정 아이템이있는지확인 | 장바구니에 들어있는 총가격 | 장바구니 배열에서 아이템제거
- 1 (계산식) setPropFromList | addPropFromList
- 0 (계산식) objectSet | arraySet | average | plus | sum | max
반응형 아키텍트
함수형 어니언 아키텍트와 반응형 아키텍트는 함께 사용할수 있다.