Skip to content

Functional composition in Javascript

Dia Lee edited this page Oct 31, 2019 · 1 revision

원본 출처 : https://joecortopassi.com/articles/functional-composition-in-javascript/

'functional composition'이 뭔가요?

Functional composition은 두 개 이상의 function을 가지고, 그 중 하나를 하나의 function으로 만드는 것을 말합니다. function을 단일 함수 호출로 합성하면 특정 동작의 파이프라인으로 사용하기 시작할 수 있다. 그런 다음 이러한 파이프라인을 구성하는 각 기능의 결과를 가져와 파이프라인의 다음 기능에 대한 인수로 사용할 수 있다.
function의 파이프라인을 만드는 이러한 접근 방식은 더 복잡해 보일 수 있지만, 결과적으로 데이터 작업 방식에 있어 더 많은 유연성을 제공하는 동시에 예측 가능성, 가독성, 테스트 가능성을 지원한다.
이 접근 방식의 또 다른 부작용은 이 접근 방식을 구성하는 function만큼 쉽게 구성할 수 있는 견고한 파이프라인을 만들 수 있다는 것이다.

어떻게 기능 구현을 하나요?

가장 단순한 형태에서 그것은 단지 두 가지 기능을 함께 녹여내는 function일 뿐입니다. 두 가지 function: add10 , add100

add10 = num => num + 10;
add100 = num => num + 100;

이 두가지 함수를 합치게 되면,

add110 = num => add10(add100(num))

이제 두 개의 작은/단일 용도의 function으로 구성된 유용한 function을 가지고 있다.
하지만 만약 우리가 비슷하지만 다른 무언가를 하고 싶다면?

minus10 = num => num - 10;
minus100 = num => num - 100;

어떻게 구성하는지 처음부터 다시 시작해야 할 것이다.

minus110 = num => minus10(minus100(num))

Higher Order Functions

그래서 우리는 작은 기능으로부터 두 가지 유용한 function(add110minus110)을 만들었지만, 그것은 주요 작업흐름을 반복한다.
두 기능 모두 두 개의 작은 기능을 취하여 연속적으로 적용하므로, 그로부터 패턴을 추출하여 합성(composition)이라고 하는 상위 오더(high order function) 함수로 만들 수 있는 기회를 준다.

 compose = (func1, func2) => arg => func2(func1(arg));

이제 수동으로 두 개의 분리된 합성 기능을 수동으로 만들어야 하는 대신에, 그것을 우리를 위해 할 높은 순서 기능을 가지고 있다. 이제 새로운 기능을 사용하여 add110 및 minus110 함수를 업데이트할 수 있다.

add110 = compose(add10, add100);
minus110 = compose(minus10, minus100);

그리고 order 갯수가 더 높은 구성요소이기 때문에, 각각의 케이스를 하드코딩하지 않고도 고유한 새로운 방식으로 기본함수를 재사용할 수 있다.

add10Minus100 = compose(add10, minus100);
add100Minus10 = compose(add100, minus10);

이 상위 오더 함수의 또 다른 흥미로운 사용 사례는, 기능이 자바스크립트의 '‘first class citizen'이며 변수에 의해 포착될 수 있기 때문에, boolean 조건을 기반으로 하여 완전히 새로운 기능을 구성할 수도 있다는 것이다.

func1 = (condition < 50)? minus10: add10
func2 = (age < 20)? add100: minus100
priceAdjuster = compose(func1, func2)

Composing Multiple Functions

당사의 higher order function은 유용하지만 두 가지 기능만 구성할 수 있다.
임의의 수의 기능을 함께 추가하려면 어떻게 해야 할까?
그게 훨씬 더 유용하겠지만, 어떻게 생겼을까?
놀랍게도 우리가 이미 쓴 것과 비슷하다.

compose = funcs => arg => funcs.reduce((a, b) => (arg) => b(a(arg)), i=>i)   

작동 방식을 이해하기 위해 주요 부분으로 나누어 보자.

The Function Signature

compose = funcs => arg => ...

이 function은 sign을 통해 funcs 인자(기능의 배열)을 간단한 함수로 만드는 것 같고,
arg는 새로운 function을 반환합니다. 때문에 한번에 모두 사용하여 우리의 모든 function을 composition하지 않아도 될 것을 의미한다. 재활용 가능한 함수를 만들었다. func은 javascript에서 사용되는 closure라고 불린다.

The Identity Function

만약 우리가 정말로 임의의 양의 목록을 만들기를 원한다면, 우리는 세이프가드를 만들어야 한다.
foo = 1 + 2와 같은 간단한 계산을 할 때 두 개의 숫자와 연산자가 있을 것으로 예상한다. 숫자 중 하나를 빼고 foo = 1 +를 입력하면 구문 오류가 발생한다. 마찬가지로, compose가 작동하려면 적어도 하나의 function이 필요합니다. 그렇지 않으면 null 또는 오류를 반환한다.

이 오류를 방지하기 위해 기본 함수를 ID 함수로 만듭니다. 이 함수는 목적상 '준 것을 되돌려 주는 기능'을 의미할 뿐이다. 말 그대로 pass-through이다.

i => i

위의 문법을 ES5로 바꾼다면,

function(i) {
    return i
}

init function에서 나머지 파이프라인을 제외함으로써 다음 두 가지를 보장한다.

우리는 항상 function을 반환할 것이다. function은 최소한 수정되지 않은 인수를 반환하는것이 composed pipeline의 결과이다.

The Reducer

그래서, curring를 통해 여러 가지의 함수를 사용해보았고, 기본 케이스는 아이덴티티 함수에 있다.
javascript의 Array.prototype.reduce()를 사용해보도록 하자.

funcs.reduce((a, b) => (arg) => b(a(arg))

일반적으로 reduce가 하는 일을 간단히 요약하자면, 3개의 주요 부분으로 구성되어 있다.
항목의 배열, 항목에 적용할 결합 함수 및 실행 결과(accumulator라 불린다)이다. 아주 간단한 예제를 소개하자면 아래와 같다.

[1,2,3].reduce((accumulator, current) => {
    return accumulator + current;
})
// (((1) + 2) + 3)
// returns 6

위의 코드는 명확한 표현이지만 단순한 표현으로 단축해서 사용할 수 있다.

funcs.reduce((a, b) => (arg) => b(a(arg))

응용해서,

funcs.reduce((composition, nextFunc) => {
    return arg => {
        const result = nextFunc(arg)
        return composition(result);
    }
})

그래서 우리가 작곡하는 기능에서 reduce로 하고 있는 것은 이전 function의 결과를 다음 기능에 대한 논쟁으로 계속 사용하는 기능의 파이프라인을 만드는 것다. 따라서 3가지 기능이 더해진다면 add100, minus10, double로 확장되는 파이프라인을 기본적으로 구축하고 있습니다.

myFunc = num => double(minus10(add100(num)))

결론

기능 구성은 일부 데이터에 일련의 기능을 적용하는 데 유용한 도구가 될 수 있으며 사용하기 쉽다. 작은 단일 용도의 기능으로 구성되므로 테스트 및 재사용에 도움이 된다. 또한 변환의 개별 섹션을 조합하여 보다 유연한 코드를 만들 수 있다.

Clone this wiki locally