본문으둜 κ±΄λ„ˆλ›°κΈ°

🐀 Chapter 8: ν•¨μˆ˜ μ‘°ν•©μ˜ 원리와 μ‘μš©

πŸ¦„ ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ΄λž€?​

  • ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ€ 순수 ν•¨μˆ˜μ™€ μ„ μ–Έν˜• ν”„λ‘œκ·Έλž˜λ°μ˜ ν† λŒ€ μœ„μ— ν•¨μˆ˜ μ‘°ν•©κ³Ό λͺ¨λ‚˜λ“œ μ‘°ν•©μœΌλ‘œ μ½”λ“œλ₯Ό μ„€κ³„ν•˜κ³  κ΅¬ν˜„ν•˜λŠ” 기법이닀.
  • ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ˜ λ‹€μŒ μ„Έ 가지 μˆ˜ν•™ 이둠에 κΈ°λ°˜μ„ 두고 μžˆλ‹€.
  1. λžŒλ‹€ μˆ˜ν•™(ramda calculus): μ‘°ν•© 논리와 μΉ΄ν…Œκ³ λ¦¬ 이둠의 ν† λŒ€κ°€ λ˜λŠ” 논리 μˆ˜ν•™
  2. μ‘°ν•© 논리(combinatory logic): ν•¨μˆ˜ μ‘°ν•©μ˜ 이둠적 λ°°κ²½
  3. μΉ΄ν…Œκ³ λ¦¬ 이둠(category theory): λͺ¨λ‚˜λ“œ μ‘°ν•©κ³Ό κ³ μ°¨ νƒ€μž…μ˜ 이둠적 λ°°κ²½
  • ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄λŠ” 정적 νƒ€μž…(static type), μžλ™ λ©”λͺ¨λ¦¬ 관리(automatic memory management), 계산법(evaluation), νƒ€μž… μΆ”λ‘ (type inference), 일등 ν•¨μˆ˜(first-class function)에 κΈ°λ°˜μ„ 두고, λŒ€μˆ˜ 데이터 νƒ€μž…(algebraic data type), νŒ¨ν„΄ 맀칭(pattern matching), λͺ¨λ‚˜λ“œ(monad), κ³ μ°¨ νƒ€μž…(high order type) λ“±μ˜ κ³ κΈ‰ κΈ°λŠ₯을 μ œκ³΅ν•œλ‹€. λ‹€λ§Œ, ν•¨μˆ˜ν˜• 언어라고 ν•΄μ„œ μ΄λŸ¬ν•œ κΈ°λŠ₯을 λͺ¨λ‘ μ œκ³΅ν•˜μ§€λŠ” μ•ŠλŠ”λ‹€.

πŸ¦„ μ œλ„€λ¦­ ν•¨μˆ˜β€‹

  • νƒ€μž… λ³€μˆ˜ T둜 ν‘œκΈ°ν•  λ•Œ 이λ₯Ό μ œλ„€λ¦­ νƒ€μž…μ΄λΌκ³  ν•œλ‹€.

πŸ“š νƒ€μž…μŠ€ν¬λ¦½νŠΈμ˜ μ œλ„€λ¦­ ν•¨μˆ˜ ꡬ문​

  • νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œ μ œλ„€λ¦­ νƒ€μž…μ€ ν•¨μˆ˜μ™€ μΈν„°νŽ˜μ΄μŠ€, 클래슀, νƒ€μž… 별칭에 μ μš©ν•  수 있으며, κΊ½μ‡  κ΄„ν˜Έ <>둜 νƒ€μž…μ„ 감싼 <T>, <T, Q>처럼 ν‘œν˜„ν•œλ‹€.
function g1<T>(a: T): void {};
function g2<T, Q>(a: T, b: Q): void {};
  • μ œλ„€λ¦­ νƒ€μž…μœΌλ‘œ ν•¨μˆ˜λ₯Ό μ •μ˜ν•˜λ©΄ μ–΄λ–€ νƒ€μž…μ—λ„ λŒ€μ‘ν•  수 μžˆλ‹€. g1 ν•¨μˆ˜λŠ” a λ§€κ°œλ³€μˆ˜κ°€ μ œλ„€λ¦­ νƒ€μž…μœΌλ‘œ μ§€μ •λ˜μ—ˆκ³ , g2 ν•¨μˆ˜λŠ” a와 b λ§€κ°œλ³€μˆ˜κ°€ 각각 λ‹€λ₯Έ μ œλ„€λ¦­ νƒ€μž…μœΌλ‘œ μ§€μ •λ˜μ—ˆλ‹€.
// ν™”μ‚΄ν‘œ ν•¨μˆ˜
const g3 = <T>(a: T): void => {};
const g4 = <T, Q>(a: T, b: Q): void => {};

// νƒ€μž… 별칭에 μ œλ„€λ¦­ νƒ€μž…μ„ 적용
type Type1Func<T> = (T) => void
type Type2Func<T, Q> = (T, Q) => void
type Type3Func<T, Q, R> = (T, Q) => R; // T와 Q νƒ€μž… 값을 μž…λ ₯λ°›μ•„ R νƒ€μž… 값을 λ°˜ν™˜

πŸ“š ν•¨μˆ˜μ˜ 역할​

  • μˆ˜ν•™μ—μ„œ ν•¨μˆ˜λŠ” κ°’ x에 μˆ˜μ‹μ„ μ μš©ν•΄ 또 λ‹€λ₯Έ κ°’ yλ₯Ό λ§Œλ“œλŠ” 역할을 ν•œλ‹€.
  • ν•¨μˆ˜λ₯Ό f라고 ν‘œκΈ°ν•˜λ©΄ κ°’ x, y, fκ°„μ˜ 관계λ₯Ό λ‹€μŒμ²˜λŸΌ ν‘œν˜„ν•  수 μžˆλ‹€.
x ~> f ~> y
  • ν•¨μˆ˜ fκ°€ T νƒ€μž…μ˜ xκ°’μœΌλ‘œ R νƒ€μž…μ˜ y값을 λ§Œλ“ λ‹€κ³  ν•˜λ©΄ λ‹€μŒμ²˜λŸΌ ν‘œν˜„ν•œλ‹€.
(x: T) ~-> f -> (y: R)
  • μˆ˜ν•™μ—μ„œλŠ” 이런 관계λ₯Ό μΌλŒ€μΌ 관계라고 ν•˜κ³ , 이런 λ™μž‘μ„ ν•˜λŠ” ν•¨μˆ˜ fλ₯Ό 맀핑 μ€„μ—¬μ„œ 맡이라고 ν‘œν˜„ν•œλ‹€.
  • νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ–Έμ–΄λ‘œ μΌλŒ€μΌ 맡 ν•¨μˆ˜λ₯Ό λ§Œλ“ λ‹€λ©΄ νƒ€μž… T인 값을 μ΄μš©ν•΄ νƒ€μž… R인 값을 λ§Œλ“€μ–΄ μ£Όμ–΄μ•Ό ν•˜λ―€λ‘œ, ν•¨μˆ˜μ˜ μ‹œκ·Έλ‹ˆμ²˜λ₯Ό λ‹€μŒμ²˜λŸΌ ν‘œν˜„ν•  수 μžˆλ‹€.
type MapFunc<T, R> = (T) => R

πŸ“š 아이덴티티 ν•¨μˆ˜β€‹

  • 맡 ν•¨μˆ˜μ—μ„œ κ°€μž₯ λ‹¨μˆœν•œ ν˜•νƒœλŠ” μž…λ ₯κ°’ xλ₯Ό 가곡 없이 κ·ΈλŒ€λ‘œ λ°˜ν™˜ν•œλ‹€. 즉, μž…λ ₯κ³Ό 좜λ ₯ νƒ€μž…μ΄ κ°™λ‹€.
  • ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ—μ„œ μ΄λŸ¬ν•œ 역할을 ν•˜λŠ” ν•¨μˆ˜ μ΄λ¦„μ—λŠ” 보톡 identity ν˜Ήμ€ IλΌλŠ” 단어가 ν¬ν•¨λœλ‹€. μ•žμ—μ„œ 예둜 λ“  MapFunc νƒ€μž…μ„ μ‚¬μš©ν•΄ 아이덴티티 ν•¨μˆ˜μ˜ μ‹œκ·Έλ‹ˆμ²˜λ₯Ό λ‹€μŒμ²˜λŸΌ ν‘œν˜„ν•  수 μžˆλ‹€.
type MapFunc<T, R> = (T) => R
type IdentityFunc<T> = MapFunc<T, T>
  • μ΄λ ‡κ²Œ μ •μ˜ν•œ μ œλ„€λ¦­ ν•¨μˆ˜ νƒ€μž… IdentityFunc<T>λŠ” λ‹€μŒκ³Ό 같은 λ‹€μ–‘ν•œ ν•¨μˆ˜λ₯Ό μ„ μ–Έν•  λ•Œ ν¬κ΄„μ μœΌλ‘œ μ‚¬μš©ν•  수 μžˆλ‹€.
const numberIdentity: IdentityFunc<number> = (x: number): number => x;
const stringIdentity: IdentityFunc<string> = (x: string): string => x;
const arrayIdentity: IdentityFunc<any[]> = (x: any[]): any[] => x;

πŸ¦„ κ³ μ°¨ ν•¨μˆ˜μ™€ 컀리​

πŸ“š κ³ μ°¨ ν•¨μˆ˜λž€?​

  • μ–΄λ–€ ν•¨μˆ˜κ°€ 또 λ‹€λ₯Έ ν•¨μˆ˜λ₯Ό λ°˜ν™˜ν•  λ•Œ κ·Έ ν•¨μˆ˜λ₯Ό κ³ μ°¨ ν•¨μˆ˜(high-order function)라고 ν•œλ‹€.
// ν•¨μˆ˜ μ‹œκ·Έλ‹ˆμ²˜
type FirstOrderFunc<T, R> = (T) => R;
type SecondOrderFunc<T, R> = (T) => FirstOrderFunc<T, R>;
type ThirdOrderFunc<T, R> = (T) => SecondOrderFunc<T, R>;
  • 이 μ‹œκ·Έλ‹ˆμ²˜λ₯Ό μ°Έμ‘°ν•΄ μ‹€μ œ ν•¨μˆ˜λ₯Ό λ§Œλ“ λ‹€.
  • μ•„λž˜λŠ” ν•¨μˆ˜ 호좜 μ—°μ‚°μžλ₯Ό μ„Έ 번 μ—°μ†ν•΄μ„œ μ‚¬μš©ν–ˆλ‹€. 이λ₯Ό 컀리라고 ν•œλ‹€.
const add3: ThirdOrderFunc<number, number> =
(x: number): SecondOrderFunc<number, number> =>
(y: number): FirstOrderFunc<number, number> =>
(x: number): number => x + y + z;

console.log(add3(1)(2)(3)); // 6

πŸ“š λΆ€λΆ„ 적용 ν•¨μˆ˜μ™€ 컀리​

  • λ§Œμ•½ μœ„ μ˜ˆμ—μ„œ add3(1), add3(1)(2)처럼 μžμ‹ μ˜ μ°¨μˆ˜λ³΄λ‹€ ν•¨μˆ˜ 호좜 μ—°μ‚¬μžλ₯Ό 덜 μ‚¬μš©ν•˜λ©΄ λΆ€λΆ„ 적용 ν•¨μˆ˜, 짧게 λ§ν•˜λ©΄ λΆ€λΆ„ ν•¨μˆ˜λΌκ³  ν•œλ‹€.
const add2: SecondOrderFunc<number, number> = add3(1);
const add1: FirstOrderFunc<number, number> = add2(2);
console.log(
add1(3), // 6
add2(2)(3), // 6
add3(1)(2)(3), // 6
);

πŸ“š ν΄λ‘œμ €β€‹

  • κ³ μ°¨ ν•¨μˆ˜μ˜ λͺΈν†΅μ—μ„œ μ„ μ–Έλ˜λŠ” λ³€μˆ˜λ“€μ€ ν΄λ‘œμ €λΌλŠ” 유효 λ²”μœ„λ₯Ό 가진닀. ν΄λ‘œμ €λŠ” μ§€μ†λ˜λŠ” 유효 λ²”μœ„λ₯Ό μ˜λ―Έν•œλ‹€.
function add(x: number): (number) => number { // λ°”κΉ₯μͺ½ 유효 λ²”μœ„ μ‹œμž‘
return function(y: number): number { // μ•ˆμͺ½ 유효 λ²”μœ„ μ‹œμž‘
return x + y; // ν΄λ‘œμ € (xλŠ” 자유 λ³€μˆ˜)
} // μ•ˆμͺ½ 유효 λ²”μœ„ 끝
} // λ°”κΉ₯μͺ½ 유효 λ²”μœ„ 끝
  • 이처럼 자유 λ³€μˆ˜κ°€ 있으면 κ·Έ λ³€μˆ˜μ˜ λ°”κΉ₯μͺ½ 유효 λ²”μœ„μ—μ„œ 자유 λ³€μˆ˜μ˜ 의미(μ„ μ–Έλ¬Έ)λ₯Ό μ°ΎλŠ”λ°, λ°”κΉ₯μͺ½ 유효 λ²”μœ„μ—μ„œ x의 의미(x: number)λ₯Ό μ•Œ 수 μžˆμœΌλ―€λ‘œ μ½”λ“œλ₯Ό μ •μƒμ μœΌλ‘œ μ»΄νŒŒμΌν•œλ‹€.
  • ν΄λ‘œμ €λ₯Ό μ§€μ†λ˜λŠ” 유효 λ²”μœ„λΌκ³  ν•˜λŠ” μ΄μœ λŠ” λ‹€μŒμ²˜λŸΌ addν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜λ”λΌλ„ λ³€μˆ˜ xκ°€ λ©”λͺ¨λ¦¬μ—μ„œ ν•΄μ œλ˜μ§€ μ•ŠλŠ”λ‹€.
const add1 = add(1); // λ³€μˆ˜ x λ©”λͺ¨λ¦¬ μœ μ§€
  • 자유 λ³€μˆ˜ xλŠ” λ‹€μŒ μ½”λ“œκ°€ μ‹€ν–‰λ˜μ–΄μ•Ό λ©”λͺ¨λ¦¬κ°€ ν•΄μ œλœλ‹€.
const result = add1(2); // result에 3을 μ €μž₯ ν›„ λ³€μˆ˜ x λ©”λͺ¨λ¦¬ ν•΄μ œ
  • 이처럼 κ³ μ°¨ ν•¨μˆ˜κ°€ λΆ€λΆ„ ν•¨μˆ˜κ°€ μ•„λ‹Œ 값을 λ°œμƒν•΄μ•Ό λΉ„λ‘œμ„œ 자유 λ³€μˆ˜μ˜ λ©”λͺ¨λ¦¬κ°€ ν•΄μ œλ˜λŠ” 유효 λ²”μœ„λ₯Ό ν΄λ‘œμ €λΌκ³  ν•œλ‹€.
  • ν΄λ‘œμ €λŠ” λ©”λͺ¨λΌκ°€ ν•΄μ œλ˜μ§€ μ•Šκ³  ν”„λ‘œκ·Έλž¨μ΄ 끝날 λ•ŒκΉŒμ§€ 지속될 μˆ˜λ„ μžˆλ‹€.
const makeNames = (): () => string => { // λ°”κΉ₯μͺ½ 유효 λ²”μœ„
const names = ['Jack', 'Jane', 'Smith'];
let index = 0;

return (): string => { // μ•ˆμͺ½ 유효 λ²”μœ„
if (index === names.length) {
index = 0;
}

return names[index++];
}
}

const makeName: () => string = makeNames();

console.log(
[1, 2, 3, 4, 5, 6].map(n => makeName())
);

// ["Jack", "Jane", "Smith", "Jack", "Jane", "Smith"]

πŸ¦„ ν•¨μˆ˜ 쑰합​

  • ν•¨μˆ˜ 쑰합은 μž‘μ€ κΈ°λŠ₯을 κ΅¬ν˜„ν•œ ν•¨μˆ˜λ₯Ό μ—¬λŸ¬ 번 μ‘°ν•©ν•΄ 더 의미 μžˆλŠ” ν•¨μˆ˜λ₯Ό λ§Œλ“€μ–΄ λ‚΄λŠ” ν”„λ‘œκ·Έλž¨ 섀계 기법이닀.
const f = <T>(x: T): string => `f(${x})`;
const g = <T>(x: T): string => `g(${x})`;
const h = <T>(x: T): string => `h(${x})`;

πŸ“š compose ν•¨μˆ˜β€‹

  • compose ν•¨μˆ˜λŠ” κ°€λ³€ 인수 μŠ€νƒ€μΌλ‘œ ν•¨μˆ˜λ“€μ˜ 배열을 μž…λ ₯λ°›λŠ”λ‹€. κ·Έλ‹€μŒ ν•¨μˆ˜λ“€μ„ μ‘°ν•©ν•΄ λ§€κ°œλ³€μˆ˜ xλ₯Ό μž…λ ₯λ°›λŠ” 1μ°¨ ν•¨μˆ˜λ₯Ό λ°˜ν™˜ν•œλ‹€.
const compose = <T, R>(...functions: readonly Function[]): Function => (x: T): (T) => R => {
const deepCopiedFunctions = [...functions];

return deepCopiedFunctions.reverse().reduce((value, func) => func(value), x);
}

const composedFGH = compose(h, g, f);
console.log(composedFGH('x')); // h(g(f(x)))
  • λ‹€μŒ μ½”λ“œλŠ” inc ν•¨μˆ˜λ₯Ό compose둜 μ„Έ 번 쑰합함 composedλž€ ν•¨μˆ˜λ₯Ό λ§Œλ“ λ‹€.
const inc = x => x + 1;

const composed = compose(inc, inc, inc);
console.log(composed(1)); // 4

πŸ“š pipe ν•¨μˆ˜β€‹

  • pipe ν•¨μˆ˜λŠ” compose와 λ§€κ°œλ³€μˆ˜λ“€μ„ ν•΄μ„ν•˜λŠ” μˆœμ„œκ°€ λ°˜λŒ€μ΄λ―€λ‘œ, λ‹€μŒ μ½”λ“œλŠ” reverseν•˜λŠ” μ½”λ“œκ°€ μ—†λ‹€.
const pipe = <T, R>(...functions: readonly Function[]): Function => (x: T): (T) => R => {
return functions.reduce((value, func) => func(value), x);
}

const piped = pipe(f, g, h);
console.log(piped('x')); // h(g(f))

πŸ“š pipe와 compose ν•¨μˆ˜ 뢄석​

  • 예λ₯Ό λ“€μ–΄, ν•¨μˆ˜ f, g, h의 ν•¨μˆ˜ μ‹œκ·Έλ‹ˆμ²˜λŠ” λ‹€μŒμ²˜λŸΌ λͺ¨λ‘ λ‹€λ₯΄λ‹€.
f: (number) => string
g: (string) => string[]
h: (string[]) => number
  • 이 경우 μ œλ„€λ¦­ νƒ€μž…μ„ μ μš©ν•˜κΈ°κ°€ νž˜λ“€λ‹€. λ”°λΌμ„œ functionsλŠ” μžλ°”μŠ€ν¬λ¦½νŠΈ νƒ€μž… Functionλ“€μ˜ 배열인 Function[]으둜 μ„€μ •ν•œλ‹€.
const pipe = (...functions: Function[])
  • pipe ν•¨μˆ˜λŠ” functions 배열을 μ‘°ν•©ν•΄ μ–΄λ–€ ν•¨μˆ˜λ₯Ό λ°˜ν™˜ν•΄μ•Ό ν•˜λ―€λ‘œ λ°˜ν™š νƒ€μž…μ€ Function으둜 μ„€μ •ν•œλ‹€.
const pipe = (...functions: Function[]): Function
  • 그런데 pipe둜 μ‘°ν•©λœ κ²°κ³Ό ν•¨μˆ˜λŠ” 애리티가 1이닀. λ”°λΌμ„œ λ‹€μŒμ²˜λŸΌ λ§€κ°œλ³€μˆ˜ xλ₯Ό μž…λ ₯받은 ν•¨μˆ˜λ₯Ό μž‘μ„±ν•œλ‹€. 그런데 이 λ‚΄μš©μ„ μ œλ„€λ¦­ νƒ€μž…μœΌλ‘œ ν‘œν˜„ν•˜λ©΄ νƒ€μž… T의 κ°’ xλ₯Ό μž…λ ₯λ°›μ•„ (T) => R νƒ€μž…μ˜ ν•¨μˆ˜λ₯Ό λ°˜ν™˜ν•˜λŠ” 것이 λœλ‹€.
const pipe = <T, R>(...functions: readonly Function[]): Function => (x: T): (T) => R => {}

πŸ“š λΆ€λΆ„ ν•¨μˆ˜μ™€ ν•¨μˆ˜ 쑰합​

  • κ³ μ°¨ ν•¨μˆ˜μ˜ λΆ€λΆ„ ν•¨μˆ˜λŠ” ν•¨μˆ˜ 쑰합에 μ‚¬μš©λ  수 μžˆλ‹€.
const add = x => y => x + y;
const inc = add(1);

const add3 = pipe(
inc,
add(2),
);

console.log(add3(1)) // 4

πŸ“š ν¬μΈνŠΈκ°€ μ—†λŠ” ν•¨μˆ˜β€‹

  • λ‹€μŒ map ν•¨μˆ˜λŠ” ν•¨μˆ˜ 쑰합을 κ³ λ €ν•΄ μ„€κ³„ν•œ κ²ƒμœΌλ‘œ, map(f) ν˜•νƒœμ˜ λΆ€λΆ„ ν•¨μˆ˜λ₯Ό λ§Œλ“€λ©΄ composeλ‚˜ pipe에 μ‚¬μš©ν•  수 μžˆλ‹€. 이처럼 ν•¨μˆ˜ 쑰합을 κ³ λ €ν•΄ μ„€κ³„ν•œ ν•¨μˆ˜λ₯Ό ν¬μΈνŠΈκ°€ μ—†λŠ” ν•¨μˆ˜λΌκ³  ν•œλ‹€.
const map = f => a => a.map(f);

const square = value => value * value;
const squareMap = map(square);

const fourSquare = pipe(
squareMap,
squareMap,
);

console.log(fourSquare([3, 4])); // [81, 256]
  • μ΄λ²ˆμ—” reduceλ₯Ό μ‚¬μš©ν•˜λŠ” ν¬μΈνŠΈκ°€ μ—†λŠ” ν•¨μˆ˜λ₯Ό λ§Œλ“ λ‹€. λ‹€μŒ reduce ν•¨μˆ˜λŠ” λ°°μ—΄μ˜ reduce ν•¨μˆ˜λ₯Ό 2μ°¨ κ³ μ°¨ ν•¨μˆ˜ ν˜•νƒœλ‘œ μž¬κ΅¬μ„±ν•œ μ˜ˆμ΄λ‹€.
const reduce = (f, initValue) => a => a.reduce(f, initValue);

const sum = (result, value) => result + value;

const sumArray = reduce(sum, 0);

const pitagoras = pipe(
squareMap,
sumArray,
Math.sqrt
);

console.log(pitagoras([3, 4])); // [9, 16] => 25 => 5