Functional Programming Jargon
FP (Functional Programming)๋ ๋ง์ ์ด์ ์ ์ ๊ณตํ๋ฉฐ ๊ฒฐ๊ณผ์ ์ผ๋ก ์ธ๊ธฐ๊ฐ ๋์์ง๊ณ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ๊ฐ ํ๋ก๊ทธ๋๋ฐ ํจ๋ฌ๋ค์์๋ ๊ณ ์ ํ ํน์ ์ฉ์ด๊ฐ ํฌํจ๋์ด ์์ผ๋ฉฐ FP๋ ์์ธ๋ ์๋๋๋ค. ์ฉ์ด์ง์ ์ ๊ณตํจ์ผ๋ก์จ FP ํ์ต์ด ์ฌ์์ง๊ธธ ๋ฐ๋๋๋ค.
์์ ๋ JavaScript (ES2015)๋ก ์ ๊ณต๋ฉ๋๋ค. Why JavaScript?
์ด๊ฒ์ WIP์ ๋๋ค; PR์ ๋ณด๋ด ์ฃผ์๊ธฐ ๋ฐ๋๋๋ค;)
ํด๋น๋๋ ๊ฒฝ์ฐ, ์ด ๋ฌธ์๋ Fantasy Land spec์ ์ ์๋ ์ฉ์ด๋ฅผ ์ฌ์ฉํฉ๋๋ค.
Table of Contents
- Arity
- Higher-Order Functions (HOF)
- Partial Application
- Currying
- Closure
- Auto Currying
- Function Composition
- Continuation
- Purity
- Side effects
- Idempotent
- Point-Free Style
- Predicate
- Contracts
- Category
- Value
- Constant
- Functor
- Pointed Functor
- Lift
- Referential Transparency
- Equational Reasoning
- Lambda
- Lambda Calculus
- Lazy evaluation
- Monoid
- Monad
- Comonad
- Applicative Functor
- Morphism
- Setoid
- Semigroup
- Foldable
- Lens
- Type Signatures
- Algebraic data type
- Option
- Functional Programming Libraries in JavaScript
Arity
ํจ์๊ฐ ์ทจํ๋ ์ธ์์ ๊ฐฏ์์ ๋๋ค. ๋จํญ, ์ด์, ์ผํญ ๋ฑ์ ๋จ์ด์์ ์ด ๋จ์ด๋ ๋ ๊ฐ์ ์ ๋ฏธ์ฌ "--ary"์ "--ity"๋ก ๊ตฌ๋ถ๋๋ค๋ ๊ตฌ๋ณ์ด ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ๋ํ๊ธฐ๋ ๋ ๊ฐ์ ์ธ์๋ฅผ ์ทจํ๋ฏ๋ก ์ด์ง ํจ์ ๋๋ 2์ arity๋ฅผ โโ๊ฐ๋ ํจ์๋ก ์ ์๋ฉ๋๋ค. ๊ทธ๋ฌํ ํจ์๋ ๋ผํด์ด์์ ๊ทธ๋ฆฌ์ค์ด๋ก ๋ฟ๋ฆฌ๋ฅผ ์ ํธํ๋ ์ฌ๋๋ค์ ์ํด ๋๋๋ก "dyadic"์ด๋ผ๊ณ ๋ถ๋ฆด ์ ์์ต๋๋ค. ๋น์ทํ๊ฒ ๋ณ์ ๊ฐ์์ ์ธ์๋ฅผ ์ทจํ๋ ํจ์๋ "variadic"์ด๋ผ๊ณ ๋ถ๋ฆฌ๋ ๋ฐ๋ฉด, 2์ง ํจ์๋ currying๊ณผ ๋ถ๋ถ ์ ์ฉ์๋ ๋ถ๊ตฌํ๊ณ ๋ ๊ฐ์ ์ธ์๋ง ์ ๊ณตํด์ผํฉ๋๋ค (์๋ ์ฐธ์กฐ).
const sum = (a, b) => a + b
const arity = sum.length
console.log(arity) // 2
// The arity of sum is 2
Higher-Order Functions (HOF)
ํจ์๋ฅผ ์ธ์๋ก ์ฌ์ฉํ๊ฑฐ๋ ํจ์๋ฅผ ๋ฐํํ๋ ํจ์์ ๋๋ค.
const filter = (predicate, xs) => xs.filter(predicate)
const is = (type) => (x) => Object(x) instanceof type
filter(is(Number), [0, '1', 2, null]) // [0, 2]
Partial Application
ํจ์๋ฅผ ๋ถ๋ถ์ ์ผ๋ก ์ ์ฉํ๋ค๋ ๊ฒ(Partial Application)์ ์ธ์ ์ค ์ผ๋ถ๋ฅผ ์๋ ํจ์์ ๋ฏธ๋ฆฌ ์ฑ์์ ์ ํจ์๋ฅผ ๋ง๋๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค.
// ๋ถ๋ถ์ ์ผ๋ก ์ ์ฉ๋ ํจ์๋ฅผ ๋ง๋๋ ๋์ฐ๋ฏธ
// ํจ์์ ๋ช ๊ฐ์ง ์ธ์๋ฅผ ์ทจํฉ๋๋ค.
const partial = (f, ...args) =>
// ๋๋จธ์ง ์ธ์๋ฅผ ์ทจํ๋ ํจ์๋ฅผ ๋ฐํํฉ๋๋ค.
(...moreArgs) =>
// ๊ทธ๋ฆฌ๊ณ ๋ชจ๋ ์๋ ํจ์๋ฅผ ํธ์ถํฉ๋๋ค.
f(...args, ...moreArgs)
// ๋ญ๊ฐ ์ ์ฉํ ๊ฒ
const add3 = (a, b, c) => a + b + c
// `2`์`3`์ ๋ถ๋ถ์ ์ผ๋ก `add3`์ ์ ์ฉํ๋ฉด ํ๋์ ์ธ์๋ก ์ธ ์ ์์ต๋๋ค
const fivePlus = partial(add3, 2, 3) // (c) => 2 + 3 + c
fivePlus(4) // 9
Function.prototype.bind
๋ฅผ ์ฌ์ฉํ์ฌ ๋ถ๋ถ์ ์ผ๋ก ์ ์ฉํ ์๋ ์์ต๋๋ค :
const add1More = add3.bind(null, 2, 3) // (c) => 2 + 3 + c
Partial application์ ์ฌ์ฉํ๋ฉด ๋ฐ์ดํฐ๋ฅผ ๋ณด์ ํ ๋ ๋ฐ์ดํฐ๋ฅผ ์กฐ์ํด์ ๋ณด๋ค ๊ฐ๋จํ ํจ์๋ฅผ ์์ฑํ ์ ์์ต๋๋ค. Curried ํจ์๋ ๋ถ๋ถ์ ์ผ๋ก ์๋ ์ ์ฉ๋ฉ๋๋ค.
Currying
์ฌ๋ฌ ์ธ์๋ฅผ ์ฌ์ฉํ๋ ํจ์๋ฅผ ํ ๋ฒ์ ํ๋์ฉ ์ฌ์ฉํ๋ ํจ์๋ก ๋ณํํ๋ ํ๋ก์ธ์ค์ ๋๋ค.
ํจ์๊ฐ ํธ์ถ๋ ๋๋ง๋ค ํ๋์ ์ธ์๋ง ํ์ฉํ๊ณ ๋ชจ๋ ์ธ์๊ฐ ์ ๋ฌ๋ ๋๊น์ง ์ธ์ ํ๋๋ฅผ ์ทจํ๋ ํจ์๋ฅผ ๋ฆฌํดํฉ๋๋ค.
const sum = (a, b) => a + b
const curriedSum = (a) => (b) => a + b
curriedSum(40)(2) // 42.
const add2 = curriedSum(2) // (b) => 2 + b
add2(10) // 12
Closure
ํด๋ก์ (Closure)๋ ๋ฒ์ ์ธ๋ถ์ ๋ณ์์ ์ก์ธ์คํ๋ ๋ฐฉ๋ฒ์ ๋๋ค. ๊ณต์์ ์ผ๋ก ํด๋ก์ ๋ ์ ์ ์ค์ฝํ๊ฐ ์ง์ ๋ ๋ฐ์ธ๋ฉ์ ๊ตฌํํ๋ ๊ธฐ์ ์ ๋๋ค. ์ด๊ฒ์ ํ๊ฒฝ๊ณผ ์คํ์ฝ๋(ํจ์)๋ฅผ ํํํฉ๋๋ค.
ํด๋ก์ ๋ ์คํ์ด ์ ์๋ ๋ธ๋ก ๋ฐ์ผ๋ก ์ด๋ํ ํ์๋ ํจ์์ ๋ก์ปฌ๋ณ์๋ฅผ ์ก์ธ์ค์ฉ์ผ๋ก ์บก์ฒํ๋ ๋ฒ์์ ๋๋ค. ์ฆ. ๋ณ์๊ฐ ์ ์ธ๋ ๋ธ๋ก์ด ์คํ์ ๋ง์น ํ์ ๋ฒ์๋ฅผ ์ฐธ์กฐํ ์ ์์ต๋๋ค.
const addTo = x => y => x + y
var addToFive = addTo(5)
addToFive(3) // returns 8
addTo()
ํจ์๋ (๋ด๋ถ์ ์ผ๋ก add ()
๋ผ๊ณ ๋ถ๋ฆฌ๋) ํจ์๋ฅผ ๋ฐํํ๋ฉฐ ๋งค๊ฐ ๋ณ์ 5๋ฅผ ๊ฐ๋ curry ํธ์ถ๋ก addToFive
๋ผ๋ ๋ณ์์ ์ ์ฅํฉ๋๋ค.
์ด์์ ์ผ๋ก addTo
ํจ์๊ฐ ์คํ์ ๋๋ด๋ฉด ๊ทธ ๋ฒ์๋ ์ง์ญ ๋ณ์ add, x, y๋ก ์ ๊ทผ ํ ์ ์์ด์ผํฉ๋๋ค. ๊ทธ๋ฌ๋ addToFive()
๋ฅผ ํธ์ถํ๋ฉด 8์ ๋ฐํํฉ๋๋ค. ์ด๋ ์ฝ๋ ๋ธ๋ก์ด ์คํ์ ๋ง์น ํ์๋ addTo
ํจ์์ ์ํ๊ฐ ์ ์ฅ๋๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด addTo
๊ฐ addTo(5)
๋ก ํธ์ถ๋๊ณ x ๊ฐ์ด 5๋ก ์ค์ ๋์์์ ์ ์ ์๋ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
์ดํ์ ์ ํจ ๋ฒ์๋ x ๋ฐ add ๊ฐ (์คํ์ ์๋ฃํ ๋ถ๋ชจ์ ๊ฐ์ธ ๋ณ์)์ ์ฐพ์ ์ ์๋ ์ด์ ์ ๋๋ค. ์ด ๊ฐ์ Closure๋ผ๊ณ ํฉ๋๋ค.
์คํ์ ํจ์์ ์ดํ์ ์ ํจ ๋ฒ์์ ํจ๊ป ๋ถ๋ชจ์ ๋ํ ์ฐธ์กฐ ํ์์ผ๋ก ์ ์ฅ๋ฉ๋๋ค. ์ด๋ ๊ฒํ๋ฉด ํด๋ก์ ์ ๊ธฐ๋ณธ๋ณ์๊ฐ ๊ฐ๋น์ง ์ปฌ๋ ์ ๋๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค.(์ ์ด๋ ํ๋์ ์ค์ ์ฐธ์กฐ๊ฐ ์์ผ๋ฏ๋ก)
Lambda Vs Closure :
Lambda๋ ๊ธฐ๋ณธ์ ์ผ๋ก ํจ์๋ฅผ ์ ์ธํ๋ ํ์ค ๋ฐฉ๋ฒ์ด ์๋ ์ธ๋ผ์ธ์ผ๋ก ์ ์๋ ํจ์์ ๋๋ค. Lambda๋ ์ข ์ข ๊ฐ์ฒด๋ก ์ ๋ฌ๋ ์ ์์ต๋๋ค.
Closure๋ ๋ชธ์ ์ธ๋ถ ํ๋๋ฅผ ์ฐธ์กฐํ์ฌ ์ฃผ๋ณ์ํ๋ฅผ ๋๋ฌ์ผ ํจ์์ ๋๋ค. ๋ซํ์ํ๋ ํด๋ก์ ์ ํธ์ถ์ ๊ฐ๋ก์ง๋ฌ ์ ์ง๋ฉ๋๋ค.
์ถ๊ฐ ์ฝ๊ธฐ / ์ถ์ฒ
Auto Currying
์ฌ๋ฌ ๊ฐ์ ์ธ์๋ฅผ ์ทจํ๋ ํจ์๋ฅผ ํ๋์ ์ธ์๋ก ๋ณํํ๋ฉด ์ฌ๋ฐ๋ฅธ ์์ ์ธ์๋ณด๋ค ์์ผ๋ฉด ๋๋จธ์ง๋ฅผ ์ทจํ๋ ํจ์๊ฐ ๋ฐํ๋ฉ๋๋ค. ํจ์๊ฐ ์ฌ๋ฐ๋ฅธ ์์ ์ธ์๋ฅผ ์ป์ผ๋ฉด ํ๊ฐ๋ฉ๋๋ค.
lodash & Ramda๋ ์ด๋ฐ์์ผ๋ก ์๋ํ๋ curry
ํจ์๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค.
const add = (x, y) => x + y
const curriedAdd = _.curry(add)
curriedAdd(1, 2) // 3
curriedAdd(1) // (y) => 1 + y
curriedAdd(1)(2) // 3
์ถ๊ฐ ์ฝ๊ธฐ
Function Composition
ํ๋์ ํจ์ ์ถ๋ ฅ์ด ๋ค๋ฅธ ํจ์์ ์ ๋ ฅ์ธ ์ 3์ ํจ์๋ฅผ ํ์ฑํ๊ธฐ ์ํด ๋ ํจ์๋ฅผ ํจ๊ป ๋ชจ์ผ๋ ํ์์ ๋๋ค.
const compose = (f, g) => (a) => f(g(a)) // ์ ์
const floorAndToString = compose((val) => val.toString(), Math.floor) // ์ฌ์ฉ๋ฒ
floorAndToString(121.212121) // '121'
Continuation
ํ๋ก๊ทธ๋จ์ ํน์ ์ง์ ์์ ์ฝ๋์ ์คํ๋์ง ์์ ๋ถ๋ถ์ ๊ณ์(Continuation)์ด๋ผ๊ณ ํฉ๋๋ค.
const printAsString = (num) => console.log(`Given ${num}`)
const addOneAndContinue = (num, cc) => {
const result = num + 1
cc(result)
}
addOneAndContinue(2, printAsString) // 'Given 3'
๋น๋๊ธฐ ํ๋ก๊ทธ๋๋ฐ์์๋ ํ๋ก๊ทธ๋จ์ ๊ณ์ ์งํํ๊ธฐ ์ ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ธฐ ์ํด ๋๊ธฐํด์ผํ๋ ๊ฒฝ์ฐ๊ฐ ์ข ์ข ์์ต๋๋ค. ์๋ต์ ํ๋ก๊ทธ๋จ์ ๋๋จธ์ง ๋ถ๋ถ์ผ๋ก ์ ๋ฌ๋๋ ๊ฒฝ์ฐ๊ฐ ์ข ์ข ์์ต๋๋ค.
const continueProgramWith = (data) => {
// ๋ฐ์ดํฐ์ ํจ๊ป ํ๋ก๊ทธ๋จ์ ๊ณ์ํ๋ค.
}
readFileAsync('path/to/file', (err, response) => {
if (err) {
// handle error
return
}
continueProgramWith(response)
})
Purity
๋ฐํ ๊ฐ์ด ์ ๋ ฅ ๊ฐ์ ์ํด์๋ง ๊ฒฐ์ ๋๊ณ ๋ถ์ํจ๊ณผ๊ฐ ๋ฐ์ํ์ง ์์ผ๋ฉด ํจ์๋ ์์(Purity)ํฉ๋๋ค.
const greet = (name) => `Hi, ${name}`
greet('Brianne') // 'Hi, Brianne'
๋ค์๊ณผ ๊ฐ์ด:
window.name = 'Brianne'
const greet = () => `Hi, ${window.name}`
greet() // "Hi, Brianne"
์ ์์ ์ ์ถ๋ ฅ์ ํจ์ ์ธ๋ถ์ ์ ์ฅ๋ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํฉ๋๋ค...
let greeting
const greet = (name) => {
greeting = `Hi, ${name}`
}
greet('Brianne')
greeting // "Hi, Brianne"
... ๊ทธ๋ฆฌ๊ณ ์ด ํจ์ ๋ฐ์์ ์ํ๋ฅผ ์์ ํฉ๋๋ค.
Side effects
ํจ์ ๋๋ ํํ์์ ๊ฐ์ ๋ฐํํ๋ ๊ฒ๊ณผ๋ ๋ณ๋๋ก ์ธ๋ถ ๋ณ๊ฒฝ ๊ฐ๋ฅ ์ํ์ ์ํธ ์์ฉ (์ฝ๊ธฐ ๋๋ ์ฐ๊ธฐ)ํ๋ ๊ฒฝ์ฐ ๋ถ์ํจ๊ณผ(Side effects)๊ฐ ์๋ค๊ณ ํฉ๋๋ค.
const differentEveryTime = new Date()
console.log('IO is a side effect!')
Idempotent
ํจ์์ ๊ฒฐ๊ณผ๋ฅผ ๋ค์ ์ ์ฉํด๋ ๋ค๋ฅธ ๊ฒฐ๊ณผ๊ฐ ๋์ค์ง ์์ผ๋ฉด ํจ์๋ ๋ฉฑ๋ฑ์ฑ(Idempotent)์ ๊ฐ์ง๋๋ค.
f(f(x)) โ f(x)
Math.abs(Math.abs(10))
sort(sort(sort([2, 1])))
Point-Free Style
์ ์๊ฐ ์ฌ์ฉ๋ ์ธ์๋ฅผ ๋ช ์์ ์ผ๋ก ์๋ณํ์ง ์๋ ํจ์ ์์ฑ ๋ฐฉ์์ ๋๋ค. ์ด ์คํ์ผ์ ์ผ๋ฐ์ ์ผ๋ก currying ๋๋ ๋ค๋ฅธ Higher-Order functions์ ํ์๋กํฉ๋๋ค. A.K.A Tacit programming.
// Given
const map = (fn) => (list) => list.map(fn)
const add = (a) => (b) => a + b
// Then
// Not points-free - `numbers` is an explicit argument
const incrementAll = (numbers) => map(add(1))(numbers)
// Points-free - The list is an implicit argument
const incrementAll2 = map(add(1))
incrementAll
์ ๋งค๊ฐ ๋ณ์ numbers
๋ฅผ ์ฌ์ฉํ๊ณ ์๋ณํ๋ฏ๋ก Point-free ํ์ง ์์ต๋๋ค. incrementAll2
๋ ํจ์์ ๊ฐ์ ๊ฒฐํฉํ์ฌ ์์ฑ๋๋ฉฐ ์ธ์์ ๋ํด์๋ ์ธ๊ธํ์ง ์์ต๋๋ค. Point-freeํฉ๋๋ค.
points-free ํจ์ ์ ์๋ function
๋๋ =>
์์ด ์ผ๋ฐ์ ์ธ ํ ๋น์ฒ๋ผ ๋ณด์
๋๋ค.
Predicate
์ ๋ถ(Predicate)๋ ์ฃผ์ด์ง ๊ฐ์ ๋ํด ์ฐธ ๋๋ ๊ฑฐ์ง์ ๋ฆฌํดํ๋ ํจ์์ ๋๋ค. ์ ๋ถ(Predicate)์ ์ผ๋ฐ์ ์ธ ์ฌ์ฉ์ ๋ฐฐ์ด ํํฐ์ ์ฝ๋ฐฑ์ ๋๋ค.
const predicate = (a) => a > 2
;[1, 2, 3, 4].filter(predicate) // [3, 4]
Contracts
๊ณ์ฝ(Contracts)์ ๋ฐํ์์ ํจ์ ๋๋ ํํ์์ผ๋ก๋ถํฐ ํ์์ ์๋ฌด ๋ฐ ๋ณด์ฆ์ ์ง์ ํฉ๋๋ค. ์ด๊ฒ์ ํจ์ ๋๋ ํํ์์ ์ ๋ ฅ ๋ฐ ์ถ๋ ฅ์์ ์์๋๋ ์ผ๋ จ์ ๊ท์น์ผ๋ก ์๋ํ๋ฉฐ ์ผ๋ฐ์ ์ผ๋ก ๊ณ์ฝ ์๋ฐ์ ์ค๋ฅ๊ฐ ๋ณด๊ณ ๋ฉ๋๋ค.
// Define our contract : int -> int
const contract = (input) => {
if (typeof input === 'number') return true
throw new Error('Contract violated: expected int -> int')
}
const addOne = (num) => contract(num) && num + 1
addOne(2) // 3
addOne('some string') // Contract violated: expected int -> int
Category
๋ฒ์ฃผ ์ด๋ก ์ category๋ ๊ฐ์ฒด์ ๊ฐ์ฒด๊ฐ์ morphism ๋ชจ์์ง์ ๋๋ค. ํ๋ก๊ทธ๋๋ฐ์์ ์ฌ์ฉํ๋ category๋ ์ผ๋ฐ์ ์ผ๋ก ํ์ ์ ๊ฐ์ฒด๋ก, ํจ์๋ฅผ ๊ฐ์ฒด ์ฌ์ด์ morphism ์ผ๋ก ๊ฐ์ง๋ category์ ๋๋ค.
์ ํจํ Category๊ฐ ๋๊ธฐ ์ํด์๋ 3 ๊ฐ์ง ๊ท์น์ด ์ถฉ์กฑ๋์ด์ผํฉ๋๋ค:
- ๊ฐ์ฒด๋ฅผ ๊ทธ ์์ฒด๋ก ๋งคํํ๋ identity morphism์ด ์์ด์ผํฉ๋๋ค.
a
๊ฐ ์ด๋ค Category์ ๊ฐ์ฒด์ธ ๊ฒฝ์ฐ,a -> a
์ ํจ์๊ฐ ์์ด์ผํฉ๋๋ค. - morphism์ด ํฉ์ฑ๋์ผ ํฉ๋๋ค.
์ฌ๊ธฐ์
a
,b
๋ฐc
๋ ์ด๋ค ๋ฒ์ฃผ์ ๊ฐ์ฒด์ด๊ณ ,f
๋a -> b
์ morphism์ด๊ณg
๋b -> c
์ morphism์ ๋๋ค;(g โข f)(x)
๋g(f(x))
์ ๊ฐ์์ผํฉ๋๋ค. - ํฉ์ฑ์ ์ฐ๊ด์ฑ์ด ์์ด์ผํฉ๋๋ค.
f โข (g โข h)
๋(f โข g) โข h
์ ๊ฐ์์ผํฉ๋๋ค.
์ด ๊ท์น์ ๋งค์ฐ ์ถ์์ ์ธ ์์ค์์ ํฉ์ฑ์ ๊ด๋ฆฌํ๊ธฐ ๋๋ฌธ์ Category ์ด๋ก ์ ๊ฐ์ฒด๋ฅผ ํฉ์ฑํ๋ ์๋ก์ด ๋ฐฉ๋ฒ์ ๋ฐ๊ฒฌํ๋๋ฐ ํฐ ๋์์ด ๋ฉ๋๋ค.
์ถ๊ฐ ์ฝ๊ธฐ
Value
๋ณ์์ ํ ๋น ํ ์ ์๋ ๋ชจ๋ ๊ฒ์ ๋๋ค.
5
Object.freeze({name: 'John', age: 30}) // `freeze` ํจ์๋ ๋ถ๋ณ์ฑ์ ๊ฐ์ ํฉ๋๋ค.
;(a) => a
;[1]
undefined
Constant
ํ ๋ฒ ํ ๋นํ๋ฉด ๋ค์ ํ ๋นํ ์ ์๋ ๋ณ์์ ๋๋ค.
const five = 5
const john = Object.freeze({name: 'John', age: 30})
์์๋ ์ฐธ์กฐ ํฌ๋ช ์ฑ์ ๊ฐ์ง๋๋ค. ์ฆ, ๊ฒฐ๊ณผ์ ์ํฅ์ ๋ฏธ์น์ง ์๊ณ ๋ํ๋ด๋ ๊ฐ์ผ๋ก ๋ฐ๊ฟ ์ ์์ต๋๋ค.
์์ ๋ ์์๋ฅผ ์ฌ์ฉํ๋ฉด ๋ค์ ํํ์์ด ํญ์ true
๋ฅผ ๋ฐํํฉ๋๋ค.
john.age + five === ({name: 'John', age: 30}).age + (5)
Functor
๊ฐ์ฒด์ ๊ฐ ๊ฐ์ ์คํํ๋ฉด์ ์๋ก์ด ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๋์ map
ํจ์๋ฅผ ๊ตฌํํ๋ ๊ฐ์ฒด๋ ๋ ๊ฐ์ง ๊ท์น์ ๋ฐ๋ฆ
๋๋ค :
์ ์ ๋ณด์กด
object.map(x => x) โ object
ํฉ์ฑ ๊ฐ๋ฅ
object.map(compose(f, g)) โ object.map(g).map(f)
(์์์ ํจ์ f
, g
)
์๋ฐ ์คํฌ๋ฆฝํธ์ Functor์ธ Array
๋ ๋ ๊ฐ์ง Functor ๊ท์น์ ๋ฐ๋ฆ
๋๋ค:
;[1, 2, 3].map(x => x) // = [1, 2, 3]
๊ทธ๋ฆฌ๊ณ
const f = x => x + 1
const g = x => x * 2
;[1, 2, 3].map(x => f(g(x))) // = [3, 5, 7]
;[1, 2, 3].map(g).map(f) // = [3, 5, 7]
Pointed Functor
์์ ์ ๋จ์ผ ๊ฐ์ ๋ฃ๋ of
ํจ์๊ฐ ์๋ ๊ฐ์ฒด์
๋๋ค .
ES2015์์ Array๋ฅผ pointed functor๋ก ๋ง๋๋ Array.of
๋ฅผ ์ถ๊ฐํ์ต๋๋ค.
Array.of(1) // [1]
Lift
Lifting์ ๊ฐ์ ๊ฐ์ ธ ์์ functor์ ๊ฐ์ ๊ฐ์ฒด์ ๋ฃ๋ ๊ฒ ์ ๋๋ค. ํจ์๋ฅผ Applicative Functor๋ก ๋ค์ด ์ฌ๋ฆฌ๋ฉด ํด๋น Functor์๋ ์๋ ๊ฐ์ ๋ํด ํจ์๋ฅผ ์ ์ฉ ํ ์ ์์ต๋๋ค.
์ผ๋ถ ๊ตฌํ์์๋ lift
, liftA2
๋ผ ๋ถ๋ฆฌ๋ ํจ์๊ฐ functor์์ ํจ์๋ฅผ ๋ณด๋ค ์ฝ๊ฒ ์คํํ๋๋ก ํฉ๋๋ค.
const liftA2 = (f) => (a, b) => a.map(f).ap(b) // ์ด๊ฒ์ `ap`์ด์ง,`map`์ด ์๋๋ผ๋ ๊ฒ์ ์ฃผ์ํ์ญ์์ค.
const mult = a => b => a * b
const liftedMult = liftA2(mult) // ์ด ํจ์๋ ์ด์ ๋ฐฐ์ด๊ณผ ๊ฐ์ functor์์ ์๋ํฉ๋๋ค.
liftedMult([1, 2], [3]) // [3, 6]
liftA2(a => b => a + b)([1, 2], [3, 4]) // [4, 5, 5, 6]
map
๊ณผ ๊ฐ์ด ํ๋์ ์ธ์๋ฅผ ๊ฐ๋ ํจ์๋ฅผ ๋ค์ด ์ฌ๋ฆฌ๊ณ ์ ์ฉํ๋ ์ผ์ ํฉ๋๋ค.
const increment = (x) => x + 1
lift(increment)([2]) // [3]
;[2].map(increment) // [3]
Referential Transparency
ํ๋ก๊ทธ๋จ์ ๋์์ ๋ณ๊ฒฝํ์ง ์๊ณ ๊ฐ์ผ๋ก ๋์ฒด ํ ์ ์๋ ํํ์์ ์ฐธ์กฐํฌ๋ช ์ฑ(Referential Transparency)์ ๊ฐ์ง๋ค๊ณ ํฉ๋๋ค.
์ฐ๋ฆฌ๊ฐ greet ํจ์๋ฅผ ๊ฐ์ก๋ค๊ณ ํ๋ฉด :
const greet = () => 'Hello World!'
Hello World!
๋ฅผ ๋์ฒดํ ์ ์๋ greet()
๋ ์ฐธ์กฐํฌ๋ช
์ฑ(Referential Transparency)์
๋๋ค.
Equational Reasoning
์์ฉ ํ๋ก๊ทธ๋จ์ด ํํ์์ผ๋ก ํฉ์ฑ๋์ด ๋ถ์ํจ๊ณผ๊ฐ ์๋ ๊ฒฝ์ฐ ์์คํ ์ ๋ํ ์ง์ค์ ํด๋น ๋ถ๋ถ์์ ํ์๋ ์ ์์ต๋๋ค.
Lambda
๊ฐ์ฒ๋ผ ์ทจ๊ธ ๋ ์ ์๋ ์ต๋ช ์ ํจ์์ ๋๋ค.
;(function (a) {
return a + 1
})
;(a) => a + 1
๋๋ค๋ ์ข ์ข ๊ณ ์ฐจ ํจ์์ ์ธ์๋ก ์ ๋ฌ๋ฉ๋๋ค.
;[1, 2].map((a) => a + 1) // [2, 3]
๋ณ์์ ๋๋ค๋ฅผ ํ ๋นํ ์ ์์ต๋๋ค.
const add1 = (a) => a + 1
Lambda Calculus
ํจ์๋ฅผ ์ฌ์ฉํ์ฌ universal model of computation์ ๋ง๋๋ ์ํ ๋ถ์ผ์ ๋๋ค.
Lazy evaluation
๊ฒ์ผ๋ฅธ ํ๊ฐ(Lazy evaluation)๋ ๊ฐ์ด ํ์ํ ๋๊น์ง ํํ์ ํ๊ฐ๋ฅผ ์ง์ฐ์ํค๋ ํธ์ถ ๋ณ ํ๊ฐ ๋ฉ์ปค๋์ฆ์ ๋๋ค. ํจ์ํ ์ธ์ด์์๋ ๋ฌดํํ ๋ชฉ๋ก๊ณผ ๊ฐ์ ๊ตฌ์กฐ๊ฐ ํ์ฉ๋ฉ๋๋ค. ์ด ๊ตฌ์กฐ๋ ๋ช ๋ น ์์ ์ง์ ์ด ์ค์ํ ๋ช ๋ นํ ์ธ์ด์์๋ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
const rand = function*() {
while (1 < 2) {
yield Math.random()
}
}
const randIter = rand()
randIter.next() // ๊ฐ ์คํ๋ง๋ค ์์์ ๊ฐ์ด ์ฃผ์ด์ง๋ฉฐ ํ์์ ๋ฐ๋ผ ํํ์์ด ํ๊ฐ๋ฉ๋๋ค.
Monoid
๊ฐ์ฒด๋ฅผ ๊ฐ์ ํ์ ์ ๋ค๋ฅธ ๊ฐ์ฒด์ "๊ฒฐํฉ"ํ๋ ๊ธฐ๋ฅ์ ๊ฐ์ง ๊ฐ์ฒด์ ๋๋ค.
ํ๋์ ๊ฐ๋จํ monoid๋ ์ซ์ ์ถ๊ฐ์ ๋๋ค :
1 + 1 // 2
์ด ๊ฒฝ์ฐ number๋ ๊ฐ์ฒด์ด๋ฉฐ +
๋ ํจ์์
๋๋ค.
๊ฐ๊ณผ ๊ฒฐํฉ ๋ ๋ ๊ฐ์ ๋ณ๊ฒฝํ์ง ์๋ "ID"๊ฐ์ด ์์ด์ผํฉ๋๋ค.
์ถ๊ฐ ํ ID ๊ฐ์ 0
์
๋๋ค.
1 + 0 // 1
๋ํ ์์ ์ ๊ทธ๋ฃนํ๊ฐ ๊ฒฐ๊ณผ (์ฐ๊ด์ฑ)์ ์ํฅ์ ๋ฏธ์น์ง ์์์ผํฉ๋๋ค :
1 + (2 + 3) === (1 + 2) + 3 // true
๋ฐฐ์ด ์ฐ๊ฒฐ์ ๋ํ monoid๋ฅผ ํ์ฑํฉ๋๋ค :
;[1, 2].concat([3, 4]) // [1, 2, 3, 4]
์์ด๋ ๊ฐ์ ๋น ๋ฐฐ์ด []
์
๋๋ค :
;[1, 2].concat([]) // [1, 2]
identity์ compose ํจ์๊ฐ ์ ๊ณต๋๋ฉด, ํจ์ ์์ฒด๋ monoid๋ฅผ ํ์ฑํฉ๋๋ค :
const identity = (a) => a
const compose = (f, g) => (x) => f(g(x))
foo
๋ ํ๋์ ์ธ์๋ฅผ ์ทจํ๋ ํจ์์
๋๋ค.
compose(foo, identity) โ compose(identity, foo) โ foo
Monad
Monad๋ ์ด๋ค ํ์
๊ณผ ๊ทธ ํ์
์ ์ ์ฉํ ์ ์๋ of
์ chain
ํจ์์
๋๋ค. chain
์ ์ค์ฒฉ๋ ๊ฒฐ๊ณผ๋ฅผ ์ค์ฒฉ๋์ง ์๊ฒ ํ๋๊ฒ์ ์ ์ธํ๊ณ map
์ ๋น์ทํฉ๋๋ค.
// ๊ตฌํ
Array.prototype.chain = function (f) {
return this.reduce((acc, it) => acc.concat(f(it)), [])
}
// ์ฌ์ฉ๋ฒ
Array.of('cat,dog', 'fish,bird').chain((a) => a.split(',')) // ['cat', 'dog', 'fish', 'bird']
// map๊ณผ ๋น๊ต
Array.of('cat,dog', 'fish,bird').map((a) => a.split(',')) // [['cat', 'dog'], ['fish', 'bird']]
of
๋ ๋ค๋ฅธ ํจ์ํ ์ธ์ด์์ return
์ด๋ผ๊ณ ๋ ํฉ๋๋ค.
chain
์ ๋ค๋ฅธ ์ธ์ด์์flatmap
๊ณผ bind
๋ผ๊ณ ๋ ํฉ๋๋ค.
Comonad
extract
์ extend
ํจ์๋ฅผ ๊ฐ์ง ๊ฐ์ฒด์
๋๋ค.
const CoIdentity = (v) => ({
val: v,
extract () {
return this.val
},
extend (f) {
return CoIdentity(f(this))
}
})
Extract์ functor์์ ๊ฐ์ ์ทจํฉ๋๋ค.
CoIdentity(1).extract() // 1
Extend๋ comonad์์ ํจ์๋ฅผ ์คํํฉ๋๋ค. ์ด ํจ์๋ comonad์ ๋์ผํ ํ์ ์ ๋ฐํํด์ผ ํฉ๋๋ค.
CoIdentity(1).extend((co) => co.extract() + 1) // CoIdentity(2)
Applicative Functor
Applicative Functor๋ ap
ํจ์๋ฅผ ๊ฐ์ง ๊ฐ์ฒด์
๋๋ค. ap
์ ๊ฐ์ฒด์ ํจ์๋ฅผ ๊ฐ์ ํ์
์ ๋ค๋ฅธ ๊ฐ์ฒด์ ๊ฐ์ ์ ์ฉํฉ๋๋ค.
// ๊ตฌํ
Array.prototype.ap = function (xs) {
return this.reduce((acc, f) => acc.concat(xs.map(f)), [])
}
// ์์ ์ฌ์ฉ๋ฒ
;[(a) => a + 1].ap([1]) // [2]
์ด๋ ๋ ๊ฐ์ ๊ฐ์ฒด๊ฐ ์๊ณ ๊ทธ ๋ด์ฉ์ ์ด์ง ํจ์๋ฅผ ์ ์ฉํ๋ ค๋ ๊ฒฝ์ฐ์ ์ ์ฉํฉ๋๋ค.
// ๊ฒฐํฉํ๋ ค๋ ๋ฐฐ์ด
const arg1 = [1, 3]
const arg2 = [4, 5]
//๊ฒฐํฉ ๊ธฐ๋ฅ - ์ด๊ฒ์ด ์๋ํ๋ ค๋ฉด curryํด์ผํฉ๋๋ค.
const add = (x) => (y) => x + y
const partiallyAppliedAdds = [add].ap(arg1) // [(y) => 1 + y, (y) => 3 + y]
์ด๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ์ป๊ธฐ ์ํด ap
๋ผ๊ณ ๋ถ๋ฅผ ์์๋ ๋ฐฐ์ด์ ์ ๊ณตํฉ๋๋ค :
partiallyAppliedAdds.ap(arg2) // [5, 6, 7, 8]
Morphism
๋ณํ ํจ์์ ๋๋ค.
Endomorphism
์ ๋ ฅ ํ์ ์ด ์ถ๋ ฅ๊ณผ ๋์ผํ ํจ์์ ๋๋ค.
// uppercase :: String -> String
const uppercase = (str) => str.toUpperCase()
// decrement :: Number -> Number
const decrement = (x) => x - 1
Isomorphism
๊ตฌ์กฐ์ ์ผ๋ก ์์ฐ์ค๋ฝ๊ณ ๋ฐ์ดํฐ๊ฐ ์์ค๋์ง ์๋ ๋ ๊ฐ์ง ํ์ ๊ฐ์ฒด๊ฐ์ ๋ณํ์์ ๋๋ค.
์๋ฅผ ๋ค์ด, 2D ์ขํ๋ ๋ฐฐ์ด [2,3]
๋๋ ๊ฐ์ฒด {x: 2, y: 3}
๋ก ์ ์ฅ ๋ ์ ์์ต๋๋ค.
// ์๋ฐฉํฅ์ผ๋ก ๋ณํ ํ ํจ์๋ฅผ ์ ๊ณตํ๋ฉด isomorphic์ด ๋ฉ๋๋ค.
const pairToCoords = (pair) => ({x: pair[0], y: pair[1]})
const coordsToPair = (coords) => [coords.x, coords.y]
coordsToPair(pairToCoords([1, 2])) // [1, 2]
pairToCoords(coordsToPair({x: 1, y: 2})) // {x: 1, y: 2}
Setoid
๋์ผํ ํ์
์ ๋ค๋ฅธ ๊ฐ์ฒด๋ฅผ ๋น๊ตํ๋๋ฐ ์ฌ์ฉํ ์ ์๋ equals
ํจ์๊ฐ ์๋ ๊ฐ์ฒด์
๋๋ค.
๋ฐฐ์ด์ Setoid๋ก ๋ง๋ญ๋๋ค:
Array.prototype.equals = function (arr) {
const len = this.length
if (len !== arr.length) {
return false
}
for (let i = 0; i < len; i++) {
if (this[i] !== arr[i]) {
return false
}
}
return true
}
;[1, 2].equals([1, 2]) // true
;[1, 2].equals([0]) // false
Semigroup
๋์ผํ ํ์
์ ๋ค๋ฅธ ๊ฐ์ฒด์ ๊ฒฐํฉํ๋ concat
ํจ์๊ฐ ์๋ ๊ฐ์ฒด์
๋๋ค.
;[1].concat([2]) // [1, 2]
Foldable
๊ฐ์ฒด๋ฅผ ๋ค๋ฅธ ํ์
์ผ๋ก ๋ณํ ํ ์์๋ reduce
ํจ์๊ฐ ์๋ ๊ฐ์ฒด์
๋๋ค.
const sum = (list) => list.reduce((acc, val) => acc + val, 0)
sum([1, 2, 3]) // 6
Lens
Lens๋ ๋ค๋ฅธ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ์ํด getter์ non-mutating setter๋ฅผ ์์ผ๋ก ๋ง๋๋ ๊ตฌ์กฐ์ฒด (์ข ์ข ๊ฐ์ฒด ๋๋ ํจ์)์ ๋๋ค.
// Using [Ramda's lens](http://ramdajs.com/docs/#lens)
const nameLens = R.lens(
//๊ฐ์ฒด์ name ์์ฑ์ ๋ํ getter
(obj) => obj.name,
// ์ด๋ฆ ์์ฑ์ ๋ํ setter
(val, obj) => Object.assign({}, obj, {name: val})
)
์ฃผ์ด์ง ๋ฐ์ดํฐ ๊ตฌ์กฐ์ ๋ํด get๊ณผ set์ ์์ ์ฌ์ฉํ๋ฉด ๋ช ๊ฐ์ง ์ฃผ์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
const person = {name: 'Gertrude Blanch'}
// getter๋ฅผ ํธ์ถํ๋ค.
R.view(nameLens, person) // 'Gertrude Blanch'
// setter๋ฅผ ํธ์ถํ๋ค.
R.set(nameLens, 'Shafi Goldwasser', person) // {name: 'Shafi Goldwasser'}
// ๊ตฌ์กฐ์ฒด์ ๊ฐ์ ํจ์๋ฅผ ์คํํ๋ค.
R.over(nameLens, uppercase, person) // {name: 'GERTRUDE BLANCH'}
Lens๋ ํฉ์ฑ ๊ฐ๋ฅํฉ๋๋ค. ๋ฐ๋ผ์ ์ค์ฒฉ๋ ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ฒ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค.
// ์ด Lens๋ ๋น์ด์์ง ์์ ๋ฐฐ์ด์ ์ฒซ ๋ฒ์งธ ํญ๋ชฉ์ ์ด์ ์ ๋ง์ถฅ๋๋ค.
const firstLens = R.lens(
// ๋ฐฐ์ด์ ์ฒซ ๋ฒ์งธ ํญ๋ชฉ ๊ฐ์ ธ ์ค๊ธฐ
xs => xs[0],
// ๋ฐฐ์ด์ ์ฒซ ๋ฒ์งธ ํญ๋ชฉ์ ๋ํ ๋น ๋ณํ setter
(val, [__, ...xs]) => [val, ...xs]
)
const people = [{name: 'Gertrude Blanch'}, {name: 'Shafi Goldwasser'}]
// ๋น์ ์ด ๊ฐ์ ํ ์๋ ์๊ฒ ์ง๋ง, Lens๋ ์ผ์ชฝ์์ ์ค๋ฅธ์ชฝ์ผ๋ก ํฉ์ฑ๋ฉ๋๋ค.
R.over(compose(firstLens, nameLens), uppercase, people) // [{'name': 'GERTRUDE BLANCH'}, {'name': 'Shafi Goldwasser'}]
๋ค๋ฅธ ๊ตฌํ:
- partial.lenses - Tasty syntax sugar and a lot of powerful features
- nanoscope - Fluent-interface
Type Signatures
์ข ์ข JavaScript์ ํจ์์๋ ์ธ์์ ํ์ ๊ณผ ๋ฐํ ๊ฐ์ ๋ํ๋ด๋ ์ฃผ์์ด ํฌํจ๋ฉ๋๋ค.
์ปค๋ฎค๋ํฐ ์ ๋ฐ์ ๊ฑธ์ณ ์ฝ๊ฐ์ ์ฐจ์ด๊ฐ ์์ง๋ง ๋ค์ ํจํด์ ์์ฃผ ๋ฐ๋ฅด๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค:
// functionName :: firstArgType -> secondArgType -> returnType
// add :: Number -> Number -> Number
const add = (x) => (y) => x + y
// increment :: Number -> Number
const increment = (x) => x + 1
ํจ์๊ฐ ์ธ์๋ก ๋ค๋ฅธ ํจ์๋ฅผ ๋ฐ์๋ค์ด๋ฉด ๊ดํธ ์์ ์ธ์ฌ ์์ต๋๋ค.
// call :: (a -> b) -> a -> b
const call = (f) => (x) => f(x)
a
,b
, c
,d
๋ ์ธ์๊ฐ ์ด๋ค ํ์
์ด๋ ๋ ์ ์์์ ๋ํ๋ด๊ธฐ ์ํด ์ฌ์ฉ๋ฉ๋๋ค. ๋ค์ ๋ฒ์ ์ map
์ ์ด๋ค ํ์
์ ๊ฐ์ ๋ค๋ฅธ ํ์
์ธ b
ํ์
์ผ๋ก ๋ณํํ๋ ํจ์๋ฅผ ์ทจํฉ๋๋ค.์ด ํ์
์ a
ํ์
์ ๊ฐ์ ๋ฐฐ์ด์ด๊ณ , b
ํ์
์ ๊ฐ์ ๋ฐฐ์ด์ ๋ฐํํฉ๋๋ค.
// map :: (a -> b) -> [a] -> [b]
const map = (f) => (list) => list.map(f)
์ถ๊ฐ ์ฝ๊ธฐ
- Ramda's type signatures
- Mostly Adequate Guide
- What is Hindley-Milner? on Stack Overflow
Algebraic data type
๋ค๋ฅธ ํ์ ์ ์กฐํฉํ์ฌ ๋ง๋ ๋ณตํฉ ํ์ ์ ๋๋ค. ๋์ํ์ ์ ๋๊ฐ์ง ์ผ๋ฐ์ ์ธ ํด๋์ค๋ sum๊ณผ product์ ๋๋ค.
Sum type
ํฉ๊ณ ํ์ (Sum type)์ ๋ ํ์ ์ ๊ฒฐํฉ์ผ๋ก ๋ค๋ฅธ ํ์ ์ผ๋ก ๊ฒฐํฉ๋ฉ๋๋ค. ๊ฒฐ๊ณผ ํ์ ์์ ๊ฐ๋ฅํ ๊ฐ์ ์๋ ์ ๋ ฅ ํ์ ์ ํฉ์ด๊ธฐ ๋๋ฌธ์ sum์ด๋ผ๊ณ ํฉ๋๋ค.
JavaScript์๋ ์ด์ ๊ฐ์ ํ์
์ด ์์ง๋ง Set
์ ์ฌ์ฉํ์ฌ ๊ฐ์ฅ ํ ์ ์์ต๋๋ค:
// ์ฌ๊ธฐ์๋ Set๊ฐ ์๋๋ผ ์ด ๊ฐ๋ง ๊ฐ์ง ์์๋ค๊ณ ์์ํด๋ณด์ธ์.
const bools = new Set([true, false])
const halfTrue = new Set(['half-true'])
// weakLogic ํ์
์๋ bools๊ณผ halfTrue์ ๊ฐ์ sum์ด ๋ค์ด ์์ต๋๋ค.
const weakLogicValues = new Set([...bools, ...halfTrue])
ํฉ๊ณ ํ์ (Sum type)์ ๋๋๋ก ํฉ์งํฉ ํ์ , ์ฐจ๋ณํ๋ ํฉ์งํฉ ๋๋ ํ๊ทธ๊ฐ ์ง์ ๋ ํฉ์งํฉ์ด๋ผ๊ณ ํฉ๋๋ค.
JS์๋ ๊ฒฐํฉ ํ์ ์ ์ ์ํ๊ณ ์ฌ์ฉํ๋๋ฐ ๋์์ด ๋๋ couple libraries๊ฐ ์์ต๋๋ค.
Flow์ union types๊ณผ TypeScript์ Enums์ด ์ด์ ๊ฐ์ ์ญํ ์ ํฉ๋๋ค.
Product type
Product type์ ํ์ ์ ๋ ์ ์๊ณ ์๋ ๋ฐฉ์์ผ๋ก ๊ฒฐํฉํฉ๋๋ค.
// point :: (Number, Number) -> {x: Number, y: Number}
const point = (x, y) => ({ x, y })
๋ฐ์ดํฐ ๊ตฌ์กฐ์ ์ด ๊ฐ๋ฅํ ๊ฐ์ ๋ค๋ฅธ ๊ฐ์ ๊ฒฐ๊ณผ์ด๋ฏ๋ก ์ ํ์ด๋ผ๊ณ ํฉ๋๋ค. ๋ง์ ์ธ์ด์๋ ์ ํ ํ์ ์ ๊ฐ์ฅ ๊ฐ๋จํ ํ์์ธ ํํ ํ์ ์ด ์์ต๋๋ค.
Set theory์ ์ฐธ์กฐํ์ธ์.
Option
Option์ Some
๊ณผ None
์ผ๋ก ๋ถ๋ฆฌ๋ sum type์
๋๋ค.
Option์ ๊ฐ์ ๋ฐํํ์ง ์๋ ํจ์๋ฅผ ์์ฑํ๋ ๋ฐ ์ ์ฉํฉ๋๋ค.
// Naive definition
const Some = (v) => ({
val: v,
map (f) {
return Some(f(this.val))
},
chain (f) {
return f(this.val)
}
})
const None = () => ({
map (f) {
return this
},
chain (f) {
return this
}
})
// maybeProp :: (String, {a}) -> Option a
const maybeProp = (key, obj) => typeof obj[key] === 'undefined' ? None() : Some(obj[key])
Chain
์ Option
์ ๋ฐํํ๋ ์ํ์ค ํจ์์ ์ฌ์ฉํ์ธ์.
// getItem :: Cart -> Option CartItem
const getItem = (cart) => maybeProp('item', cart)
// getPrice :: Item -> Option Number
const getPrice = (item) => maybeProp('price', item)
// getNestedPrice :: cart -> Option a
const getNestedPrice = (cart) => getItem(obj).chain(getPrice)
getNestedPrice({}) // None()
getNestedPrice({item: {foo: 1}}) // None()
getNestedPrice({item: {price: 9.99}}) // Some(9.99)
Option
์ Maybe
๋ผ๊ณ ๋ํฉ๋๋ค. Some
์ ๋๋๋ก Just
๋ผ๊ณ ๋ถ๋ฆ
๋๋ค. None
์ Nothing
์ด๋ผ๊ณ ๋ ํฉ๋๋ค.
Functional Programming Libraries in JavaScript
- mori
- Immutable
- Ramda
- ramda-adjunct
- Folktale
- monet.js
- lodash
- Underscore.js
- Lazy.js
- maryamyriameliamurphies.js
- Haskell in ES6
- Sanctuary
P.S: ์ด ์ ์ฅ์๋ ํ๋ฅญํ contributions ๋๋ถ์ ์ฑ๊ณตํ์ต๋๋ค!