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

Injection

One-to-One Function ์ด๋ผ๊ณ  ๋ถˆ๋ฆฌ๋ฉฐ ๊ณต์—ญ(Codomain)์—์„œ ํ™”์‚ด์„ ๋ฐ›๋Š” ๊ฒƒ ์ค‘ ๋‘ ๊ฐœ ์ด์ƒ์˜ ํ™”์‚ด์„ ํ•œ๊บผ๋ฒˆ์— ๋ฐ›๋Š” ์›์†Œ๊ฐ€ ์—†์Œ์„ ์˜๋ฏธํ•œ๋‹ค.

2. Surjection

Surjection

Onto Function ์ด๋ผ๊ณ  ๋ถˆ๋ฆฌ๋ฉฐ ๊ณต์—ญ์—์„œ ํ™”์‚ด์„ ์•ˆ ๋ฐ›๋Š” ์›์†Œ๊ฐ€ ์—†์Œ์„ ์˜๋ฏธํ•œ๋‹ค. ์ฆ‰, ๊ณต์—ญ(Codomain) = ์น˜์—ญ(Range) ์ด ์„ฑ๋ฆฝ๋จ์„ ์˜๋ฏธํ•œ๋‹ค.

3. Bijection

Bijection

Injection & Surjection ์ด ์„ฑ๋ฆฝ๋จ์„ ์˜๋ฏธํ•œ๋‹ค. ์ฆ‰, ์ •์˜์—ญ(Domain)์˜ ๋ชจ๋“  ํ™”์‚ด์ด ๊ณต์—ญ(Codomain)์˜ ๋ชจ๋“  ์›์†Œ์— 1:1๋กœ ๋Œ€์‘ํ•จ์„ ์˜๋ฏธํ•œ๋‹ค. ์ฆ‰, ์ •์˜์—ญ๊ณผ ๊ณต์—ญ์˜ ์›์†Œ์˜ ๊ฐœ์ˆ˜๊ฐ€ ๊ฐ™์œผ๋ฉฐ ๊ณต์—ญ = ์น˜์—ญ์ด ์„ฑ๋ฆฝ๋จ์„ ์˜๋ฏธํ•œ๋‹ค.

4. Composition

ํ•จ์ˆ˜์˜ ๊ณต์—ญ์ด ๋‹ค๋ฅธ ํ•จ์ˆ˜์˜ ์ •์˜์—ญ๊ณผ ์ผ์น˜ํ•˜๋Š” ๊ฒฝ์šฐ, ๋‘ ํ•จ์ˆ˜๋ฅผ ์ด์–ด ํ•˜๋‚˜์˜ ํ•จ์ˆ˜๋กœ ๋งŒ๋“œ๋Š” ์—ฐ์‚ฐ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

Function 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 ์ด ์„ฑ๋ฆฝํ• ๊นŒ?

Disassemble of Composition

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 ๐‘ฆ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

  1. ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜๋“œ์‹œ ์ด๋ฆ„์„ ๊ฐ€์งˆ ํ•„์š”๊ฐ€ ์—†๋‹ค. ํ•จ์ˆ˜์˜ ์ด๋ฆ„ s ๋ฅผ ์ œ๊ฑฐํ•œ๋‹ค.
    (๐‘ฅ, ๐‘ฆ) ~> ๐‘ฅ x ๐‘ฅ + ๐‘ฆ x ๐‘ฆ
  2. ํ•จ์ˆ˜์˜ ์ž…๋ ฅ ๋ณ€์ˆ˜ ์ด๋ฆ„ ๋˜ํ•œ ํ•„์š”๊ฐ€ ์—†๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๐‘ฅ ~> ๐‘ฅ์™€ ๐‘ฆ ~> ๐‘ฆ๋Š” ๋ณ€์ˆ˜ ์ด๋ฆ„์€ ๋‹ค๋ฅด์ง€๋งŒ ๊ฐ™์€ ํ•ญ๋“ฑํ•จ์ˆ˜๋‹ค.
    ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ (๐‘ฅ, ๐‘ฆ) ~> ๐‘ฅ x ๐‘ฅ + ๐‘ฆ x ๐‘ฆ์™€ (๐‘ข, ๐‘ฃ) ~> ๐‘ข x ๐‘ข + ๐‘ฃ x ๐‘ฃ๋Š” ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  3. ๋‘ ๊ฐœ ์ด์ƒ์˜ ์ž…๋ ฅ์„ ๋ฐ›๋Š” ํ•จ์ˆ˜๋Š” ํ•˜๋‚˜์˜ ์ž…๋ ฅ์„ ๋ฐ›์•„ ๋˜ ๋‹ค๋ฅธ ํ•จ์ˆ˜๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ํ•จ์ˆ˜๋กœ ๋‹ค์‹œ ์“ธ ์ˆ˜ ์žˆ๋‹ค.
    (๐‘ฅ, ๐‘ฆ) ~> ๐‘ฅ 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

Category Theory ์—์„œ๋Š” X, Y, Z ๋ฅผ Set, ๊ทธ๋ฆฌ๊ณ  f, g ๋ฅผ Morphism์ด๋ผ ๋ถ€๋ฅธ๋‹ค.

Functor and Applicative Functor and Monad

๊ทธ๋ฆฌ๊ณ  Category Theory ๋ฅผ ์ผ๋ฐ˜ํ™” ์‹œํ‚ค๊ธฐ ์œ„ํ•ด ์ถ”์ƒํ™” ํ•˜๋Š” ๋‹จ๊ณ„์—์„œ Functor, Applicative Functor, Monad ์™€ ๊ฐ™์€ ๊ฒƒ๋“ค์„ ์ดํ•ดํ•ด์•ผํ•œ๋‹ค.

์šฐ์„  Functor ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž. Functor ๊ฐ€ ๊ฐ€์žฅ ์ผ๋ฐ˜ํ™”๋œ ๊ฐœ๋…์ด๊ณ , ์ด๊ฒƒ์€ ๋‹ค์Œ ๊ทธ๋ฆผ์ฒ˜๋Ÿผ lift ์‹œํ‚ค๋Š” ๊ฒƒ๊ณผ ๊ฐ™๋‹ค.

Functor 1

C ์‹œ์Šคํ…œ์„ D ์‹œ์Šคํ…œ์œผ๋กœ ์˜ฎ๊ธธ ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋ชจ๋“  ๊ด€๊ณ„๊ฐ€ ์œ ์ง€๋œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๊ฒƒ์€ ๋‹ค์‹œ ์›๋ž˜๋Œ€๋กœ ๋˜๋Œ์•„๊ฐˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ Functor ๋ฅผ ๊ฑธ๊ธฐ ์ด์ „๊ณผ ๋™์ผํ•ด์•ผํ•œ๋‹ค.

Functor 2

๋˜ํ•œ 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

  1. โ€œ์ฐธ์กฐ ํˆฌ๋ช…์„ฑ.โ€ Wikipedia. Feb. 06, 2022, Wikipedia - ์ฐธ์กฐ ํˆฌ๋ช…์„ฑ.
  2. โ€œ๋ฉฑ๋“ฑ๋ฒ•์น™.โ€ Wikipedia. Mar. 07, 2022, Wikipedia - ๋ฉฑ๋“ฑ๋ฒ•์น™.
  3. โ€œํ•จ์ˆ˜์˜ ํ•ฉ์„ฑ.โ€ Wikipedia. Jan. 21, 2023, Wikipedia - ํ•จ์ˆ˜์˜ ํ•ฉ์„ฑ.
  4. โ€œ๋žŒ๋‹ค ๋Œ€์ˆ˜.โ€ Wikipedia. Jul. 23, 2022, Wikipedia - ๋žŒ๋‹ค ๋Œ€์ˆ˜.
  5. Moon. โ€œํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ - Pipe.โ€ Medium. Dec. 29, 2019, ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ.
  6. 12 Math. โ€œgโˆ˜f ๊ฐ€ โ€œ์ผ๋Œ€์ผ ๋Œ€์‘โ€ ์ด๋ฉด f ์™€ g ๋„ โ€œ์ผ๋Œ€์ผ ๋Œ€์‘โ€?.โ€, Youtube. Jan. 12, 2023, Bijection ์„ ๋งŒ์กฑํ•˜๋Š” ํ•ฉ์„ฑ ํ•จ์ˆ˜์˜ ๋ถ„ํ•ด.
  7. Dmitry Lupich. โ€œSwift Monads, Functors and Applicatives with examples.โ€ Medium. Feb. 09, 2020, Swift Monads.
  8. โ€œPipe Operator.โ€ Elixir School. Jun. 15, 2023, Elixir Pipe Operator.