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)))
๊ฒฐ๋ก
๊ธฐ๋ฅ ๊ตฌ์ฑ์ ์ผ๋ถ ๋ฐ์ดํฐ์ ์ผ๋ จ์ ๊ธฐ๋ฅ์ ์ ์ฉํ๋ ๋ฐ ์ ์ฉํ ๋๊ตฌ๊ฐ ๋ ์ ์์ผ๋ฉฐ ์ฌ์ฉํ๊ธฐ ์ฝ๋ค. ์์ ๋จ์ผ ์ฉ๋์ ๊ธฐ๋ฅ์ผ๋ก ๊ตฌ์ฑ๋๋ฏ๋ก ํ ์คํธ ๋ฐ ์ฌ์ฌ์ฉ์ ๋์์ด ๋๋ค. ๋ํ ๋ณํ์ ๊ฐ๋ณ ์น์ ์ ์กฐํฉํ์ฌ ๋ณด๋ค ์ ์ฐํ ์ฝ๋๋ฅผ ๋ง๋ค ์ ์๋ค.