Functional composition in Javascript - Lee-hyuna/33-js-concepts-kr GitHub Wiki

์›๋ณธ ์ถœ์ฒ˜ : 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(add110๊ณผ minus110)์„ ๋งŒ๋“ค์—ˆ์ง€๋งŒ, ๊ทธ๊ฒƒ์€ ์ฃผ์š” ์ž‘์—…ํ๋ฆ„์„ ๋ฐ˜๋ณตํ•œ๋‹ค.
๋‘ ๊ธฐ๋Šฅ ๋ชจ๋‘ ๋‘ ๊ฐœ์˜ ์ž‘์€ ๊ธฐ๋Šฅ์„ ์ทจํ•˜์—ฌ ์—ฐ์†์ ์œผ๋กœ ์ ์šฉํ•˜๋ฏ€๋กœ, ๊ทธ๋กœ๋ถ€ํ„ฐ ํŒจํ„ด์„ ์ถ”์ถœํ•˜์—ฌ ํ•ฉ์„ฑ(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)))

๊ฒฐ๋ก 

๊ธฐ๋Šฅ ๊ตฌ์„ฑ์€ ์ผ๋ถ€ ๋ฐ์ดํ„ฐ์— ์ผ๋ จ์˜ ๊ธฐ๋Šฅ์„ ์ ์šฉํ•˜๋Š” ๋ฐ ์œ ์šฉํ•œ ๋„๊ตฌ๊ฐ€ ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฝ๋‹ค. ์ž‘์€ ๋‹จ์ผ ์šฉ๋„์˜ ๊ธฐ๋Šฅ์œผ๋กœ ๊ตฌ์„ฑ๋˜๋ฏ€๋กœ ํ…Œ์ŠคํŠธ ๋ฐ ์žฌ์‚ฌ์šฉ์— ๋„์›€์ด ๋œ๋‹ค. ๋˜ํ•œ ๋ณ€ํ™˜์˜ ๊ฐœ๋ณ„ ์„น์…˜์„ ์กฐํ•ฉํ•˜์—ฌ ๋ณด๋‹ค ์œ ์—ฐํ•œ ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.