Functional Programming & Monad
Deep Dive into Functional Programming
1. Idempotence (๋ฉฑ๋ฑ ๋ฒ์น) ๐ฉโ๐ป
์ฐ์ฐ์ ์ฌ๋ฌ ๋ฒ ํ๋๋ผ๋ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง์ง ์๋ ์ฑ์ง์ ์๋ฏธํ๋ค. ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ ๋ฐ๋ก ์ด ๋ฉฑ๋ฑ ๋ฒ์น์ ๊ธฐ์ดํด ๋ฐ์ ํ ํ๋ก๊ทธ๋๋ฐ ๊ธฐ๋ฒ์ด๋ค.
1. Unary Operation (๋จํญ์ฐ์ฐ)
Monadic Operation, Unitary Operation ๋ผ๊ณ ๋ ๋ถ๋ฆฌ๋ฉฐ ์ด๋ค ์งํฉ S ์์ ์์ ์ผ๋ก ๊ฐ๋ ํจ์์ ๋ฉฑ๋ฑ์ฑ์ S ์ ๋ชจ๋ ์์ x ์ ๋ํด
f(f(๐ฅ)) = f(๐ฅ)
๊ฐ ์ฑ๋ฆฝํ๋ค๋ ์ฑ์ง์ด๋ค. ๋ฉฑ๋ฑ์ฑ์ ์ง๋ ํจ์๋ฅผ ๋ฉฑ๋ฑ ํจ์(Idempotent Function)๋ผ๊ณ ํ๋ค.
2. Identity Function (ํญ๋ฑ ํจ์)
f(๐ฅ) = ๐ฅ
๋ ํญ์ ์๊ธฐ ์์ ์ด๋ฏ๋ก ๋ฉฑ๋ฑ์ฑ์ ๋ง์กฑํ๋ค.
3. Constant Function (์์ ํจ์)
f(๐ฅ) = a
์ญ์ ๋ฉฑ๋ฑ์ฑ์ ๋ง์กฑํ๋ค.
2. Referential Transparency (์ฐธ์กฐ ํฌ๋ช ์ฑ) ๐ฉโ๐ป
1. Satisfying Referential Transparency
- ์ฐ์ ์ฐ์ฐ์ ์ฐธ์กฐ ์ ํฌ๋ช ํ๋ค. y = a x b ๋ ์ฌ๋ฌ ๋ฒ ์คํํ๋๋ผ๋ a, b ๊ฐ ๋์ผํ๋ค๋ฉด ๋งค๋ฒ ๋์ผํ y ๋ฅผ ๊ฐ๋๋ค.
- ํํ์๊ณผ ๊ด๋ จ๋ ๋ชจ๋ ํจ์๊ฐ ์์ ํจ์๋ผ๋ฉด ํํ์์ ์ฐธ์กฐ ์ ํฌ๋ช
ํ๋ค. ์ด๋ ์ด๋ ํ
Side Effect
๋ ์กด์ฌํ์ง ์์์ ์๋ฏธํ๋ค.
์ ์ธํ ํ๋ก๊ทธ๋จ๋ฐ, ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์ ์ฐธ์กฐ ํฌ๋ช ์ฑ์ ๋ง์กฑ์ํค๋ ๋ฐฉํฅ์ผ๋ก ์๋ํ๋ค.
2. Unsatisfying Referential Transparency
- ํ ๋น์ ์ฐธ์กฐ ์ ํฌ๋ช ํ์ง ์๋ค. ๐ฅ = ๐ฅ + 1 ์ ์ด๊ธฐ๊ฐ์ด 10์ผ ๊ฒฝ์ฐ 1๋ฒ ์คํํ๋ฉด 11์ด์ง๋ง 2๋ฒ ์คํํ๋ฉด 12๊ฐ ๋๋ค.
- ๋ ๋ค๋ฅธ ์๋ก today()๋ ์ฐธ์กฐ ์ ํฌ๋ช ํ์ง ์๋ค. ์ค๋ ์คํํ ๊ฒฐ๊ณผ์ ๋ด์ผ ์คํํ ๊ฒฐ๊ณผ๊ฐ ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ด๋ค.
๋ช
๋ นํ ํ๋ก๊ทธ๋๋ฐ์ ํ๋ก๊ทธ๋จ์ด ์คํ๋๋ ์์ ์ ๋ฐ๋ผ ํน์ ์์ ์๋ง ์ ํจ
ํ๋ค. ๋ฐ๋ผ์ ์ ์์ ์์๊ฐ ์ค์ํ๊ณ
์ด๋ ์ฐธ์กฐ ์ ํฌ๋ช
ํ์ง ์๋ค.
3. Function Composition (ํจ์์ ํฉ์ฑ) ๐ฉโ๐ป
1. Injection
One-to-One Function ์ด๋ผ๊ณ ๋ถ๋ฆฌ๋ฉฐ ๊ณต์ญ(Codomain)
์์ ํ์ด์ ๋ฐ๋ ๊ฒ ์ค ๋ ๊ฐ ์ด์์ ํ์ด์ ํ๊บผ๋ฒ์ ๋ฐ๋ ์์๊ฐ ์์์
์๋ฏธํ๋ค.
2. Surjection
Onto Function ์ด๋ผ๊ณ ๋ถ๋ฆฌ๋ฉฐ ๊ณต์ญ์์ ํ์ด์ ์ ๋ฐ๋ ์์๊ฐ ์์์ ์๋ฏธํ๋ค. ์ฆ, ๊ณต์ญ(Codomain) = ์น์ญ(Range)
์ด ์ฑ๋ฆฝ๋จ์
์๋ฏธํ๋ค.
3. Bijection
Injection & Surjection ์ด ์ฑ๋ฆฝ๋จ์ ์๋ฏธํ๋ค. ์ฆ, ์ ์์ญ(Domain)
์ ๋ชจ๋ ํ์ด์ด ๊ณต์ญ(Codomain)
์ ๋ชจ๋ ์์์ 1:1๋ก ๋์ํจ์
์๋ฏธํ๋ค. ์ฆ, ์ ์์ญ๊ณผ ๊ณต์ญ์ ์์์ ๊ฐ์๊ฐ ๊ฐ์ผ๋ฉฐ ๊ณต์ญ = ์น์ญ์ด ์ฑ๋ฆฝ๋จ์ ์๋ฏธํ๋ค.
4. Composition
ํจ์์ ๊ณต์ญ์ด ๋ค๋ฅธ ํจ์์ ์ ์์ญ๊ณผ ์ผ์นํ๋ ๊ฒฝ์ฐ, ๋ ํจ์๋ฅผ ์ด์ด ํ๋์ ํจ์๋ก ๋ง๋๋ ์ฐ์ฐ์ ํ ์ ์๋ค.
์์์ ์งํฉ A, B, C ๋ฐ ๋ ํจ์
f: A ~> B
g: B ~> C
๊ฐ ์กด์ฌํ ๋ f ์ ๊ณต์ญ๊ณผ g ์ ์ ์์ญ์ด ๊ฐ๋ค๋ฉด ํฉ์ฑ ํจ์ g โฆ f ๋ฅผ ์ ์ํ ์ ์๋ค.
g โฆ f: A ~> C
g โฆ f: x ~> g(f(๐ฅ))
๋ฐ๋ผ์ ์ ๊ทธ๋ฆผ์ ๊ฒฝ์ฐ (g โฆ f)(4) = 3
์ด ๋๋ค.
5. Associative Property (๊ฒฐํฉ ๋ฒ์น)
ํจ์์ ํฉ์ฑ์ด ์ ์๋ ์ ์๋ค๋ฉด ์ด๋ ์ฐ์ ์ฐ์ฐ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ๊ฒฐํฉ ๋ฒ์น์ ๋ง์กฑํ๋ค.
๐ฅ + (๐ฆ + z) = (๐ฅ + ๐ฆ) + z
์ธ ๊ฒ์ฒ๋ผ
f: X ~> Y
g: Y ~> Z
h: Z ~> W
๊ฐ ์ฃผ์ด์ก์ ๋
h โฆ (g โฆ f) = (h โฆ g) โฆ f: X ~> W
๋ฅผ ๋ง์กฑํ๋ค.
6. Disassemble of Composition
ํจ์์ ํฉ์ฑ์์ ์ฃผ์ํด์ผํ ๊ฒ์ด ์๋ค. ํ ๊ฐ์ง ์๋ฅผ ๋ค์ด๋ณด์.
f: X ~> Y
g: Y ~> Z
์์ g โฆ f
๊ฐ Bijection ์ผ ๋ ๊ฐ๊ฐ์ ํจ์ f ์ g ์ญ์ Bijection ์ด ์ฑ๋ฆฝํ ๊น?
g โฆ f
๋ฅผ ๋ณด๋ฉด g ์ ๊ณต์ญ = ์น์ญ์ด ์ฑ๋ฆฝ๋๋ฉฐ ๋ฐ๋๋์ ๋น๊ทผ์ f ์ ์ ์์ญ์ผ๋ก๋ถํฐ ์์ญ์ด ~> ๋ฐ๋๋
, ํ ๋ผ ~> ๋น๊ทผ
์ด 1:1 ๋์ํ๋ฏ๋ก
Bijection ์ด๋ค.
๋ฐ๋ฉด, f ๋ Injection ์ด์ง๋ง Surjection ์ด ์๋๊ณ , g ๋ Surjection ์ด์ง๋ง Injection ์ด ์๋๋ค. ์ฆ, ํฉ์ฑ ํจ์๊ฐ Bijection ์ด๋ผ๊ณ ๊ฐ๊ฐ์ ํจ์๊ฐ Bijection ์ธ ๊ฒ์ ์๋๋ค.
์ฐ๋ฆฌ๊ฐ ํ๋ก๊ทธ๋๋ฐ์ ํ๋ฉด์ ํจ์๋ฅผ ์์ฑํ ๋ ๊ทธ ํจ์๊ฐ ์์ ํจ์๋ผ๊ณ ํด์ Bijection ์ด ์ฑ๋ฆฝํ์ง๋ ์๋๋ค.
์๋ฅผ ๋ค์ด ์ด๋ค ํฉ์ฑ ํจ์
g โฆ f
๋ ์ ์๋ฅผ ๋ฐ์odd
์even
๋ ๊ฐ์ง case ๋ฅผ ๊ฐ๋ Enumeration ์ผ๋ก๋ง ์๋ตํ๋ค. ์ด ํจ์๋ Surjection ์ด๋ Injection ์ ์๋๋ค.
4. Lambda Calculus (๋๋ค ๋์) ๐ฉโ๐ป
1930๋ ๋ ์๋ก ์กฐ ์ฒ์น๊ฐ ํจ์๋ฅผ ์ถ์ํ ํด ๋จ์ํ๊ฒ ํํํ ์ ์๋๋ก ํ๊ธฐ ์ํด ๊ณ ์๋์๋ค.
๋๋ค ๋์์ ์ํ๋ฉด ํจ์ s(๐ฅ, ๐ฆ) = ๐ฅ x ๐ฅ + ๐ฆ x ๐ฆ
๋ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ ์ ์๋ค.
- ํจ์๊ฐ ๋ฐ๋์ ์ด๋ฆ์ ๊ฐ์ง ํ์๊ฐ ์๋ค. ํจ์์ ์ด๋ฆ s ๋ฅผ ์ ๊ฑฐํ๋ค.
(๐ฅ, ๐ฆ) ~> ๐ฅ x ๐ฅ + ๐ฆ x ๐ฆ
- ํจ์์ ์
๋ ฅ ๋ณ์ ์ด๋ฆ ๋ํ ํ์๊ฐ ์๋ค. ์๋ฅผ ๋ค์ด
๐ฅ ~> ๐ฅ
์๐ฆ ~> ๐ฆ
๋ ๋ณ์ ์ด๋ฆ์ ๋ค๋ฅด์ง๋ง ๊ฐ์ ํญ๋ฑํจ์๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก(๐ฅ, ๐ฆ) ~> ๐ฅ x ๐ฅ + ๐ฆ x ๐ฆ
์(๐ข, ๐ฃ) ~> ๐ข x ๐ข + ๐ฃ x ๐ฃ
๋ ๊ฐ์ ํจ์๋ฅผ ๋ํ๋ธ๋ค. - ๋ ๊ฐ ์ด์์ ์
๋ ฅ์ ๋ฐ๋ ํจ์๋ ํ๋์ ์
๋ ฅ์ ๋ฐ์ ๋ ๋ค๋ฅธ ํจ์๋ฅผ ์ถ๋ ฅํ๋ ํจ์๋ก ๋ค์ ์ธ ์ ์๋ค.
(๐ฅ, ๐ฆ) ~> ๐ฅ x ๐ฅ + ๐ฆ x ๐ฆ
๋๐ฅ ~> (๐ฆ ~> ๐ฅ x ๐ฅ + ๐ฆ x ๐ฆ)
์ ๊ฐ์ ํํ๋ก ๋ค์ ์ธ ์ ์๊ณ ์ด๋ฅผ Currying ์ด๋ผ ํ๋ค.
์ ํจ์(๐ฅ, ๐ฆ)
๋ ๋จ์ผ ์ ๋ ฅ ํจ์๋ฅผ ๋ ๋ฒ ์ ์ฉํ๋ ๊ฒ์ผ๋ก ์ดํดํ ์ ์๋ค.(๐ฅ ~> (๐ฆ ~> ๐ฅ x ๐ฅ + ๐ฆ x ๐ฆ))(5)(2) = (๐ฆ ~> 5 x 5 + ๐ฆ x ๐ฆ)(2) = 5 x 5 + 2 x 2
5. Functional Programming ๐ฉโ๐ป
1. Why Functional Programming?
์์ ํจ์๋ Side Effect ๊ฐ ์๊ณ ๊ฒฐ์ ๋ก ์ ์ด๋ค. ๋ฐ๋ผ์ ์ด๋ฐ ํจ์๋ฅผ ์ ์ธํ์ผ๋ก ์ฌ์ฉํ๊ณ ํจ์๋ฅผ ํฉ์ฑํ๋ฉด ๊ทธ ๊ฒฐ๊ณผ ํฉ์ฑ ํจ์ ์ญ์ Side Effect ๊ฐ ์์ผ๋ฉฐ ํ ์คํธ๊ฐ ์ฝ๋ค๋ ์ฅ์ ์ ๊ฐ๋๋ค.
๋ํ ํจ์๋ฅผ ์์ ๋จ์๋ก ์ชผ๊ฐ ๋์๊ธฐ ๋๋ฌธ์ ์ฌ์ฌ์ฉ์ด ์ฌ์ฐ๋ฏ๋ก ์์ ํจ์๋ค์ ํฉ์ฑํด ์๋ก์ด ํจ์๋ฅผ ๋ง๋๋๋ฐ ์ฌ์ฉํ ์ ์๋ค.
2. Pure Function
์์ ํจ์๋ ๋ค์ ํน์ง์ ๊ฐ๋๋ค.
- ํจ์์ Context ๋ Isolation ๋์ด์ผ ํ๋ค.
- ํจ์์ Parameters ๋ Immutable ํด์ผ ํ๋ค.
- ํจ์๋ Asynchronous ์๋ ์์ด ๊ฒฐ๊ณผ๋ฅผ ์ฆ์ ๋ฐํํด์ผ ํ๋ค.
- ํจ์๊ฐ ์์ธ๋ฅผ ๋ฐ์์ํค์ง ์์์ผ ํ๋ค.
1 ) ํจ์์ Context ๋ Isolation ๋์ด์ผ ํ๋ค
ํจ์๊ฐ Static/Global Variables ์ ๊ฐ์ context ์ธ๋ถ์ ์ํธ์์ฉ ํ ์ ์์์ ์๋ฏธํ๋ค. ํจ์ context ์ธ๋ถ์ ๋ณ์๋ ํจ์๊ฐ ์ ์ดํ ์ ์๊ธฐ ๋๋ฌธ์ ์ธ๋ถ ์์ธ์ ์ํด ํจ์์ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๊ฑฐ๋ ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค. ๋ฐ๋ผ์ ์์ ํจ์์ context ๋ ์ธ๋ถ ์์ธ์ผ๋ก๋ถํฐ ๊ฒฉ๋ฆฌ๋์ด์ผ ํ๋ค.
๋ํ, ํจ์๊ฐ Escaping Closures, Callback Functions ์ ๊ฐ์ context ๊ฐ ์ข ๋ฃ๋ ํ ์๋์ ํ์ฉํ์ง ์์์ผ ํ๋ค. ํจ์ context ๊ฐ ์ข ๋ฃ๋ ํ ์๋ํ๋ค๋ ๊ฒ ์์ฒด๊ฐ context ์ธ๋ถ์ ์ํธ์์ฉ ํ๋ค๋ ๊ฒ์ ์๋ฏธํ๋ค. ๋ฐ๋ผ์ ์์ ํจ์๋ context ๊ฐ ์ข ๋ฃ๋ ํ ์๋ํ๋ ์ฝ๋๊ฐ ์์ด์ผ ํ๋ค.
2 ) ํจ์์ Parameters ๋ Immutable ํด์ผ ํ๋ค
์ฆ, ํจ์์ Parameters ๋ Constants ๋ก ์๋ํด์ผํ๋ค.
Swift ์ ๊ฒฝ์ฐ ๊ธฐ๋ณธ์ ์ผ๋ก Parameters ๋ Copy ๋์ด ์ ๋ฌ๋๋ฉฐ, inout
์ผ๋ก ์ ์ธํ์ง ์๋ ํ Constants ๋ก ์ ์ธ๋๋ฏ๋ก ์๋์ผ๋ก ์์ ํจ์์
์กฐ๊ฑด์ ๋ง์กฑํ๋ค.
๋ฐ๋ฉด TypeScript ์ญ์ Parameters ๊ฐ Copy ๋์ด ์ ๋ฌ๋๋ ๊ฒ์ ๋์ผํ๋, Variables ๋ก ์ ์ธ๋์ด ์์ ์ด ๊ฐ๋ฅํ๋ค. TypeScript ์์ Parameters ์ Immutable ์ ์ฒ๋ฆฌํ๊ธฐ ์ํด์๋ ๋ค์๊ณผ ๊ฐ์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ์ ์๋ค.
- Parameters ๊ฐ Array ๋๋ Tuple Literal Types ์ธ ๊ฒฝ์ฐ
readonly
modifier ๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
function square(arr: readonly number[]): number[] {
return arr.map(n => n ** 2)
}
- ๊ทธ ์ธ Types ๋๋ JavaScript ์ธ ๊ฒฝ์ฐ ๋ด๋ถ์ ์ Constants ๋ฅผ ์ ์ธํ๊ณ Deep Copy ํ๋ค.
function greeting(_name: string, _age: number) {
const [name, age] = [_name, _age]
console.log(`Hello~ My name is ${name} and I amd ${age} years old.`)
}
3 ) ํจ์๋ Asynchronous ์๋ ์์ด ๊ฒฐ๊ณผ๋ฅผ ์ฆ์ ๋ฐํํด์ผ ํ๋ค
ํจ์๊ฐ Future, Promise, DispatchQueue, setTimeout ๊ณผ ๊ฐ์ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ํ์ง ์๊ณ ๊ฒฐ๊ณผ๋ฅผ ์ฆ์ ๋ฐํํด์ผํ๋ค.
์ฌ๊ธฐ์ ์ฆ์ ๋ฐํํด์ผํ๋ค๋ ๊ฒ ๋๋ฌธ์ Lazy Evaluation ์ ์คํดํ ์ ์์ผ๋, Evaluation ์ด ์ง์ฐ๋๋ ๊ฒ์ผ ๋ฟ Closure ๋ฅผ ์ฆ์ ๋ฐํํ๋ค.
4 ) ํจ์๊ฐ ์์ธ๋ฅผ ๋ฐ์์ํค์ง ์์์ผ ํ๋ค
์๋ฌ๋ฅผ throw ํ๋ค๋ ๊ฒ์ ๋ด๋ถ ์ฝ๋์ Side Effect ๊ฐ ์กด์ฌํ๋ค๋ ๊ฒ์ ์๋ฏธํ๋ค. ์๋ฅผ ๋ค์ด ์ด๋ค ํจ์๊ฐ ๊ณ์ฐ์ ํ๋๋ฐ ๊ฐ์ด nil ์ผ ๊ฐ๋ฅ์ฑ์ด ์๋ค๊ณ ํด๋ณด์. ์ด ๊ฒฝ์ฐ ์ฐ๋ฆฌ๋ Monad ๋ฅผ ์ด์ฉํด ์์ ํจ์๊ฐ ๋๋๋ก ๋ง๋ค ์ ์๋ค. Optional Monad ๋ฅผ ์ฌ์ฉํด Wrapping ์ํค๋ฉด ๊ฐ์ nil ์ ๋ฌด์ ๊ด๊ณ ์์ด ํจ์๋ ์์ํด์ง๋ค.
๋ฌผ๋ก , ์ค์ ๋ก ๊ฐ๋ฐํ ๋ ์ ์กฐ๊ฑด์ ๋ชจ๋ ๋ง์กฑํ๋ ์์ ํจ์๋ ๋ง์ง ์๋ค.
๊ฐ์ฅ ํํ ์๋ก ๋น๋๊ธฐ ๋ฌธ์ ๋ง ํด๋ ๊ทธ๋ ๋ค. ํ์ผ ์ ์ถ๋ ฅ์ด๋ ๋ฐ์ดํฐ ํต์ ์์ด ์์ํ๊ฒ ์ฝ๋๋ง์ผ๋ก ๋์๊ฐ๋ ๊ฒฝ์ฐ๋ ๊ฑฐ์ ์๊ธฐ ๋๋ฌธ์ด๋ค. ์ต๋ํ Side Effect ๋ฅผ ์ค์ด๊ธฐ ์ํด ๋ฐ์ดํฐ ํต์ ์ ํ๋ ๋ก์ง์ async/await ๋ฅผ ์ฌ์ฉํด context ๋ฐ์ผ๋ก ๋๊ฐ์ง ์๋๋ก ๊ฐ๊ธ์ ๋๊ธฐ ์ฝ๋์ธ ๊ฒ ์ฒ๋ผ ์๋ํ๋๋ก ํ๊ฑฐ๋ ํ์ผ ๋ณต์ฌ์ ๊ฐ์ ์์ ์์ ๋ถ๋ณ์ฑ ์๋ฐ์ ์ต์ํ ํ๊ธฐ ์ํด ํ๋์ ๋๊ธฐ ํจ์์ ๋ก์ง์ ์ ์ํ๋ ๊ฒ๊ณผ ๊ฐ์ ๋ ธ๋ ฅ์ ํ ์ ์์ง๋ง ์๋ฐํ ๋งํด ์์ ํจ์๋ผ ํ ์๋ ์๋ค.์ค์ ๋ก ๊ฐ๋ฐํ ๋ ์ด์ ์ ๋๊ณ ๊ณ ๋ฏผํด์ผํ ๊ฒ์ ์์ธก ๊ฐ๋ฅํ๊ฐ์ด๋ค. ์๋ฌ๊ฐ ์์ธก ๊ฐ๋ฅํ๊ณ ์ด๋ฅผ ์ปจํธ๋กค ๋ฐ ํ ์คํธ ๊ฐ๋ฅํ๋ค๋ฉด ์ด๋ ์์ ํจ์์ ์กฐ๊ฑด์ ๋ง์กฑํ๋๋ก Monad ๋ฅผ ์ด์ฉํด ์ฒ๋ฆฌํ๊ฑฐ๋ ๊ทธ๋ ์ง ๋ชปํ๋๋ผ๋ ์์ ํจ์ ์กฐ๊ฑด์ ๊ทผ์ ํ๋ค๊ณ ๋ณผ ์ ์๋ค.
6. Monad ๐ฉโ๐ป
1. Category Theory
Monad ๋ฅผ ์ดํดํ๊ธฐ ์ํด์๋ ์ด๋ค ์ด๋ก ์์ Monad ๋ผ๋ ๊ฐ๋ ์ด ๋์๋์ง๋ฅผ ์ดํดํด์ผํ๋ค. ์ด๊ฒ์ ์ํ์ ๋ฒ์ฃผ ์ค Category Theory ๋ผ๋ ํ๋ฌธ์์ ์์๋์๋ค.
์์์ ์ดํด๋ณธ ํจ์์ ํฉ์ฑ ์ด ๋ฐ๋ก ์ด Category Theory ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๋ ๊ฒ์ด๋ค.
Category Theory ์์๋ X, Y, Z ๋ฅผ Set
, ๊ทธ๋ฆฌ๊ณ f, g ๋ฅผ Morphism
์ด๋ผ ๋ถ๋ฅธ๋ค.
๊ทธ๋ฆฌ๊ณ Category Theory ๋ฅผ ์ผ๋ฐํ ์ํค๊ธฐ ์ํด ์ถ์ํ ํ๋ ๋จ๊ณ์์ Functor
, Applicative Functor
, Monad
์ ๊ฐ์ ๊ฒ๋ค์
์ดํดํด์ผํ๋ค.
์ฐ์ Functor ์ ๋ํด ์์๋ณด์. Functor ๊ฐ ๊ฐ์ฅ ์ผ๋ฐํ๋ ๊ฐ๋
์ด๊ณ , ์ด๊ฒ์ ๋ค์ ๊ทธ๋ฆผ์ฒ๋ผ lift
์ํค๋ ๊ฒ๊ณผ ๊ฐ๋ค.
C ์์คํ ์ D ์์คํ ์ผ๋ก ์ฎ๊ธธ ์ ์์ผ๋ฉฐ ๋ชจ๋ ๊ด๊ณ๊ฐ ์ ์ง๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๊ฒ์ ๋ค์ ์๋๋๋ก ๋๋์๊ฐ ์ ์์ผ๋ฉฐ Functor ๋ฅผ ๊ฑธ๊ธฐ ์ด์ ๊ณผ ๋์ผํด์ผํ๋ค.
๋ํ Functor ๋ Functor ๋ฅผ ๊ฑธ๊ณ Morphism ์ ์ ์ฉํ ๊ฒ๊ณผ Morphism ์ ์ ์ฉํ ๊ฒ์ Functor ๋ฅผ ๊ฑด ๊ฒ์ด ๋์ผํด์ผํ๋ค.
๊ทธ๋ ๋ค๋ฉด ์ ๊ตณ์ด ์ด๋ฐ๊ฑธ ํ๋ ๊ฒ์ผ๊น? Fourier Transform, Laplace Transform ํน์ Log ํจ์๋ฅผ ์ด์ฉํด๋ณธ์ ์ด ์๋ค๋ฉด ๊ธฐ์กด์ Coordinate Systems ์์ ๊ณ์ฐํ๊ธฐ ํ๋ ๊ฒ๋ค์ Complex Plane(๋ณต์ ํ๋ฉด)์ผ๋ก ์ฎ๊ฒจ ๊ณ์ฐ ํ ๋ณต์ํ๊ฑฐ๋, ๋น์จ๋ก ๋ค๋ฃฐ ์ ์๋ Log ํจ์๋ฅผ ์ด์ฉํด ๊ณ์ฐํด๋ณธ ์ ์ด ์์ ๊ฒ์ด๋ค. ๊ฐ๋จํ๊ฒ ์ด์ผ๊ธฐํ๋ฉด ๊ณ ๋ฑํ๊ต ์ํ์ฑ ์ ๋ถ์ด ์๋ ์์ฉ๋ก๊ทธ ํ๋ง ์์ผ๋ฉด ํฌ๊ณ ๋ณต์กํ ๊ณ์ฐ์ ์ผ๋ง๋ ๊ฐ๋จํ๊ฒ ํ ์ ์์์ ๋ฌผ๋ก ์ด๊ณ , Log ํจ์๋ฅผ ์ด์ฉํด ๊ณผํ์๊ฐ์ ๋น์จ๋ก ์ค์ผ์ผ๋ง ๋ ๊ทธ๋ํ๊ฐ ์ฃผ๋ ํธ๋ฆฌํจ์ ๊ฒฝํํด ๋ณด์์ ๊ฒ์ด๋ค.
์ฆ, ๋ฌด์ธ๊ฐ โ๊ณ์ฐ์ ํธํ๊ฒ ํ๊ธฐ ์ํด ๊ทธ ํ๊ฒฝ์ ๊ทธ๋๋ก lift ์์ผ ๋ณํ์ํค๋ ๊ฒโ ์ด๊ฒ์ด ๋ฐ๋ก Functor ์ ์ญํ ์ด๋ค.
Functor ์ ๊ธฐ๋ฅ์ ๋ํด ์ถ์ํ ์์ผ ์ข ๋ ํน์ํ๊ฒ ๋ง๋ ๊ฒ์ด Applicative Functor ๊ณ , Applicative Functor ์ ๊ธฐ๋ฅ์ ๋ํด ์ถ์ํ ์ํจ ๊ฒ์ด Monad ๋ค.
2. Type Class
์ผ๋ฐ์ ์ผ๋ก OOP ๊ฐ ์ค์ฌ์ธ ์ธ์ด์์ String ์ด๋ผ๋ Class/Structure ๋ฅผ ๋ง๋ ๋ค๊ณ ํด๋ณด์. ๊ทธ๋ฌ๋ฉด String ์ด๋ผ๋ Class/Structure ๋ฅผ ๋ง๋ค๊ณ , ํ์ํ ํจ์๋ฅผ ๊ตฌํํ๋ ์์ผ๋ก ์ค๊ณํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๋ฌํ Type ์ Generic ์ ์ด์ฉํด ์ถ์ํ ํ๋ค.
ํ์ง๋ง ์์ํ ํจ์ํ ์ธ์ด์ธ Haskell ์ ์ด๋ฌํ ๋ฐ์์ ์ ํํด Monad ๋ฐฉ์์ผ๋ก ๋ง๋ค์ด ํน์ ํจ์(Morphism)๊ฐ Types ์ ์ ์ฝ์ ๋ฐ์ง ์๋๋ก ํ๋ค. ์ฆ, Monad ๊ณต๊ฐ์์ ์์ ๋กญ๊ฒ ์ฌ์ฉ ๊ฐ๋ฅํ ํจ์๋ฅผ ํตํด ์ด๋ค Types ๊ฐ lift ๋๋ ๊ฐ์ ์ ์ฉํ ์ ์๊ณ , ์ด๋ฅผ ๋ค์ lift ์ด์ ์ผ๋ก ๋๋๋ฆฌ๋ ๋ฐฉ์์ ์ทจํ ๊ฒ์ด๋ค.
์ํ์ด ์๋ ํ๋ก๊ทธ๋๋ฐ ์ธ๊ณ์์ Monad ๋ฅผ ์ ์ฌ์ฉํ๋์ง, ์ด๋ค ์ด์ ์ ๊ฐ๋์ง ์ดํดํ๊ธฐ ์ํ ์ฒซ ๊ฑธ์์ Type Class
๊ฐ ์ ํ์ํ์ง ์ดํดํ๋ ๊ฒ์ด๋ค.
Monad ์ ๊ฐ์ฅ ํํ ์๋ก Maybe(=Optional)์ ๋ค์ด๋ณด์. Optional Monad ์์ด ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃฌ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด Type Guard ๋ฅผ ํ ๊ฒ์ด๋ค.
func fourTimes(value: Int?) -> Int? {
guard let value else { return nil }
return value * 4
}
let result1 = fourTimes(value: 23)
let result2 = fourTimes(value: nil)
print(result1) // Optional(92)
print(result2) // nil
type nullable = undefined | null
const fourTimes = (value: number | nullable): number | null => {
if (value === undefined || value === null) return null
return value * 4
}
const result1 = fourTimes(23)
const result2 = fourTimes(null)
console.log(result1) // 92
console.log(result2) // null
Type Class ๋ ํจ์๊ฐ ํ๋์ ๊ธฐ๋ฅ๋ง ํ ์ ์๋๋ก, ๊ทธ๋์ ํด๋น Type Class ๋ด์์๋ ํ๋์ Type ์ผ๋ก ๋ค๋ฃฐ ์ ์๋๋ก
ํจ์ผ๋ก์จ ๋ ์ ์ ์ฝ๋๋ก ๋น์ฆ๋์ค ์์ฒด์ ์ง์คํ๊ณ ์์ ํจ์๋ฅผ ๋ง๋ค ์ ์๋๋ก ํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ค. ์ fourTimes(value:)
ํจ์์ ์๋
fiveTimes(value:)
ํจ์๋ฅผ ๋น๊ตํด๋ณด์.
func fiveTimes(value: Int) -> Int {
value * 5
}
let result3 = Optional(23).map(fiveTimes(value:))
let result4 = Optional(nil).map { fiveTimes(value: $0) }
print(result3) // Optional(115)
print(result4) // nil
class Maybe<Wrapped> {
static some<Wrapped>(value: Wrapped): Maybe<Wrapped> {
return new Maybe('some', value)
}
static readonly none: Maybe<never> = (() => {
const self = new Maybe('none')
delete self.value
return self as Maybe<never>
})()
static of<Wrapped>(value: Wrapped): Maybe<any> {
if (value === undefined || value === null) {
return Maybe.none
} else {
return Maybe.some(value)
}
}
private constructor(private kind: 'some' | 'none', public value?: Wrapped) {
}
map<U>(transform: (value: Wrapped) => U): Maybe<U> {
switch (this.kind) {
case 'some':
return Maybe.some(transform(this.value!))
case 'none':
return Maybe.none
}
}
}
const fiveTimes = (value: number): number => value * 5
const result3 = Maybe.of(23).map(fiveTimes)
const result4 = Maybe.of(null).map(value => value * 5)
console.log(result3) // Maybe {kind: 'some', value: 115}
console.log(result4) // Maybe {kind: 'none'}
์ด๋ฅผ ์ํด Swift ์ Optional ์ initializer ๊ฐ Type Class ๋ฅผ ๋ง๋ ๋ค. ํ์ง๋ง TypeScript ์๋ ์ด์ ๊ฐ์ ๊ฒ์ด ์์ด ์ง์ ๊ตฌํํด์ผ ํ๋๋ฐ
Union Type
,Namespace
,Class
๋ฑ์ ์ฌ์ฉํด ๊ตฌํํ ์ ์๋ค.์ฌ๊ธฐ์๋
Class
์of
๋ผ๋ Type Method ๋ฅผ ์ฌ์ฉํด ๊ตฌํํ๋ค.
๋ค์ ์น์ ๋ถํฐ Functor, Applicative Functor, Monad ๊น์ง ์ฐจ๋ก๋ก ํ์ฅํ๋ฉฐ Maybe Monad ๋ฅผ ์ง์ ๊ตฌํํด๋ณด๋๋ก ํ์.
3. Functor
ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์์ Functor ๋ ๋ค์๊ณผ ๊ฐ์ด ์ ์ํ ์ ์๋ค.
A functor applies a function to a value wrapped in a context.
๊ทธ๋ฆฌ๊ณ ์ข ๋ ์์ธํ ์ด์ผ๊ธฐ ํ๋ฉด ์ฌ๊ธฐ์ ๋งํ๋ ํจ์๋ ์์คํ
์ lift ์ํค๊ธฐ ์ํด map(_:)
ํจ์๋ก ๊ตฌํ๋๋ค.
Functor is a type, that implements map function.
์ฐธ๊ณ ๋ก ์ํ์ ์นดํ
๊ณ ๋ฆฌ ์ด๋ก ๋๋ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด Haskell ์์๋ Maybe
๋ผ ๋ถ๋ฆฌ๋ ๊ฒ์ด ๋ง์ ์ธ์ด์์ Optional
์ด๋ผ๋ Types ๋ก ์ ๊ณต๋๋ค.
enum Maybe<Wrapped> {
case some(Wrapped)
case none
}
class Maybe<Wrapped> {
static some<Wrapped>(value: Wrapped): Maybe<Wrapped> {
return new Maybe('some', value)
}
static readonly none: Maybe<never> = (() => {
const self = new Maybe('none')
delete self.value
return self as Maybe<never>
})()
static of<Wrapped>(value: Wrapped): Maybe<any> {
if (value === undefined || value === null) {
return Maybe.none
} else {
return Maybe.some(value)
}
}
private constructor(private kind: 'some' | 'none', public value?: Wrapped) {
}
}
TypeScript ๋ Enumeration ์ด Associated Values ๋ฅผ ์ง์ํ์ง ์๊ธฐ ๋๋ฌธ์ ์ง์ ๊ตฌํํด์ผํ๋ค.
์ด ๊ธ์์๋ Class ๋ฅผ ์ฌ์ฉํด ๊ตฌํํ๋ค(์์์๋ ์ค๋ช ํ๋ฏ์ด Union Type, Namespace ๋ฑ์ ์ฌ์ฉํ ๊ตฌํ ์ญ์ ๊ฐ๋ฅํ๋ค).
Wrapped
์ Type ์ด Int
์ผ ๋๋ง ์๋ํ๋ add(_:)
๋ผ๋ ํจ์๋ฅผ ์ถ๊ฐํด๋ณด์.
extension Maybe where Wrapped == Int {
func add(_ value: Int) -> Maybe<Int> {
switch self {
case .some(let wrappedValue): return .some(wrappedValue + value)
case .none: return .none
}
}
}
let foo: Maybe<Int> = .some(10)
let bar = foo.add(7)
print(foo) // some(10)
print(bar) // some(17)
let baz: Maybe<Int> = .none
print(baz) // none
์ ์ฝ๋๋ฅผ ํตํด ์ฐ๋ฆฌ๋ Maybe ์ ํจ์๋ฅผ ์์ ํ๊ฒ ์ ์ฉํ ์ ์์์ ํ์ธํ๋ค. ํ์ง๋ง ์ฐ๋ฆฌ๊ฐ Functor ์์ ์ํ๋ ๊ฒ์ add(_:)
์ ๊ฐ์ ํจ์๊ฐ
์๋๋ค. ์ฐ๋ฆฌ๋ ์์คํ
์ lift ์์ผ์ค map(_:)
ํจ์๋ฅผ ๊ตฌํํด์ผํ๋ค. ๋ฐ๋ผ์ ์ฐ๋ฆฌ๊ฐ ์ํ๋ Functor ์
์ต์ข
๊ตฌํ์ ๋ค์๊ณผ ๊ฐ๋ค.
enum Maybe<Wrapped> {
case some(Wrapped)
case none
}
extension Maybe {
func map<U>(_ transform: (Wrapped) -> U) -> Maybe<U> {
switch self {
case .some(let wrappedValue): return .some(transform(wrappedValue))
case .none: return .none
}
}
}
class Maybe<Wrapped> {
static some<Wrapped>(value: Wrapped): Maybe<Wrapped> {
return new Maybe('some', value)
}
static readonly none: Maybe<never> = (() => {
const self = new Maybe('none')
delete self.value
return self as Maybe<never>
})()
static of<Wrapped>(value: Wrapped): Maybe<any> {
if (value === undefined || value === null) {
return Maybe.none
} else {
return Maybe.some(value)
}
}
private constructor(private kind: 'some' | 'none', public value?: Wrapped) {
}
map<U>(transform: (value: Wrapped) => U): Maybe<U> {
switch (this.kind) {
case 'some':
return Maybe.some(transform(this.value!))
case 'none':
return Maybe.none
}
}
}
์ Functor ๊ฐ ์ ์๋ํ๋์ง ํ์ธํด๋ณด์.
func intToString(_ value: Int) -> String {
String(value)
}
let foo: Maybe<Int> = .some(10)
let baz: Maybe<Int> = .none
let fooPrime = foo.map(intToString(_:))
let bazPrime = baz.map(intToString(_:))
print(fooPrime) // some("10")
print(bazPrime) // none
const intToString = (value: number): string => String(value)
const foo: Maybe<number> = Maybe.some(10)
const baz: Maybe<number> = Maybe.none
const fooPrime = foo.map(intToString)
const bazPrime = baz.map(intToString)
console.log(fooPrime) // Maybe {kind: 'some', value: '10'}
console.log(bazPrime) // Maybe {kind: 'none'}
4. Applicative Functor
Applicative applies a wrapped function to a wrapped value
Functor Lift the System ์ ๋ณด๋ฉด Functor ๊ฐ lift ์ํค๋ ๊ฒ์ด Set ๋ฟ๋ง ์๋๋ผ Morphism ์
ํฌํจํ๋ค๋ ๊ฒ์ ์ ์ ์๋ค. ํ์ง๋ง ์ Functor ์์ ์ฐ๋ฆฌ๋ Set ๋ง lift ์ํค๊ณ , Set ์ด .some
์ธ ๊ฒฝ์ฐ์ ํํด
map(_:)
ํจ์๋ฅผ ์ ์ฉ์์ผฐ๋ค. ์ด๋ฒ ์น์
์์๋ Morphism ์์ฒด๋ฅผ lift ์์ผ๋ณด์.
apply(_:)
ํจ์๋ฅผ ์ ์ํด๋ณด์. ์ด์ ์น์
์์ map(_:)
ํจ์๊ฐ (Wrapped) -> U
Type ์ ํจ์๋ฅผ Argument ๋ก ๋ฐ์๋ค๋ฉด, ์ด๋ฒ์๋
Maybe<(Wrapped) -> U>
Type ์ ํจ์์ Argument ๋ก ๋ฐ๋๋ค.
extension Maybe {
func apply<U>(_ wrappedTransform: Maybe<(Wrapped) -> U>) -> Maybe<U> {
switch wrappedTransform {
case .some(let transform):
switch self {
case .some(let wrappedValue): return .some(transform(wrappedValue))
case .none: return .none
}
case .none: return .none
}
}
}
๊ทธ๋ฐ๋ฐ ์ ์ฝ๋๋ฅผ ๋ณด๋ฉด Wrapping ๋ ํจ์๊ฐ .some
์ธ ๊ฒฝ์ฐ ํจ์์ Wrapping ์ ํผ ์ดํ์ ์ฝ๋๋ฅผ ๋ณด๋ฉด ๊ธฐ์กด์ map(_:)
ํจ์๋ฅผ ์ฌ์ฌ์ฉ ํ ์
์๋ค๋ ๊ฒ์ ํ์ธํ ์ ์๋ค. apply(_:)
ํจ์๋ฅผ ๋ฆฌํฉํ ๋งํด๋ณด์.
extension Maybe {
func apply<U>(_ wrappedTransform: Maybe<(Wrapped) -> U>) -> Maybe<U> {
switch wrappedTransform {
case .some(let transform): return self.map(transform)
case .none: return .none
}
}
}
class Maybe<Wrapped> {
static some<Wrapped>(value: Wrapped): Maybe<Wrapped> {
return new Maybe('some', value)
}
static readonly none: Maybe<never> = (() => {
const self = new Maybe('none')
delete self.value
return self as Maybe<never>
})()
static of<Wrapped>(value: Wrapped): Maybe<any> {
if (value === undefined || value === null) {
return Maybe.none
} else {
return Maybe.some(value)
}
}
private constructor(private kind: 'some' | 'none', public value?: Wrapped) {
}
map<U>(transform: (value: Wrapped) => U): Maybe<U> {
switch (this.kind) {
case 'some':
return Maybe.some(transform(this.value!))
case 'none':
return Maybe.none
}
}
apply<U>(wrappedTransform: Maybe<(value: Wrapped) => U>): Maybe<U> {
switch (wrappedTransform.kind) {
case 'some':
return this.map(wrappedTransform.value!);
case 'none':
return Maybe.none;
}
}
}
์ Applicative Functor ๊ฐ ์ ์๋ํ๋์ง ํ์ธํด๋ณด์.
func intToString(_ value: Int) -> String {
String(value)
}
let foo: Maybe<Int> = .some(10)
let baz: Maybe<Int> = .none
let fnFoo: Maybe<(Int) -> String> = .some(intToString(_:))
let fnBaz: Maybe<(Int) -> String> = .none
let fooPrime = foo.apply(fnFoo)
let bazPrime = baz.apply(fnBaz)
print(fooPrime) // some("10")
print(bazPrime) // none
const intToString = (value: number): string => String(value)
const foo: Maybe<number> = Maybe.some(10)
const baz: Maybe<number> = Maybe.none
const fnFoo: Maybe<(value: number) => string> = Maybe.some(intToString)
const fnBaz: Maybe<(value: number) => string> = Maybe.none
const fooPrime = foo.apply(fnFoo)
const bazPrime = baz.apply(fnBaz)
console.log(fooPrime) // Maybe {kind: 'some', value: '10'}
console.log(bazPrime) // Maybe {kind: 'none'}
Maybe Monad ๋ฅผ ์ ์ํ๊ธฐ ์ํด ์ง๊ธ๊น์ง ํ ๊ฒ์ ์ ๋ฆฌํด๋ณด์.
Set
์ lift ์ํค๊ธฐ ์ํดmap(_:)
ํจ์๋ฅผ ์ ์ํด Functor ๋ฅผ ๊ตฌํํ๊ณ ,Morphism
์ lift ์ํค๊ธฐ ์ํดapply(_:)
ํจ์๋ฅผ ์ ์ํด Applicative Functor ๋ฅผ ๊ตฌํํ๋ค.์ฐ๋ฆฌ๊ฐ Maybe Monad ๋ฅผ ์ ์ํ๋ ค๋ ๋ชฉ์ ์ด ๋ฌด์์ด์๋์ง ๋ค์ ์๊ฐํด๋ณด์.
nil
์ฌ๋ถ์ ์๊ด ์์ด ์ฝ๋๋ฅผ ๋ค๋ฃฐ ์ ์๋๋ก ํด ๋ฐ์ดํฐ์ ํจ์์ ๋ํด ๋ช ์์ ์ธ Type Guard ์์ด๋ Type Safe ํ ๋น์ฆ๋์ค ๋ก์ง์ ๊ตฌํํ๊ธฐ ์ํจ์ด์๋ค. Functor ์์map(_:)
ํจ์๋Set
์ lift ์์ผ ์ด๋ฅผ ๊ฐ๋ฅํ๋๋ก ํ๊ณ , Applicative Functor ์์apply(_:)
ํจ์๋Morphism
์ lift ์ํด์ผ๋ก์จ ์ด๋ฅผ ๊ฐ๋ฅํ๊ฒ ํ๋ค.
5. Monad
A monad applies wrapped function that returns wrapped value to the wrapped value.
๊ธ๋ง ๋ด์๋ ๋ฌด์จ ๋ง์ธ์ง ์ดํดํ๊ธฐ๊ฐ ์ฝ์ง ์๋ค. Maybe Monad ๋ฅผ ์ ์ํ๊ธฐ ์ํด flatMap(_:)
ํจ์๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์ ์ํด๋ณด์.
extension Maybe {
func flatMap<U>(_ transform: (Wrapped) -> Maybe<U>) -> Maybe<U> {
switch self {
case .some(let wrappedValue): return transform(wrappedValue)
case .none: return .none
}
}
}
class Maybe<Wrapped> {
static some<Wrapped>(value: Wrapped): Maybe<Wrapped> {
return new Maybe('some', value)
}
static readonly none: Maybe<never> = (() => {
const self = new Maybe('none')
delete self.value
return self as Maybe<never>
})()
static of<Wrapped>(value: Wrapped): Maybe<any> {
if (value === undefined || value === null) {
return Maybe.none
} else {
return Maybe.some(value)
}
}
private constructor(private kind: 'some' | 'none', public value?: Wrapped) {
}
map<U>(transform: (value: Wrapped) => U): Maybe<U> {
switch (this.kind) {
case 'some':
return Maybe.some(transform(this.value!))
case 'none':
return Maybe.none
}
}
flatMap<U>(transform: (value: Wrapped) => Maybe<U>): Maybe<U> {
switch (this.kind) {
case 'some':
return transform(this.value!)
case 'none':
return Maybe.none
}
}
apply<U>(wrappedTransform: Maybe<(value: Wrapped) => U>): Maybe<U> {
switch (wrappedTransform.kind) {
case 'some':
return this.map(wrappedTransform.value!);
case 'none':
return Maybe.none;
}
}
}
flatMap(_:)
ํจ์๋ฅผ map ํจ์์ ๊ตฌํ ๊ณผ ๋น๊ตํด๋ณด์.
Functor ์ ๊ตฌํ์ ์ํ map(_:)
ํจ์๊ฐ (Wrapped) -> U
ํจ์๋ฅผ ๋ฐ์ ๋ฐํํ U
๋ฅผ map(_:)
ํจ์๊ฐ Maybe<U>
๋ก
lift ํด์ ๋ฐํํ๋ค. return .some(transform(wrappedValue))
Monad ์ ๊ตฌํ์ ์ํ flatMap(_:)
ํจ์๋ (Wrapped) -> Maybe<U>
ํจ์๋ฅผ ๋ฐ์ ๋ฐํํ Maybe<U>
๋ฅผ flatMap(_:)
ํจ์๊ฐ
Maybe<U>
๋ก ๋ฐํํ๋ค. ์ฆ, ์ถ๊ฐ๋ก lift ํ์ง ์๋๋ค. return transform(wrappedValue)
map ๊ณผ flatMap ์ ์ฐจ์ด์ ํต์ฌ์ด ๋ฐ๋ก ๋ฐํํ ๋ ์ถ๊ฐ๋ก lift ๋ฅผ ํ๋๊ฐ? ํ์ง ์๋๊ฐ? ์ด๋ค.
์ด๊ฒ์ ํ์ด์ ์ค๋ช
ํด๋ณด์. ์ฐ์ ์ฌ์ด ์ค๋ช
์ ์ํด Functor ์ ์ํด ์์คํ
์ด lift ๋๋ ๊ฒ์ ๋๋๋ฆฌ๋ ๋ฐ๋ ๋ฐฉํฅ์ผ๋ก์ lift ๋ฅผ un-lift
๋ผ
๋ถ๋ฅด๊ธฐ๋ก ํ์.
map(_:)
๊ณผ flatMap(_:)
์ ๋ชจ๋ ๋ด๋ถ์ switch-case
๋ฅผ ์ด์ฉํด un-lift ๋ฅผ ๊ตฌํํ๊ณ ์๋ค. ์ฐจ์ด์ ์ map ์ un-lift ๋
๋ฐ์ดํฐ๋ฅผ ํจ์ transform ์ด (Wrapped) -> U
๋ฅผ ์ ์ฉํ ๋ค์ map ํจ์๊ฐ U
๋ฅผ ๋ค์ lift ์์ผ Maybe<U>
๋ก ๋ฐํํ๋ ๋ฐ๋ฉด,
flatMap ์ un-lift ๋ ๋ฐ์ดํฐ๋ฅผ ํจ์ transform ์ด (Wrapped) -> Maybe<U>
๋ฅผ ์ ์ฉํ ๋ค์ flatMap ํจ์๊ฐ Maybe<U>
๋ฅผ
์ถ๊ฐ์ ์ธ lift ์์ด ๊ทธ๋๋ก Maybe<U>
๋ก ๋ฐํํ๋ค๋ ๊ฒ์ด๋ค.
์ด๋ก๋ถํฐ ์ฐ๋ฆฌ๋ map ๊ณผ flatMap ์ ์ต์ข
์ ์ธ Return Type ์ด ๋์ผํ๊ฒ Maybe<U>
๋ผ๋ ๊ฒ์ ์ ์ ์์ผ๋ฉฐ, map(_:)
ํจ์์ ์ ๋ฌ๋๋
transform ์ Wrapped ๋ T
๊ฐ ์ค๋ ๊ฒ์ด ์ ์ ํ๋ฉฐ, flatMap(_:)
ํจ์์ ์ ๋ฌ๋๋ transform ์ Wrapped ๋ Maybe<T>
๊ฐ ์ค๋ ๊ฒ์ด
์ ์ ํ๋ค๋ ๊ฒ์ ์ ์ ์๋ค.
Swift Standard Library ์ ์ํด ์ ์๋ Optional ๊ณผ Array Monad ๋ก๋ถํฐ ์ด๋ฌํ Monad Rule ์ด ์ ์ฉ๋๊ณ ์์์ ํ์ธํ ์ ์๋ค.
- Optional
@inlinable public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
@inlinable public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?
- Array
@inlinable public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
@inlinable public func flatMap<SegmentOfResult>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence
@inlinable public func compactMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
์ด์ Monad Rule ์ ์ ์ฉํ๊ธฐ ์ํด ๊ตฌํํ flatMap(_:)
์ด ์ ์๋ํ๋์ง, ๊ทธ๋ฆฌ๊ณ ์ ์ฌ์ฉํ๋์ง ํ์ธํด๋ณด์.
let foo: Maybe<Int> = .some(10)
let baz: Maybe<Maybe<Int>> = .some(foo)
print(type(of: foo)) // Maybe<Int>
print(type(of: baz)) // Maybe<Maybe<Int>>
const foo: Maybe<number> = Maybe.some(10)
const baz: Maybe<Maybe<number>> = Maybe.some(foo)
console.log(foo) // Maybe {kind: 'some', value: 10}
console.log(baz) // Maybe {kind: 'some', value: Maybe}
console.log(baz.value) // Maybe {kind: 'some', value: 10}
baz ๋ Maybe ์ ์ํด ๋ ๋ฒ lift ๋์ด Maybe<Maybe<Int>>
Type ์ด๋ค.
Functor ๋ฅผ ์ํด ๊ตฌํํ ๊ธฐ์กด์ map(_:)
ํจ์์ intToString(_:)
ํจ์์ maybeInt_to_MaybeString(_:)
ํจ์๋ฅผ ์ ์ฉํด๋ณด์.
func intToString(_ value: Int) -> String {
String(value)
}
func maybeInt_to_MaybeString(_ monad: Maybe<Int>) -> Maybe<String> {
return monad.map(intToString(_:))
func intToString(_ value: Int) -> String {
String(value)
}
}
let fooPrime = foo.map(intToString(_:))
print(type(of: fooPrime)) // Maybe<String>
print(fooPrime) // some("10")
let bazPrime = baz.map(maybeInt_to_MaybeString(_:))
print(type(of: bazPrime)) // Maybe<Maybe<String>>
print(bazPrime) // some(__lldb_expr_47.Maybe<Swift.String>.some("10"))
const intToString = (value: number): string => String(value)
const maybeInt_to_MaybeString = (monad: Maybe<number>): Maybe<string> => {
return monad.map(intToString)
function intToString(value: number): string {
return String(value)
}
}
const fooPrime = foo.map(intToString)
console.log(fooPrime) // Maybe {kind: 'some', value: '10'}
const bazPrime = baz.map(maybeInt_to_MaybeString)
console.log(bazPrime) // Maybe {kind: 'some', value: Maybe}
console.log(bazPrime.value) // Maybe {kind: 'some', value: '10'}
foo
์ ๊ฒฝ์ฐ map(_:)
ํจ์์ ์ํด Maybe<Int>
๊ฐ Int
๊ฐ ๋์๊ณ , intToString(_:)
ํจ์์ ์ํด String
์ด ๋๋ค. ๋ง์ง๋ง์ผ๋ก
map(_:)
์ ์ํด lift ๋์ด Maybe<String>
์ด ๋๋ค.
baz
์ ๊ฒฝ์ฐ map(_:)
ํจ์์ ์ํด Maybe<Maybe<Int>>
์ด Maybe<Int>
๊ฐ ๋๋ค. ์ดํ intToString(_:)
ํจ์์ ์ํด
Maybe<String>
์ด ๋๊ณ , ๋ง์ง๋ง์ผ๋ก map(_:)
์ ์ํด lift ๋์ด Maybe<Maybe<String>>
์ด ๋๋ค.
์ด๊ฒ์ด ๋ฐ๋ก Functor ์ ๋ฌธ์ ์ ์ด๋ค.
nil
์ฌ๋ถ์ ๋ฌด๊ดํ๊ฒ ๋น์ฆ๋์ค๋ฅผ ๋ค๋ฃจ๊ธฐ ์ํด Functor ๋ฅผ ์ฌ์ฉํด lift ์์ผฐ๋๋ฐ, ์ด Functor ๋ฅผ ๋ ๋ฒ ์ฌ์ฉํ ๊ฒฝ์ฐ ์คํ๋ ค lift ๋ก ์ธํด ๋น์ฆ๋์ค ๋ก์ง์ ๋์ผํ๊ฒ ์ ์ฉํ ์ ์๋ ์์ด๋ฌ๋ํ ์ํฉ์ ๋์ด๊ฒ ๋๋ ๊ฒ์ด๋ค. ๋ฐ๋ผ์ ์ด๋ฌํ Case ์ ๋์ธ ๊ฒฝ์ฐ์๋ ๋์ผํ System ์ผ๋ก ๋ค๋ฃจ๊ธฐ ์ํดlift
๋ฟ ์๋๋ผun-lift
์์ผ์ฃผ๋flatMap(_:)
ํจ์๊ฐ ํ์ํ ๊ฒ์ด๋ค.์ฆ,
map(_:)
ํจ์์flatMap(_:)
ํจ์๋ฅผ ํ์์ ๋ฐ๋ผ ์ ์ ํ ์ ์ฉ์ํค๋ฉด ์ด๋ ํ ๊ฒฝ์ฐ์๋ ๋์ผํ System ์ ๋ง๋ค์ด ๋์ผํ๊ฒ ๋น์ฆ๋์ค๋ฅผ ๋ค๋ฃฐ ์ ์๊ฒ ๋๋ค.
์ด๋ฒ์๋ map(_:)
๋์ flatMap(_:)
์ ์ ์ฉํด๋ณด์.
let fooPrime = foo.map(intToString(_:))
print(type(of: fooPrime)) // Maybe<String>
print(fooPrime) // some("10")
let bazPrime = baz.flatMap(maybeInt_to_MaybeString(_:))
print(type(of: bazPrime)) // Maybe<String>
print(bazPrime) // some("10")
const fooPrime = foo.map(intToString)
console.log(fooPrime) // Maybe {kind: 'some', value: '10'}
const bazPrime = baz.flatMap(maybeInt_to_MaybeString)
console.log(bazPrime) // Maybe {kind: 'some', value: '10'}
flatMap(_:)
ํจ์๋ฅผ ์ฌ์ฉํจ์ผ๋ก์จ foo
์ baz
๋ฅผ ๋์ผํ๊ฒ Maybe<String>
์ผ๋ก ๋ค๋ฃฐ ์ ์๊ฒ ๋์๋ค.
Functional Programming ์์ map(_:)
๊ณผ flatMap(_:)
์ด ๊ฐ๋ ์ง์ ํ ์๋ฏธ๋ ๋จ์ํ iteration ์ ํ๋ ๊ฒ์ด ์๋๋ผ Monad Rule
์ ์ ์ฉํ๊ธฐ ์ํด lift
์ un-lift
๋ฅผ ํด ๋จ์ผ ์ฐจ์์ System ์ผ๋ก ๋ค๋ฃฐ ์ ์๊ฒ ํ๋ ๊ฒ์ด๋ค.
์ด์ ์ฐ๋ฆฌ๋ Swift ์ ๊ฒฝ์ฐ Optional ๋ฟ ์๋๋ผ
Array
,Set
, ์ฌ์ง์ดResult
๊ฐ์ ๊ฒ๋ค ๋ชจ๋ Functor ์ด๋ฉฐ Monad ๋ผ๋ ๊ฒ์ ์ดํดํ ์ ์๋ค.๋ง์ฐฌ๊ฐ์ง๋ก TypeScript ์ญ์
Array
,Set
์ ๊ฐ์ ๊ฒ๋ค์ด Functor ์ด๋ฉฐ Monad ๋ผ๋ ๊ฒ์ ์ดํดํ ์ ์๋ค.
7. Immutable ๐ฉโ๐ป
Maybe(Optional) Monad, Just Monad, Nothing Monad, IO Monad ๋ฑ ๋ค์ํ Monad ๊ฐ ์กด์ฌํ๋ค. ํ์ง๋ง ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ๋๋ ๊ฒ์
Array
์ Monad ๋ค.
Functional Programming ์ ํ ๋ ๊ฐ๊ธ์ ์ด๋ฉด ์์ ํจ์
๋ฅผ ์ํด ์๋ณธ ๋ฐฐ์ด์ ๋ํ mutation ์ ํ์ฉํ์ง ์์์ผ ํ๋ค. ๋ฐฐ์ด์
Functional Programming ์ ์ฝ๊ฒ ํ ์ ์๋๋ก ๋ง์ ๋ด์ฅ ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ค. ์ด ์ค immutability ๋ฅผ ํผ์์ํค๋ ์ฃผ์ ๋ฉ์๋์ ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ
์ํ ๋ฐฉ๋ฒ์ ์์๋ณธ๋ค.
- Bad Case
var foo = [1, 4, 6, 9, 13]
foo.append(15)
print(foo) // [1, 4, 6, 9, 13, 15]
let foo = [1, 4, 6, 9, 13]
foo.push(15)
console.log(foo) // [1, 4, 6, 9, 13, 15]
- Good Case
var foo = [1, 4, 6, 9, 13]
var bar = foo + [15]
print(foo) // [1, 4, 6, 9, 13]
print(bar) // [1, 4, 6, 9, 13, 15]
let foo = [1, 4, 6, 9, 13]
let bar = [...foo, 15]
console.log(foo) // [1, 4, 6, 9, 13]
console.log(bar) // [1, 4, 6, 9, 13, 15]_
์ฃผ๋ก ๋ฐฐ์ด์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐ/์ญ์ ํ ๋ ์ด๋ค ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋์ ๋ฐ๋ผ immutability ๊ฐ ํผ์๋ ์ ์๋ค. ์ผ๋ฐ์ ์ผ๋ก ๊ธฐ์กด ๋ฐฐ์ด์ ๋ฐ์ดํฐ๋ฅผ Deep Copy ํ๋ ๋ฐฉ๋ฒ์ผ๋ก ํด๊ฒฐํ๋ค.
- Bad Case
var foo = [6, 2, 13, 1, 7, 15]
foo.sort(by: >)
print(foo) // [15, 13, 7, 6, 2, 1]
let foo = [6, 2, 13, 1, 7, 15]
foo.sort((a, b) => b - a)
console.log(foo) // [15, 13, 7, 6, 2, 1]
- Good Case
var foo = [6, 2, 13, 1, 7, 15]
var bar = foo.sorted(by: >)
print(foo) // [6, 2, 13, 1, 7, 15]
print(bar) // [15, 13, 7, 6, 2, 1]
let foo = [6, 2, 13, 1, 7, 15]
let bar = [...foo].sort((a, b) => b - a)
console.log(foo) // [6, 2, 13, 1, 7, 15]
console.log(bar) // [15, 13, 7, 6, 2, 1]
๋๋ถ๋ถ ์ ๋ ฌ์ ํ ๋ ์๋ณธ ๋ฐฐ์ด์ ์ง์ ์ ๋ ฌํ๋ค. ํ์ง๋ง ์์ ํจ์๋ฅผ ์ํด์ ์ด๋ฐ mutation ์ ์ง์ํด์ผํ๋ค. ๋ฐ๋ผ์ ์ ๋ฐฐ์ด ์ธ์คํด์ค๋ฅผ ์์ฑํ๋๋ก ํด ์๋ณธ์ immutability ๋ฅผ ํผ์์ํค์ง ์๋๋ก ํ๋ค.
8. Pipe & Compose ๐ฉโ๐ป
1. What is the Pipe?
f: X ~> Y
g: Y ~> Z
h: Z ~> W
๊ฐ ์ฃผ์ด์ก์ ๋ ํจ์ f, g, h ๋ฅผ ํฉ์ฑํด์ ํํํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
X ~> Y ~> Z ~> W
ํจ์ ์ ์ฉ์ ํ๋ฆ์์ผ๋ก ๋ณด๋ฉด f > g > h
์ธ๋ฐ ์ํ์ ์ผ๋ก ํํํ ๋๋ ๊ดํธ ์์ชฝ์ด ๋จผ์ ๊ณ์ฐ๋์ด์ผํ๋ฏ๋ก h โฆ (g โฆ f)
๋ก ํํ๋๋ค.
h(g(f(x)))
๋ผ๋ ์์์ด ๋์ด์ผ ํ๊ธฐ ๋๋ฌธ์ด๋ค.
๋ฐ๋ผ์ f > g > h
ํ๋ฆ์ ์ต์ํ ๊ฐ๋ฐ์๋ค์ด ์ ํธํ๋ ํจ์ ํฉ์ฑ ๋ฐฉ์์ pipe
๋ผ ๋ถ๋ฅด๋ฉฐ, ์ํ์ ์ฐ์ฐ ์์๋ฅผ ๊ทธ๋๋ก ํํํ๋ h โฆ (g โฆ f)
๋ฐฉ์์ compose
๋ผ ๋ถ๋ฅธ๋ค. ์ฆ, pipe ์ compose ๋ ํจ์ ํฉ์ฑ์ ํ๋ฆ๋ง ๋ฐ๋ ๋ฐฉํฅ์ธ ๋์ผํ ํจ์๋ค.
f(x): X ~> Y = x + 5
g(x): Y ~> Z = x * 4
h(x): Z ~> W = x - 6
์ผ๋ก ์ฃผ์ด์ก์ ๋ ๊ฐ๊ฐ ๋ง์
, ๊ณฑ์
, ๋บ์
ํจ์๋ฅผ ๊ตฌํํ๊ณ ์ด๋ฅผ pipe
๋ฅผ ์ฌ์ฉํด ํจ์ ํฉ์ฑ์ผ๋ก ํํํด๋ณด์.
2. Pipe in JavaScript
ํด๋น ์น์
์ compose
์ pipe
๋ฅผ ๋ชจ๋ ์ ์ฉํด ์ฐจ์ด๋ฅผ ์์๋ณธ๋ค. JavaScript ๋ฅผ ์ฌ์ฉํด compose ์ pipe ๋ฅผ ๊ตฌํํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
const compose =
(...fns) =>
(initValue) =>
fns.reduceRight(
(acc, fn) => (acc instanceof Promise ? acc.then(fn) : fn(acc)),
initValue,
);
const pipe =
(...fns) =>
(initValue) =>
fns.reduce(
(acc, fn) => (acc instanceof Promise ? acc.then(fn) : fn(acc)),
initValue,
);
์ฐธ๊ณ ๋ก ๋น๋๊ธฐ ํจ์์ ๋ํด์๋
pipe
๋ฅผ ์ ์ฉํ๊ณ ์ ํ ๊ฒฝ์ฐasync pipe
๋ฅผ ๋ง๋ค์ด์ผํ๋ค.
ํจ์๋ฅผ ํฉ์ฑํ๊ธฐ ์ํด ๋ง์ , ๊ณฑ์ , ๋บ์ ์ ๋ํ ์์ ํจ์๋ฅผ ๋ง๋ ๋ค.
const add = (lhs, rhs) => lhs + rhs
const multiply = (lhs, rhs) => lhs * rhs
const subtract = (lhs, rhs) => lhs - rhs
const add5 = (x) => add(x, 5)
const multiply4 = (x) => multiply(x, 4)
const subtract6 = (x) => subtract(x, 6)
const toString = x => String(x)
- pipe
const functionComposition = pipe(
add5,
multiply4,
subtract6,
toString
)
console.log(functionComposition(7)) // "42"
- compose
const functionComposition = compose(
toString,
subtract6,
multiply4,
add5
)
console.log(functionComposition(7)) // "42"
map(_:)
ํจ์๋ฅผ ์ด์ฉํด ๋ฐฐ์ด์ ์ ์ฉํด๋ณด์.
const someArray = [1, 5, 7, 13, 23, 37]
const newArray = someArray.map(functionComposition)
console.log(newArray) // ["18", "34", "42", "66", "106", "162"]
map(_:)
ํจ์๋ฅผ ์ด์ฉํด ๋ฐฐ์ด์ ์ ์ฉํด๋ณด์.
const someArray = [1, 5, 7, 13, 23, 37]
const newArray = someArray.map(functionComposition)
console.log(newArray) // [18, 34, 42, 66, 106, 162]
3. Pipe in TypeScript
type AnyFunction = (value: unknown) => any;
export const compose =
(...fns: AnyFunction[]) =>
(initValue?: unknown) =>
fns.reduceRight((acc, fn) => (acc instanceof Promise ? acc.then(fn) : fn(acc)), initValue);
export const pipe =
(...fns: AnyFunction[]) =>
(initValue?: unknown) =>
fns.reduce((acc, fn) => (acc instanceof Promise ? acc.then(fn) : fn(acc)), initValue);
๋ค๋ฅธ ๋ฐฉ๋ฒ์ผ๋ก๋ TypeScript ์์ ํจ์ํ ์ฝ๋ฉ์ ๊ฐ๋ฅํ๋๋ก ๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅด ์ฌ์ฉํ๋ ๊ฒ์ด๋ค. ์ง์ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ๋ค.
ํจ์๋ฅผ ํฉ์ฑํ๊ธฐ ์ํด ๋ง์ , ๊ณฑ์ , ๋บ์ ์ ๋ํ ์์ ํจ์๋ฅผ ๋ง๋ ๋ค.
const add = (lhs: number, rhs: number): number => lhs + rhs
const multiply = (lhs: number, rhs: number): number => lhs * rhs
const subtract = (lhs: number, rhs: number): number => lhs - rhs
const add5 = (x: number): number => add(x, 5)
const multiply4 = (x: number): number => multiply(x, 4)
const subtract6 = (x: number): number => subtract(x, 6)
const toString = (x: number): string => String(x)
- pipe
const functionComposition = pipe(
add5,
multiply4,
subtract6,
toString
)
console.log(functionComposition(7)) // "42"
- compose
const functionComposition = compose(
toString,
subtract6,
multiply4,
add5
)
console.log(functionComposition(7)) // "42"
map(_:)
ํจ์๋ฅผ ์ด์ฉํด ๋ฐฐ์ด์ ์ ์ฉํด๋ณด์.
const someArray: number[] = [1, 5, 7, 13, 23, 37]
const newArray: Primitive[] = someArray.map(functionComposition)
console.log(newArray) // ["18", "34", "42", "66", "106", "162"]
4. Pipe in Swift
Swift ๋ TypeScript ์ ๊ฐ์ด ๋ชจ๋ ํจ์๋ฅผ ๋ฐ์ ์ ์๋ Function
๊ณผ ๊ฐ์ Types ๊ฐ ์กด์ฌํ์ง ์๊ธฐ ๋๋ฌธ์ JavaScript ๋ TypeScript
ํ์์ pipe(_:)
๋ ์ ์๊ฐ ๋ถ๊ฐ๋ฅํ๋ค.
func pipe<T>(_ fns: ((T) -> T)...) -> (T) -> T {
{ initValue in
fns.reduce(initValue) { acc, fn in
fn(acc)
}
}
}
pipe(_:)
ํจ์์ ์ ๋ฌ๋๋ ๋ชจ๋ Variadic Parameters ์ Types ๊ฐ ๋์ผํด์ผํ๋ค. ์๋ฅผ ๋ค์ด add, multiply, subtract
๋ ๋ชจ๋ (Int) -> Int
Types ์ด๋ฏ๋ก ์๊ด ์์ง๋ง ์ดํ toString ์ด ์ถ๊ฐ๋๋ฉด ์ด ํจ์๋ (Int) -> String
Types ์ด๋ฏ๋ก ์ฒ์
์ ๋ฌ๋ add ์ ์ํด ์ด๋ฏธ Generic Types ๊ฐ ์ ํด์ก์ผ๋ฏ๋ก ์๋ฌ๊ฐ ๋ฐ์๋๋ค.
func pipe<T, R>(_ fns: ((T) -> R)...) -> (T) -> R {
{ (initValue: T) in
fns.reduce(initValue) { acc, fn in
fn(acc) as! T
} as! R
}
}
Generic Types ๋ฅผ <T, R>
๋ก ์ ์ํด๋ ์ด์ฐจํผ ๋ ๋ค Int
๋ก ์ ํด์ ธ fns
๊ฐ [(Int) -> Int]
Types ๋ก ์ ํด์ ธ ์๋ฏธ๊ฐ ์๋ค.
๋์ Swift ์์๋ Elixir Pipe Operator ์คํ์ผ์ ์ฌ์ฉํ ์ ์๋ค.
infix operator |>: AdditionPrecedence
func |> <T, R>(value: T, function: (T) -> R) -> R {
return function(value)
}
ํจ์๋ฅผ ํฉ์ฑํ๊ธฐ ์ํด ๋ง์ , ๊ณฑ์ , ๋บ์ ์ ๋ํ ์์ ํจ์๋ฅผ ๋ง๋ ๋ค.
func add(_ lhs: Int, _ rhs: Int) -> Int {
lhs + rhs
}
let multiply = { (lhs: Int, rhs: Int) in lhs * rhs }
let subtract = { (lhs: Int, rhs: Int) in lhs - rhs }
func add5(_ x: Int) -> Int {
add(x, 5)
}
let multiply4 = { (x: Int) in multiply(x, 4) }
let subtract6 = { (x: Int) in subtract(x, 6) }
let toString = { (x: Int) in String(x) }
- pipe
func functionComposition(_ initValue: Int) -> String {
initValue
|> add5
|> multiply4
|> subtract6
|> toString
}
print(functionComposition(7)) // "42"
Closures ๋ฅผ ์ฌ์ฉํด Inline ์ผ๋ก ์์ฑํ๋ฉด ์ข ๋ pipe ๋ต๊ฒ ์ฌ์ฉํ ์ ์๋ค.
let functionComposition = {
$0
|> add5
|> multiply4
|> subtract6
|> toString
}
print(functionComposition(7)) // "42"
map(_:)
ํจ์๋ฅผ ์ด์ฉํด ๋ฐฐ์ด์ ์ ์ฉํด๋ณด์.
let someArray: [Int] = [1, 5, 7, 13, 23, 37]
let newArray: [String] = someArray.map(functionComposition)
print(newArray) // ["1", "5", "7", "13", "23", "37"]
9. Currying ๐ฉโ๐ป
1. What is the Currying?
Pipe
๋ ํจ์์ Arguments ๋ก ์ฌ๋ฌ ๊ฐ์ ํจ์๋ฅผ ๋ฐ์ ์ด๋ฅผ ์์ฐจ์ ์ผ๋ก ์ฐ๊ฒฐํ๊ธฐ ์ํด ์ฌ์ฉํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๊ฒ์ด ๊ฐ๋ฅํ๋๋ก ํ๊ธฐ ์ํด Monad ๋ฅผ
์ฌ์ฉํ๋ค. Currying ์ ์ด๊ฒ์ ๋ฐ๋๋ค. ํ๋์ ํจ์์ ์กด์ฌํ๋ ์ฌ๋ฌ ๊ฐ์ Arguments ๋ฅผ ์ฌ๋ฌ ๋จ๊ณ๋ก ๋๋๋ค. ์ด๊ฒ์ด ์ํ์ ์ผ๋ก ๊ฐ๋ฅํ๊ฐ์
๋ํด์๋ 4. Lambda Calculus ์์ ์ด๋ฏธ ํ์ธํ๋ค.
ํ ๋ฒ์ ๋ชจ๋ Arguments ๋ฅผ ๋ฐ์ง ์๊ณ ๋๋ ๋ฐ์ผ๋ฏ๋ก Evaluation ์ ์ง์ฐ์ํฌ ์ ์๋ค. ์ฆ, Lazy Evaluation ์ด ๊ฐ๋ฅํ๋ค. ์ด๊ฒ์ ๋ฌด์์ ์๋ฏธํ ๊น? ์ฌ์ ์ ์ ์ํ ์ ์๋ ๋ถ๋ถ๊น์ง ๋ฏธ๋ฆฌ ์ ์ํด ๋ ๋ค์ ํ์ํ ๊ฒ๋ง ๋ฐ๊ฟ ์ฌ์ฉํ ์ ์์์ ์๋ฏธํ๋ค. ๋ค์ ๋งํ๋ฉด ๋จ๊ณ๋ฅผ ๋๋์ผ๋ก์จ ๋ค์ ์ค๋ Arguments ๋ง ๋ฐ๊ฟ์ผ๋ก์จ ์ฌ์ฌ์ฉ์ด ๊ฐ๋ฅํ ์๋ก ๋ค๋ฅธ ํจ์๋ฅผ ๋ง๋ค์ด ๋ผ ์ ์๋ค๋ ๊ฒ์ ์๋ฏธํ๋ค.
((x, y, z) ~> 2x^2 + 5y + z)(3, 5, 2)
๋ฅผ
(x ~> (y ~> (z ~> 2x^2 + 5y + z)))(3)(5)(2)
๋ก ๋ค์ ์ฐ๋ ๊ฒ์ด Currying ์ด๋ค.
2. Curry Function in JavaScript
const howMuch = (goods, price, count, unit) => `${goods} ${count} ${unit} = ${price * count}์`
console.log(howMuch('์ฌ๊ณผ', 1350, 5, '๊ฐ')) // ์ฌ๊ณผ 5 ๊ฐ = 6750์
console.log(howMuch('์ฐธ์น', 2730, 3, '์บ')) // ์ฐธ์น 3 ์บ = 8190์
์ ํจ์์ Currying ์ ์ ์ฉํด๋ณด์. ์ํ์ ๋ฐ๋ผ ๋จ์๊ฐ ์ ํด์ง๋ ๋จผ์ ์ ๋ ฅ ๋ฐ๊ณ , ๊ทธ ๋ค์ ๊ทธ ๋ ์ ๊ฐ๊ฒฉ์ ๋ฐ๋ผ ์ํ์ ๋ช ๊ฐ ์๋์ ๋ฐ๋ผ ์ต์ข ๊ฐ๊ฒฉ์ด ์ ํด์ง๋ฏ๋ก, ๊ฐ๊ฒฉ์ ๋ฐ์ ๋ค์ ๊ฐ์๋ฅผ ๋ฐ์ผ๋ฉด ๋ ๊ฒ์ด๋ค.
const howMuch = (goods, unit) => price => count => `${goods} ${count} ${unit} = ${price * count}์`
const applePrice = howMuch('์ฌ๊ณผ', '๊ฐ')
const tunaPrice = howMuch('์ฐธ์น', '์บ')
const todayApplePrice = applePrice(1350)
const todayTunaPrice = tunaPrice(2730)
console.log(todayApplePrice(5)) // ์ฌ๊ณผ 5 ๊ฐ = 6750์
console.log(todayTunaPrice(3)) // ์ฐธ์น 3 ์บ = 8190์
์ด๋ ๊ฒ Currying ์ ์ ์ฉํ๋ฉด ์ค๋ ์ฌ๊ณผ n ๊ฐ์ ๊ฐ๊ฒฉ์ ์กฐํํ ๋๋ง๋ค ๋งค๋ฒ ์ ์ฒด ํจ์๋ฅผ ๋ค์ ํ๊ฐํ๋ ๊ฒ์ด ์๋๋ผ todayApplePrice(_:)
๋ง
ํ๊ฐํ๋ฉด ๋์ผํ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์๋ค.
Curry Function
์ฌ์ฉ์๊ฐ ์ง์ Currying ์ ์ ์ฉํ ํจ์๋ฅผ ๋ง๋ค ์๋ ์์ง๋ง, ๊ธฐ์กด์ ํจ์๋ฅผ Curry Function ์ ์ ์ฉํด ์๋์ผ๋ก Currying ์ด ์ ์ฉ๋ ์ ํจ์๋ฅผ ๋ง๋ค๋๋ก Factory Function ์ ์ ์ํ ์ ์๋ค.
const curry = (fn) => {
return function curryFn(...args1) {
if (args1.length >= fn.length) {
return fn(...args1);
} else {
return (...args2) => {
return curryFn(...args1, ...args2);
}
}
}
}
Curry Function ์ ์ ์ฉํด๋ณด์.
const howMuch = (goods, unit, price, count) => `${goods} ${count} ${unit} = ${price * count}์`
const curriedHowMuch = curry(howMuch)
const applePrice = curriedHowMuch('์ฌ๊ณผ', '๊ฐ')
const tunaPrice = curriedHowMuch('์ฐธ์น', '์บ')
const todayApplePrice = applePrice(1350)
const todayTunaPrice = tunaPrice(2730)
console.log(todayApplePrice(5)) // ์ฌ๊ณผ 5 ๊ฐ = 6750์
console.log(todayTunaPrice(3)) // ์ฐธ์น 3 ์บ = 8190์
Curry Function
์ ์ ์ฉํ ๋ ์ฃผ์ํด์ผํ ๊ฒ์ Currying ์์๋ฅผ ๊ณ ๋ คํด์ Parameters ์ ์์๋ฅผ ์ ์ํด์ผํ๋ค.
๋ฐ๋ผ์ ๊ธฐ์กด ํจ์์ Parameters ๋(goods, price, count, unit)
์์ผ๋ Curry Function ์ ์ ์ฉํ๊ธฐ ์ํด ์์๋ฅผ(goods, unit, price, count)
๋ก ๋ณ๊ฒฝํ๋ค.
3. Curry Function in TypeScript
์ ์ฝ๋๋ฅผ TypeScript ๋ก ๋ค์ ์จ๋ณด์.
const howMuch = (goods: string,
price: number,
count: number,
unit: string)
: string => `${goods} ${count} ${unit} = ${price * count}์`
console.log(howMuch('์ฌ๊ณผ', 1350, 5, '๊ฐ')) // ์ฌ๊ณผ 5 ๊ฐ = 6750์
console.log(howMuch('์ฐธ์น', 2730, 3, '์บ')) // ์ฐธ์น 3 ์บ = 8190์
Currying ์ ์ ์ฉํด๋ณด์.
const howMuch = (goods: string, unit: string) => (price: number) => (count: number)
: string => `${goods} ${count} ${unit} = ${price * count}์`
const applePrice = howMuch('์ฌ๊ณผ', '๊ฐ')
const tunaPrice = howMuch('์ฐธ์น', '์บ')
const todayApplePrice = applePrice(1350)
const todayTunaPrice = tunaPrice(2730)
console.log(todayApplePrice(5)) // ์ฌ๊ณผ 5 ๊ฐ = 6750์
console.log(todayTunaPrice(3)) // ์ฐธ์น 3 ์บ = 8190์
Curry Function
const curry = (fn: Function) => {
return function curryFn<T>(...args1: T[]) {
if (args1.length >= fn.length) {
return fn(...args1);
} else {
return (...args2: T[]) => {
return curryFn(...args1, ...args2);
};
}
};
};
Curry Function ์ ์ ์ฉํด๋ณด์.
const howMuch = (goods: string,
unit: string,
price: number,
count: number)
: string => `${goods} ${count} ${unit} = ${price * count}์`
const curriedHowMuch = curry(howMuch)
const applePrice = curriedHowMuch('์ฌ๊ณผ', '๊ฐ')
const tunaPrice = curriedHowMuch('์ฐธ์น', '์บ')
const todayApplePrice = applePrice(1350)
const todayTunaPrice = tunaPrice(2730)
console.log(todayApplePrice(5)) // ์ฌ๊ณผ 5 ๊ฐ = 6750์
console.log(todayTunaPrice(3)) // ์ฐธ์น 3 ์บ = 8190์
4. Curry Function in Swift
func howMuch(goods: String, price: Int, count: Int, unit: String) -> String {
"\(goods) \(count) \(unit) = \(price * count)์"
}
print(howMuch(goods: "์ฌ๊ณผ", price: 1350, count: 5, unit: "๊ฐ")) // ์ฌ๊ณผ 5 ๊ฐ = 6750์
print(howMuch(goods: "์ฐธ์น", price: 2730, count: 3, unit: "์บ")) // ์ฐธ์น 3 ์บ = 8190์
Currying ์ ์ ์ฉํด๋ณด์.
func howMuch(goods: String, unit: String) -> (Int) -> (Int) -> String {
{ price in { count in "\(goods) \(count) \(unit) = \(price * count)์" }}
}
let applePrice = howMuch(goods: "์ฌ๊ณผ", unit: "๊ฐ")
let tunaPrice = howMuch(goods: "์ฐธ์น", unit: "์บ")
let todayApplePrice = applePrice(1350)
let todayTunaPrice = tunaPrice(2730)
print(todayApplePrice(5)) // ์ฌ๊ณผ 5 ๊ฐ = 6750์
print(todayTunaPrice(3)) // ์ฐธ์น 3 ์บ = 8190์
Swift ์์ Curring ์ TypeScript ์ ๋ง์ฐฌ๊ฐ์ง๋ก Function Expression ๋ฐฉ์์ ์ฌ์ฉํ๋ ๊ฒ์ด Parameters ์ Body ๋ฅผ ์์ ํ ๋ถ๋ฆฌ์์ผ ์ ์ํ ์ ์์ด ๊ฐ๋ ์ฑ์ด ๋ ๋ฐ์ด๋๋ค.
let howMuch = { (goods: String, unit: String) in { (price: Int) in { (count: Int) in
"\(goods) \(count) \(unit) = \(price * count)์"
}}}
let applePrice = howMuch("์ฌ๊ณผ", "๊ฐ")
let tunaPrice = howMuch("์ฐธ์น", "์บ")
let todayApplePrice = applePrice(1350)
let todayTunaPrice = tunaPrice(2730)
print(todayApplePrice(5)) // ์ฌ๊ณผ 5 ๊ฐ = 6750์
print(todayTunaPrice(3)) // ์ฐธ์น 3 ์บ = 8190์
Curry Function
์์ฝ๊ฒ๋ Swift ๋ TypeScript ์ Function
๊ณผ ๊ฐ์ ๋ชจ๋ ํ์
์ ํจ์ ๋๋ ํด๋ก์ ๋ฅผ ๋ํ๋ผ ์ ์๋ Types ๊ฐ ์กด์ฌํ์ง ์๊ธฐ ๋๋ฌธ์
๋ชจ๋ ํ์
์ ํจ์์ ์๋๋๋ Currying ํจ์๋ฅผ ์ ์ํ ์ ์๋ค.
๋ํ, First-Class Citizens ์ด๋ฏ๋ก ํจ์์ ํ๋ผ๋ฏธํฐ๋ก ํจ์๋ ํด๋ก์ ๋ฅผ ์ ๋ฌํ ์๋ ์์ง๋ง, Generic ์ ์ฌ์ฉํ ํจ์๋ ํด๋ก์ ๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌํ ๋ ๋ฐ๋์ ํ์ ์ ๋ช ์ํด์ผํ๊ธฐ ๋๋ฌธ์ ํ์ ์ถ๋ก ์ด ์ ํ๋๋ค. ๋ฐ๋ผ์ ํจ์์ ํ๋ผ๋ฏธํฐ๋ก Generic ์ ์ฌ์ฉํ ํจ์๋ ํด๋ก์ ๋ฅผ ์ ๋ฌํ๊ฑฐ๋, Currying ์ค๊ฐ ๊ฒฐ๊ณผ๋ฅผ ๋ณ์์ ์ ์ฅํ๋ ๊ฒ์ด ์ ํ๋๋ค. ๋ค์ ์ฑํฐ Restriction in Defining a Curry Function in Swift ์์ Swift ์์ Curry Function ์ ์ ์ํ๋ ๊ฒ์ด ์ ์ด๋ ค์ด์ง ์ง์ ์ดํด๋ณด๋๋ก ํ๋ค.
10. Restriction in Defining a Curry Function in Swift ๐ฉโ๐ป
1. Simple Curry Function in Swift
Swift ๋ ํ์
์ ๋งค์ฐ ์๊ฒฉํ๊ธฐ ๋๋ฌธ์ Curry Function
์ ์์ฑํ๋ ๊ฒ์ด ์ฝ์ง ์๋ค. ๋ค์์ ๋งค์ฐ ๊ธฐ๋ณธ์ ์ธ ํํ์ Curry Function ์ด๋ค.
func curry<T, U, V>(_ function: @escaping (T, U) -> V) -> (T) -> (U) -> V {
{ (first: T) in
{ (second: U) in
function(first, second)
}
}
}
์ curry(_:)
ํจ์๋ ์ธ ๋ฒ์ ํ๋ผ๋ฏธํฐ ์
๋ ฅ์ ๋ฐ์ ์ต์ข
์ ์ผ๋ก ์คํ๋๋ค.
- ์ฒซ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ:
T
ํ์ ๊ณผU
ํ์ ์ ๋ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ์์V
ํ์ ์ ๋ฆฌํด์ ๊ฐ๋ ํจ์ - ๋ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ:
T
ํ์ - ์ธ ๋ฒ์งธ ํ๋ผ๋ฏธํฐ:
U
ํ์
func add(_ lhs: Int, _ rhs: Int) -> Int {
lhs + rhs
}
let curriedAdd = curry(add)
let addTwo = curriedAdd(2)
let addTen = curriedAdd(10)
print((1...5).map(addTwo)) // [3, 4, 5, 6, 7]
print((1...5).map(addTen)) // [11, 12, 13, 14, 15]
์ผํ ๋ณด๋ฉด Curry Function
์ด ์ ์๋ํ๋ ๊ฒ์ฒ๋ผ ๋ณด์ด์ง๋ง ๋ฐ๋ก ์ด ํจ์๊ฐ ๋ฌธ์ ๋ค. Swift ์๋ TypeScript ์ Function
๊ณผ
๊ฐ์ ํ์
์ด ์กด์ฌํ์ง ์์ ํ๋ฆฌ๋ฏธํฐ์ ๊ฐ์๊ฐ ๋ฌ๋ผ์ง๋ฉด ์ค๋ฒ๋ก๋ฉ์ ์ด์ฉํด ํ๋ผ๋ฏธํฐ๊ฐ 2๊ฐ, 3๊ฐ, 4๊ฐ, โฆ ์ผ ๋์ Curry Function
์
์ถ๊ฐ๋ก ๋ง๋ค์ด์ผ ํ๋ค.
์ค์ ๋ก TypeScript ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์์๋ Function
๊ณผ ๊ฐ์ ๋๋ฌด ๋์ ๋ฒ์๋ฅผ ํฌํจํด JavaScript ์ ๊ฐ์ ๋์์ ํ์ง ์๋๋ก
ํ์
์ฒดํฌ๋ฅผ ๊ฐํํ๊ธฐ ์ํด ํ๋ผ๋ฏธํฐ๊ฐ 2๊ฐ, 3๊ฐ, โฆ, 9๊ฐ์ผ ๋์ ์๋๋๋ Curry Function
์ ๋ฏธ๋ฆฌ ๋ง๋ค์ด ๋์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์กด์ฌํ๋ค.
์ฆ, Swift ์ญ์ ์ด๋ฐ ๋ฐฉ๋ฒ์ ํํด์ผ๋ง ๊ธฐ์กด ํจ์๋ฅผ Currying ์ํฌ ์ ์๋ค. ๊ทธ๋ฆฌ๊ณ , ๋ง์ฝ Function
๊ณผ ๊ฐ์ ํ์
์ด ์๊ฒจ๋๋ค ํ๋๋ผ๋,
Variadic Parameters ๋ ์กด์ฌํ์ง๋ง, TypeScript ์ Spread Operator
์ ๊ฐ์ด ์ฝ๋ ๋ผ์ธ์์ ์ฌ์ฉํ ์ ์๋ ๊ฒ์ ์๋๊ธฐ ๋๋ฌธ์
๊ฒฐ๊ตญ ์ฌ๊ทํจ์๋ ๋์ฑ ๋ณต์กํด์ง๊ฒ ๋๋ค.
์ผ๋ฐ ํจ์๋ฅผ ์์ฑ ํ Curry Function ์ ์ฌ์ฉํด ํจ์๋ฅผ Currying ์ํค๋ ๊ฒ ์๋๋ผ, ๋ฐ๋ก ์ Curry Function in Swift ์์ ์ดํด๋ณธ ๊ฒ์ฒ๋ผ ํจ์๋ฅผ ์์ฑํ ๋ Currying ์ด ๋๋๋ก ์์ฑํด์ผํ๋ค.
func howMuch(goods: String, unit: String) -> (Int) -> (Int) -> String {
{ price in { count in "\(goods) \(count) \(unit) = \(price * count)์" }}
}
let howMuch = { (goods: String, unit: String) in { (price: Int) in { (count: Int) in
"\(goods) \(count) \(unit) = \(price * count)์"
}}}
ํ์ง๋ง ์ด ๋ฐฉ๋ฒ ์ญ์ Generic
๊ณผ ํจ๊ป ์ฌ์ฉํ๊ธฐ๋ ์ด๋ ต๋ค.
2. Generic Makes Type Inference to Hard in Currying
1 ) Sample Function with Generic
Generic ์ ์ฌ์ฉํ๋ฉด ์ Currying ์ ์ ์ฉํ๊ธฐ ์ด๋ ค์ด์ง ์์๋ณด์. ๋ค์์ ์ซ์๋ฅผ ๋ฐ์ 2๋ฐฐ๋ก ๋ง๋ค์ด ๋ฐํํ๋ ํจ์์ Int ์ Double ํ์ ๋ฒ์ ์ด๋ค.
func productTwo(_ number: Int) -> Int {
number * 2
}
func productTwo(_ number: Double) -> Double {
number * 2
}
let alpha = productTwo(3) // 6
let beta = productTwo(4.7) // 9.4
Generic Functions ์์ ์ ์ํ๋ swap(_:_:)
ํจ์ ์ฒ๋ผ Generic ์ ์ฌ์ฉํด ๋ชจ๋ ์ซ์ ํ์
์์ ์๋๋๋๋ก ๋ง๋ค์ด๋ณด์.
func productTwo<T: Numeric>(_ number: T) -> T {
number * 2
}
let alpha = productTwo(3) // 6
let beta = productTwo(4.7) // 9.4
2 ) Make a Parameter of the Sample Function to Optional
์ธ๋ถ API ์ ํต์ ํ๋ ๊ฒฝ์ฐ ๋ง์ ๊ฐ๋ค์ด ์กด์ฌํ์ง ์์ ์ ์๊ธฐ ๋๋ฌธ์ Optional ํํ์ ๋ฐ์ดํฐ ํ์
์ ๋ง์ด ์ฌ์ฉํ๋ค. ๋ฐ๋ผ์ ํจ์๋ฅผ
ํธ์ถํ๊ธฐ ์ ๋ง์ ๊ฒฝ์ฐ๋ Type Guard
๋ฅผ ์์ฑํด์ผํ๋ค. ์ฐ๋ฆฌ๊ฐ ๋ง๋ productTwo(_:)
ํจ์๊ฐ ์ด๋ฐ ์ํฉ์์ ์์ฃผ ์ฌ์ฉ๋๋ค๊ณ ํด๋ณด์.
_ = {
let foo: Int? = 3
let bar: Double? = 4.7
guard let foo, let bar else { return }
let alpha = productTwo(foo)
let beta = productTwo(bar)
print(alpha, beta) // 6 9.4
}()
๋ฐ๋ผ์ ํจ์ productTwo(_:)
๊ฐ Parameters ๋ฅผ Optioanl
๋ก ๋ฐ๋๋ก ๋ณ๊ฒฝํด๋ณด์.
enum ArgumentError: Error {
case argumentIsNil
}
func productTwo<T: Numeric>(_ number: T?) throws -> T {
guard let number else { throw ArgumentError.argumentIsNil }
return number * 2
}
_ = {
let foo: Int? = 3
let bar: Double? = 4.7
guard let alpha = try? productTwo(foo),
let beta = try? productTwo(bar) else { return }
print(alpha, beta) // 6 9.4
}()
3 ) Reusable Function for Type Guard
๊ทธ๋ฐ๋ฐ ์ด๋ฐ ํจ์๊ฐ ๋งค์ฐ ๋ง๋ค๋ฉด? ๋ชจ๋ ํจ์๋ฅผ ์ด๋ฐ์์ผ๋ก ๋ฐ๊พผ๋ค๋ฉด ๋
ธ๊ฐ๋ค์ผ ๋ฟ ์๋๋ผ ์๋ง์ ์ค๋ณต ์ฝ๋๊ฐ ์๊ฒจ๋๋ ๊ฒ์ด๋ค.
์ฐ๋ฆฌ๋ ์ค๋ณต ์ฝ๋ ๋์ Generic ์ ์ ์ฉํ checkNil(_:)
ํจ์๋ฅผ ๋ง๋ค์ด ์ฌ์ฌ์ฉ ํ ์ ์๋ค.
enum CastingError: Error {
case inputIsNil
}
func checkNil<T>(_ data: T?) throws -> T {
guard let unwrappedData = data else { throw CastingError.inputIsNil }
return unwrappedData
}
func productTwo<T: Numeric>(_ number: T?) throws -> T {
let unwrappedValue = try checkNil(number)
return unwrappedValue * 2
}
4 ) Wrap Type Guard and Feature Functions using Currying
ํ์ง๋ง ์ฒ์๋ถํฐ ํจ์์ Parameter ์ Optional ์ ํ์ฉํ๋ ๊ฒ์ ํจ์๋ฅผ ์์ํ์ง ์๊ฒ ๋ง๋ ๋ค. ์์์ Swift ๋ TypeScript ์ ๋ฌ๋ฆฌ ๋ชจ๋ ํจ์ ํ์ ์ ๋์ํ๋ Curry Function ์ ์์ฑํ๋ ๊ฒ์ด ์ด๋ ต๋ค๊ณ ํ๋ค.
๊ทธ๋ ๋ค๋ฉด ํจ์ checkNil(_:)
+ (T) -> T
๋ ํจ์๋ฅผ ๋ฐ์ (T?) throws -> T
ํจ์๋ฅผ ๋ง๋ค์ด์ฃผ๋
Wrapping Function ์ Currying ์ ์ฌ์ฉํด ๋ง๋๋ ๊ฒ์ ๊ฐ๋ฅํ์ง ์์๊น?
typealias UnwrappingFn<T> = (T?) throws -> T
typealias NumericFn<T: Numeric> = (T) -> T
typealias WrappedNumericFn<T: Numeric> = (T?) throws -> T
ํ์ํ ํ์ ์ ์ 3๊ฐ๊ฐ ๋ ๊ฒ์ด๋ค. ์ฌ๊ธฐ์ ๋ง๊ฒ ์ฝ๋๋ฅผ ์์ฑํด๋ณด์.
enum ArgumentError: Error {
case argumentIsNil
}
func checkNil<T>(_ data: T?) throws -> T {
guard let unwrappedData = data else { throw ArgumentError.argumentIsNil }
return unwrappedData
}
func wrapFunction<T, U: Numeric>(unwrappingFn: @escaping UnwrappingFn<T>) -> (@escaping NumericFn<U>) -> WrappedNumericFn<U> {
{ numericFn in
{ data in
let unwrappedData = try checkNil(data)
return numericFn(unwrappedData)
}
}
}
์, ์ด์ ์ฒซ ๋ฒ์งธ ํธ์ถ๋ก checkNil(_:)
์ ๋ฃ๊ณ , ๊ทธ ๋ค์ productTwo(_:T)
๋ฅผ ๋ฃ์ผ๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
let createWrappedNumericFn = wrapFunction(unwrappingFn: checkNil)
// Error, Generic parameter 'T' could not be inferred
ํ์ ์ถ๋ก ์ด ์๋ํ์ง ์๋๋ค๊ณ ์๋ฌ๋ฅผ ๋์ด๋ค.
let createWrappedNumericFn: (@escaping NumericFn<T>) -> WrappedNumericFn = wrapFunction(unwrappingFn: checkNil)
// Error, Cannot find type 'T' in scope
ํ์ ์ ์ ์ธํด์ค๋ ์ฌ์ ํ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค. ์ฌ๊ธฐ์ ์ค์ํ๊ฒ ๋ด์ผํ ๊ฒ์ด Generic ์ Function Definition ์์๋ง ์ฌ์ฉ์ด ๊ฐ๋ฅํด Type Declaration ์์๋ Generic ์ ์ฌ์ฉํ ์ ์๋ค๋ ๊ฒ์ด๋ค.
์ด๊ฒ์ด ๋ฌด์จ ๋ง์ธ์ง ์๊ธฐ ์ฝ๊ฒ
checkNil(_:)
ํจ์ ํ๋๋ง ๋ฐ๋ก ์ดํด๋ณด์.func checkNil<T>(_ data: T?) throws -> T { guard let unwrappedData = data else { throw ArgumentError.argumentIsNil } return unwrappedData }
์ด ํจ์์ ๋ชจ์์ ๋ค์๊ณผ ๊ฐ๋ค.
func checkNil<T>(_ data: T?) throws -> T
checkNil(_:)
ํจ์๋ฅผ ๋ค๋ฅธ ๋ณ์์ ํ ๋นํด๋ณด์.let anotherCheckNil: UnwrappingFn<T> = checkNil // Error, Cannot find type 'T' in scope
๋ฐ๋ก ์์์ ๋ฐ์ํ ๊ฒ๊ณผ ๊ฐ์ ์๋ฌ๋ค. ์์
anotherCheckNil
์ ํ์ ์UnwrappingFn<T>
๋ก ์ ์ธํ๋ ๊ณผ์ ์์ ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค. Generic ์ ๋ณ์๊ฐ ๊ฐ์ง ์ ์๋ ํ์ ์ ์ ์ธํ ๋๋ ์ฌ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
typealias
๋ ์ค์ ๋ก ํ์ ์ ์ ์ธํ๋ ๊ฒ์ด ์๋๋ผ, ํ์ ์ ๋ชจ์์ ๋ํ ์ ์๋ก๋ถํฐalias
๋ฅผ ํ๋ ๊ฒ์ด๋ค.let checkNilAny: UnwrappingFn<Any> = checkNil let checkNilInt: UnwrappingFn<Int> = checkNil let checkNilString: UnwrappingFn<String> = checkNil
์ด๋ ๊ฒ Generic ๋์ ๋ช ํํ ํ์ ์ ์ง์ ํด์ค์ผํ๋ค. ์ฌ๊ธฐ์
checkNilAny
๋ ํ์ ์ ์๋ฌด๋ฐ ์ ์ฝ์ด ์กด์ฌํ์ง ์์ผ๋ฏ๋ก,checkNil(_:)
ํจ์์ ๋์ผํ๋ค.checkNilInt
๋ Int ํ์ ์ผ๋ก ์ ์ฝ๋์์ผ๋ฏ๋ก,func checkNil<T>(_ data: T?) throws -> T where T : SignedInteger
์ ๊ฐ๋ค.
๋ค์ wrapFunction(unwrappingFn:)
์ผ๋ก ๋์๊ฐ๋ณด์.
let createWrappedNumericFn: (@escaping NumericFn<Numeric>) -> WrappedNumericFn = wrapFunction(unwrappingFn: checkNil)
// Error, Type 'any Numeric' cannot conform to 'Numeric'
Generic T
๋์ Numeric
์ ๋ช
์์ ์ผ๋ก ์ฃผ๋ฉด ํด๊ฒฐ๋ ๊ฒ ๊ฐ์์ผ๋, Numeric
์ด ์๋ any Numeric
์ผ๋ก ํ์
์ด ์ ์ธ๋๋ฉฐ,
T: Numeric
์ ์ ์ฅํ๋ ๊ฒ์ด ๋ถ๊ฐ๋ฅํ๋ค๊ณ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
์ฒ์ func curry<T, U, V>(_ function: @escaping (T, U) -> V) -> (T) -> (U) -> V
์์ ์ฒ๋ผ Generic ์ด
๋จ์ํ ์
๋ ฅ ๋ฐ์ ํ๋ผ๋ฏธํฐ ํ์
์์ ์ ์ง์นญํจ์ผ๋ก์จ ํ์
์ ์ฝ๊ฒ ๋ช
์ํ ์ ์๋ Int
, String
๊ฐ์ ์ถ๋ก ์ ๋์๋ ์ ์๋ํ์ง๋ง,
์ด Generic ์ด <T: Numeric>
, <T: Hashable>
๊ณผ ๊ฐ์ด Generic Where Clauses ๋ฅผ ํฌํจํ๋ ํ์
์ถ๋ก ์๋ ์ฌ์ฉ์ด ์ด๋ ต๋ค.
์ด๊ฒ์ Swift ๋ ํ์ ์ ๋ํด ๋งค์ฐ ์๊ฒฉํด Generic ์ด Invariantํ๊ธฐ ๋๋ฌธ์ด๋ค.
5 ) Alternatives
๊ทธ๋ ๋ค๋ฉด ์์ ๋ฐฉ๋ฒ์ด ์๋ ๊ฒ์ผ๊น? ์์ฝ๊ฒ๋ Swift ๋ ์๊ฒฉํ ํ์
๋์ compile-time checking
์ ๊ฐ๋ฅ์ผ ํ๋ ๋์ Generic
์ Invariant
ํ ํน์ฑ ๋๋ฌธ์ TypeScript ์ ๋น๊ตํ๋ฉด ์์ ํจ์ํ ์ฝ๋ฉ์ ํ๋๋ฐ ์ ์ฝ์ด ๋ฐ๋ฅธ๋ค. Swift ์ TypeScript ๋ชจ๋ OOP
์
Functional Programming
์ด ๊ฐ๋ฅํ์ง๋ง, Swift ๋ ์ข ๋ OOP
์ ๊ฐ๊น๊ณ , TypeScript ๋ ์ข ๋ Functional Programming
์
๊ฐ๊น๋ค. ์ด๊ฒ์ ์ธ์ด์ ํน์ง์ผ๋ก, ํด๋น ์ธ์ด์ ์ด์ธ๋ฆฌ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ด ์ข๋ค.
- ๊ธฐ์กด ๋ฐฉ์๋๋ก
Type Guard
๋ฅผ ํ๋ค.
_ = {
let foo: Int? = 3
let bar: Double? = 4.7
guard let foo, let bar else { return }
let alpha = productTwo(foo)
let beta = productTwo(bar)
print(alpha, beta) // 6 9.4
}()
- Swift ๊ฐ ์ ๊ณตํ๋
Optional
Monad ๋ฅผ ์ฌ์ฉํ๋ค.
_ = {
let foo: Int? = 3
let bar: Double? = 4.7
guard let alpha = Optional(foo)?.map(productTwo(_:)),
let beta = Optional(bar)?.map(productTwo(_:)) else { return }
print(alpha, beta) // 6 9.4
}()
- ํจ์๋ฅผ Overloading ํ๋ค.
enum ArgumentError: Error {
case argumentIsNil
}
func checkNil<T>(_ data: T?) throws -> T {
guard let unwrappedData = data else { throw ArgumentError.argumentIsNil }
return unwrappedData
}
func productTwo<T: Numeric>(_ number: T) -> T {
number * 2
}
func productTwo<T: Numeric>(_ number: T?) throws -> T {
let number = try checkNil(number)
return productTwo(number)
}
๋์ Currying ์ด ์๋๊ธฐ ๋๋ฌธ์ checkNil(_:)
ํจ์๊ฐ ๋ฐ๋์ ์กด์ฌํด์ผํ๋ฉฐ, ์ด์ ๋ํ ์ฑ
์์ด ์ฌ์ฉ์์๊ฒ ์ฃผ์ด์ง๋ค.
์ด ์ธ์๋ ๊ฐ ์ํฉ์ ๋ง๊ฒ Simple Curry Function in Swift ์์์ ๊ฐ์ด ๊ฐ๋จํ ํจ์๋ฉด Currying ์ ํ ์๋ ์์ผ๋ฉฐ, Classes ๋ Structures, Enumerations ๋ฑ์ ์ฌ์ฉํด ๊ตฌํํ๊ฑฐ๋, ํ์์ ๋ฐ๋ผ ์ํฉ์ ๋ง๋ Monad ๋ฅผ ์ง์ ๊ตฌํํด์ ์ฌ์ฉํ ์๋ ์๋ค.
Reference
- โ์ฐธ์กฐ ํฌ๋ช ์ฑ.โ Wikipedia. Feb. 06, 2022, Wikipedia - ์ฐธ์กฐ ํฌ๋ช ์ฑ.
- โ๋ฉฑ๋ฑ๋ฒ์น.โ Wikipedia. Mar. 07, 2022, Wikipedia - ๋ฉฑ๋ฑ๋ฒ์น.
- โํจ์์ ํฉ์ฑ.โ Wikipedia. Jan. 21, 2023, Wikipedia - ํจ์์ ํฉ์ฑ.
- โ๋๋ค ๋์.โ Wikipedia. Jul. 23, 2022, Wikipedia - ๋๋ค ๋์.
- Moon. โํจ์ํ ํ๋ก๊ทธ๋๋ฐ - Pipe.โ Medium. Dec. 29, 2019, ํจ์ํ ํ๋ก๊ทธ๋๋ฐ.
- 12 Math. โgโf ๊ฐ โ์ผ๋์ผ ๋์โ ์ด๋ฉด f ์ g ๋ โ์ผ๋์ผ ๋์โ?.โ, Youtube. Jan. 12, 2023, Bijection ์ ๋ง์กฑํ๋ ํฉ์ฑ ํจ์์ ๋ถํด.
- Dmitry Lupich. โSwift Monads, Functors and Applicatives with examples.โ Medium. Feb. 09, 2020, Swift Monads.
- โPipe Operator.โ Elixir School. Jun. 15, 2023, Elixir Pipe Operator.