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

🐀 Chapter 11: λͺ¨λ‚˜λ“œ

πŸ¦„ λͺ¨λ‚˜λ“œ μ΄ν•΄ν•˜κΈ°β€‹

  • ν”„λ‘œκ·Έλž˜λ°μ—μ„œ λͺ¨λ‚˜λ“œλŠ” μΌμ’…μ˜ μ½”λ“œ 섀계 νŒ¨ν„΄μœΌλ‘œμ„œ λͺ‡ 개의 μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•œ ν΄λž˜μŠ€λ‹€.
  • λͺ¨λ‚˜λ“œ ν΄λž˜μŠ€λŠ” λͺ‡ 가지 곡톡적인 νŠΉμ§•μ΄ μžˆλ‹€.

πŸ“š νƒ€μž… ν΄λž˜μŠ€λž€?​

  • λͺ¨λ‚˜λ“œλ₯Ό μ΄ν•΄ν•˜λŠ” μ²«κ±ΈμŒμ€ νƒ€μž… ν΄λž˜μŠ€κ°€ μ™œ ν•„μš”ν•œμ§€ μ•„λŠ” 것이닀.
  • μž‘μ„±μžμ˜ μ˜λ„λ₯Ό μ΄ν•΄ν•˜μ§€ λͺ»ν•œ μ½”λ“œλŠ” ν”„λ‘œκ·Έλž¨μ΄ λΉ„μ •μƒμ μœΌλ‘œ μ’…λ£Œλ˜κΈ° λ•Œλ¬Έμ— 이λ₯Ό λ°©μ§€ν•˜λ €λ©΄ λ§€κ°œλ³€μˆ˜ bλŠ” λ°˜λ“œμ‹œ map λ©”μ„œλ“œκ°€ μžˆλŠ” νƒ€μž…μ΄λΌκ³  νƒ€μž…μ„ μ œν•œν•΄μ•Ό ν•œλ‹€.
const callMap = fn => b => b.map(fn);

// νƒ€μž…μ„ μ œν•œ
const callMap = <T, U>(fn: (T) => U) => <T extends { map(fn) }>(b: T) => b.map(fn);
  • λͺ¨λ‚˜λ“œ 방식 μ„€κ³„λŠ” λ°˜λ“œμ‹œ mapκ³Ό ofλΌλŠ” μ΄λ¦„μ˜ λ©”μ„œλ“œκ°€ μžˆλŠ” Monad<T> 클래슀λ₯Ό λ§Œλ“ λ‹€.
class Monad<T> {
constructor(public value: T){}
static of<U>(value: U): Monad<U> { return new Monad<U>(value) }
map<U>(fn: (x: T) => U): Monad<U> { return new Monad<U>(fn(this.value)) }
}
  • 이처럼 Monad<T>와 같은 클래슀λ₯Ό νƒ€μž… 클래슀라고 ν•œλ‹€. νƒ€μž… ν΄λž˜μŠ€λŠ” λ‹€μŒμ²˜λŸΌ ν•¨μˆ˜λ₯Ό λ§Œλ“€ λ•Œ νŠΉλ³„ν•œ νƒ€μž…μœΌλ‘œ μ œμ•½ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€.
const callMonad = (fn) => (b) => Monad.of(b).map(fn).value;
  • Monad<T>와 같은 νƒ€μž… 클래슀 덕뢄에 callMonad처럼 νƒ€μž…μ— λ”°λ₯Έ μ•ˆμ •μ„±μ„ 보μž₯ν•˜λ©΄μ„œλ„ μ½”λ“œμ˜ μž¬μ‚¬μš©μ„±μ΄ λ›°μ–΄λ‚œ λ²”μš© ν•¨μˆ˜λ₯Ό μ‰½κ²Œ λ§Œλ“€ 수 μžˆλ‹€.
callMonad((a: number) => a + 1)(1); // 2
callMonad((a: number[]) => a.map(value => value + 1))([1, 2, 3, 4]); // [2, 3, 4, 5]

πŸ“š κ³ μ°¨ νƒ€μž…μ΄λž€?​

  • μ•žμ„œ λ³Έ Monad<T>λŠ” νƒ€μž… Tλ₯Ό Monad<T> νƒ€μž…μœΌλ‘œ λ³€ν™˜ν–ˆλ‹€κ°€ λ•Œκ°€ 되면 λ‹€μ‹œ νƒ€μž… T둜 λ³€ν™˜ν•΄μ€€λ‹€.
  • Monad<T>처럼 νƒ€μž… Tλ₯Ό ν•œ 단계 더 λ†’μ΄λŠ” νƒ€μž…μœΌλ‘œ λ³€ν™˜ν•˜λŠ” μš©λ„μ˜ νƒ€μž…μ„ κ³ μ°¨ νƒ€μž…μ΄λΌκ³  ν•œλ‹€.

νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” 3μ°¨ 이상 κ³ μ°¨ νƒ€μž…μ„ λ§Œλ“€ μˆ˜λŠ” μ—†λ‹€

πŸ“š μΉ΄ν…Œκ³ λ¦¬ 이둠​

  • https://ko.wikipedia.org/wiki/%EB%B2%94%EC%A3%BC%EB%A1%A0
  • μˆ˜ν•™μ—μ„œ 집합은 ν”„λ‘œκ·Έλž˜λ°μ—μ„œ νƒ€μž…μ΄λ‹€. μˆ˜ν•™μ—μ„œ μΉ΄ν…Œκ³ λ¦¬λŠ” μ§‘ν•©μ˜ μ§‘ν•©μœΌλ‘œ 이해할 수 μžˆλ‹€.
  • ν”„λ‘œκ·Έλž˜λ°μ—μ„œ μΉ΄ν…Œκ³ λ¦¬λŠ” νƒ€μž…μ˜ νƒ€μž…, 즉 κ³ μ°¨ νƒ€μž…μœΌλ‘œ 이해할 수 μžˆλ‹€. 그리고 λͺ¨λ‚˜λ“œλŠ” λ³„λ„μ˜ νŠΉμ§•μ΄ μžˆλŠ” κ³ μ°¨ νƒ€μž…μ΄λ‹€.

πŸ“š νŒνƒ€μ§€λžœλ“œ κ·œκ²©β€‹

  • λͺ¨λ‚˜λ“œλŠ” λͺ¨λ‚˜λ“œ 룰이라고 ν•˜λŠ” μ½”λ“œ 섀계 원칙에 맞좰 κ΅¬ν˜„λœ 클래슀λ₯Ό μ˜λ―Έν•œλ‹€.
  • νŒνƒ€μ§€λžœλ“œ κ·œκ²©μ΄λž€, ν•˜μŠ€μΌˆ ν‘œμ€€ 라이브러리 ꡬ쑰λ₯Ό μžλ°”μŠ€ν¬λ¦½νŠΈ λ°©μ‹μœΌλ‘œ μž¬κ΅¬μ„±ν•œ 것이닀.
  • 이미지 μ°Έκ³ 
  • μ–΄λ–€ ν΄λž˜μŠ€μ— λ‹€μŒ λ„€ 가지 쑰건을 λͺ¨λ‘ λ§Œμ‘±ν•œλ‹€λ©΄ κ·Έ ν΄λž˜μŠ€λŠ” λͺ¨λ‚˜λ“œμ΄λ‹€.
  • νŽ‘ν„°(Functor): mapμ΄λΌλŠ” μΈμŠ€ν„΄μŠ€ λ©”μ„œλ“œλ₯Ό κ°€μ§€λŠ” 클래슀
  • μ–΄ν”ŒλΌμ΄(Apply): νŽ‘ν„°μ΄λ©΄μ„œ apλΌλŠ” μΈμŠ€ν„΄μŠ€ λ©”μ„œλ“œλ₯Ό κ°€μ§€λŠ” 클래슀
  • μ• ν”Œλ¦¬μ»€ν‹°λΈŒ(Applicative): μ–΄ν”ŒλΌμ΄μ΄λ©΄μ„œ ofλΌλŠ” 클래슀 λ©”μ„œλ“œλ₯Ό κ°€μ§€λŠ” 클래슀
  • 체인(Chain): μ• ν”Œλ¦¬μ»€ν‹°λΈŒμ΄λ©΄μ„œ chainμ΄λΌλŠ” λ©”μ„œλ“œλ₯Ό κ°€μ§€λŠ” 클래슀

πŸ“š λͺ¨λ‚˜λ“œ 룰​

  • μ–΄λ–€ 클래슀의 이름이 M이고 이 클래슀의 μΈμŠ€ν„΄μŠ€λ₯Ό m이라고 ν•  λ•Œ λͺ¨λ‚˜λ“œλŠ” μ• ν”Œλ¦¬μ»€ν‹°λΈŒμ™€ 체인의 κΈ°λŠ₯을 가지고 있고, λ‹€μŒκ³Ό 같은 두 가지 법칙을 λ§Œμ‘±ν•˜κ²Œ κ΅¬ν˜„ν•œ ν΄λž˜μŠ€μ΄λ‹€.
  • λͺ¨λ‚˜λ“œ 룰의 μ™Όμͺ½ 법칙과 였λ₯Έμͺ½ 법칙
κ΅¬λΆ„μ˜λ―Έ
μ™Όμͺ½ 법칙M.of(a).chain(f) == f(a)
였λ₯Έμͺ½ 법칙m.chain(M.of) == m

πŸ¦„ Identity λͺ¨λ‚˜λ“œ 이해와 κ΅¬ν˜„β€‹

πŸ“š κ°’ μ»¨ν…Œμ΄λ„ˆ κ΅¬ν˜„μš© IValuable<T> μΈν„°νŽ˜μ΄μŠ€ κ΅¬ν˜„β€‹

  • 이 μ»¨ν…Œμ΄λ„ˆ ν΄λž˜μŠ€λŠ” number와 같은 ꡬ체적인 νƒ€μž…μ˜ 값을 κ°€μ§€λŠ” 것이 μ•„λ‹ˆλΌ, λͺ¨λ“  νƒ€μž… T의 값을 κ°€μ§ˆ 수 μžˆλŠ” μ œλ„€λ¦­ μ»¨ν…Œμ΄λ„ˆ 클래슀λ₯Ό 생각할 수 μžˆλ‹€.
  • 이처럼 νƒ€μž… Tλ₯Ό κ°€μ§€λŠ” κ°’μ˜ μ»¨ν…Œμ΄λ„ˆλ₯Ό κ°’ μ»¨ν…Œμ΄λ„ˆλΌκ³  ν•œλ‹€.

πŸ“š 클래슀 이름이 μ™œ Identity인가?​

  • ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ—μ„œ identityλŠ” 항상 λ‹€μŒμ²˜λŸΌ κ΅¬ν˜„ν•˜λŠ” νŠΉλ³„ν•œ 의미의 ν•¨μˆ˜λ‹€.
const identity = <T>(value: T): T => value;
  • IdentityλŠ” map, ap, of, chainκ³Ό 같은 κΈ°λ³Έ λ©”μ„œλ“œλ§Œ κ΅¬ν˜„ν•œ λͺ¨λ‚˜λ“œμ΄λ‹€. μΉ΄ν…Œκ³ λ¦¬ μ΄λ‘ μ—μ„œ μžμ‹ μ˜ νƒ€μž…μ—μ„œ λ‹€λ₯Έ νƒ€μž…μœΌλ‘œ κ°”λ‹€κ°€ λŒμ•„μ˜¬ λ•Œ 값이 λ³€κ²½λ˜μ§€ μ•ŠλŠ” μΉ΄ν…Œκ³ λ¦¬λ₯Ό Identity라고 λΆ€λ₯Έλ‹€.

πŸ“š κ°’ μ»¨ν…Œμ΄λ„ˆλ‘œμ„œμ˜ Identity<T> κ΅¬ν˜„ν•˜κΈ°β€‹

import { IValuable } from '../interfaces/IValuable';

export class Identity<T> implements IValuable<T> {
constructor(private _value: T) {}
value() { return this._value };
};

πŸ“š ISetoid<T> μΈν„°νŽ˜μ΄μŠ€μ™€ κ΅¬ν˜„β€‹

  • νŒνƒ€μ§€λžœλ“œ κ·œκ²©μ—μ„œ setoidλŠ” equalsλΌλŠ” μ΄λ¦„μ˜ λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•˜λŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ˜λ―Έν•˜λ©°, νƒ€μž…μŠ€ν¬λ¦½νŠΈλ‘œλŠ” λ‹€μŒμ²˜λŸΌ κ΅¬ν˜„ν•  수 μžˆλ‹€.
import { IValuable } from './IValuable';

export interface ISetoid<T> extends IValuable<T> {
equals<U>(value: U): boolean;
};
  • 이제 Identity<T>에 ISetoid<T>λ₯Ό κ΅¬ν˜„ν•œλ‹€.
import { ISetoid } from '../interfaces/ISetoid';

export class Identity<T> implements ISetoid<T> {
constructor(private _value: T) {}
value() { return this._value };
equals<U>(that: U): boolean {
if(that instanceof Identity) {
return this.value() == that.value();
}

return false;
}
};
  • ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±
import { Identity } from '../classes/Identity';

const one = new Identity(1);
const anotherOne = new Identity(1);
const two = new Identity(2);

console.log(
one.equals(anotherOne), // true
one.equals(two), // false
one.equals(1), // false
one.equals(null), // false
one.equals([1]), // false
);
  • μ½”λ“œλŠ” Identity<number> νƒ€μž… λ³€μˆ˜κ°€ one이 λ˜‘κ°™μ€ Identity<number> νƒ€μž… λ³€μˆ˜ anotherOneκ³Ό 비ꡐ할 λ•Œλ§Œ true둜 λ°˜ν™˜ν•˜κ³  μžˆλ‹€.

πŸ“š IFunctor<T> μΈν„°νŽ˜μ΄μŠ€μ™€ κ΅¬ν˜„β€‹

  • νŒνƒ€μ§€λžœλ“œ κ·œκ²©μ—μ„œ νŽ‘ν„°λŠ” mapμ΄λΌλŠ” λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•˜λŠ” μΈν„°νŽ˜μ΄μŠ€μ΄λ‹€.
  • λ‹€μŒ μ½”λ“œλŠ” νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ–Έμ–΄μ˜ νŠΉμ„±μ„ κ³ λ €ν•΄ κ΅¬ν˜„ν•œ κ²ƒμœΌλ‘œ, μΉ΄ν…Œκ³ λ¦¬ μ΄λ‘ μ—μ„œ νŽ‘ν„°λŠ” μ—”λ„νŽ‘ν„°(endofunctor)λΌλŠ” νŠΉλ³„ν•œ μ„±μ§ˆμ„ λ§Œμ‘±μ‹œμΌœμ•Ό ν•œλ‹€.
export interface IFunctor<T> {
map<U>(fn: (x: T) => U);
}

πŸ“š μ—”λ„νŽ‘ν„°λž€?​

  • μ—”λ„νŽ‘ν„°λŠ” νŠΉμ • μΉ΄ν…Œκ³ λ¦¬μ—μ„œ μΆœλ°œν•΄λ„ 도착 μΉ΄ν…Œκ³ λ¦¬λŠ” λ‹€μ‹œ 좜발 μΉ΄ν…Œκ³ λ¦¬κ°€ 되게 ν•˜λŠ” νŽ‘ν„°λ₯Ό μ˜λ―Έν•œλ‹€.
  • λ‹€μŒ Identity<T>의 map λ©”μ„œλ“œμ˜ κ΅¬ν˜„ λ‚΄μš©μ€ μ•€λ„νŽ‘ν„°λ‘œ λ™μž‘ν•˜κ²Œ ν•˜λŠ” μ½”λ“œμ΄λ‹€.
import { ISetoid } from '../interfaces/ISetoid';
import { IFunctor } from '../interfaces/IFunctor';

export class Identity<T> implements ISetoid<T>, IFunctor<T> {
constructor(private _value: T) {}
// IValuable
value() { return this._value };
// ISetiod
equals<U>(that: U): boolean {
if(that instanceof Identity) {
return this.value() == that.value();
}

return false;
}
// IFunctor
map<U>(fn: (x: T) => U) {
return new Identity<U>(fn(this.value()));
}
};

πŸ“š IApply<T> μΈν„°νŽ˜μ΄μŠ€μ™€ κ΅¬ν˜„β€‹

  • νŒνƒ€μ§€λžœλ“œ κ·œκ²©μ—μ„œ μ–΄ν”ŒλΌμ΄(apply)λŠ” μžμ‹ μ€ νŽ‘ν„°μ΄λ©΄μ„œ λ™μ‹œμ— apλΌλŠ” λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•˜λŠ” μΈν„°νŽ˜μ΄μŠ€μ΄λ‹€.
import { IFunctor } from "./IFunctor";

export interface IApply<T> extends IFunctor<T> {
ap<U>(b: U);
}
  • 그런데 IApplyλ₯Ό κ΅¬ν˜„ν•˜λŠ” μ»¨ν…Œμ΄λ„ˆλŠ” κ°’ μ»¨νƒ€μ΄λ„ˆλ‘œμ„œλΏλ§Œ μ•„λ‹ˆλΌ κ³ μ°¨ ν•¨μˆ˜ μ»¨ν…Œμ΄λ„ˆλ‘œμ„œλ„ λ™μž‘ν•œλ‹€.
import { ISetoid } from '../interfaces/ISetoid';
import { IApply } from '../interfaces/IApply';

export class Identity<T> implements ISetoid<T>, IApply<T> {
// μƒλž΅..
// IApply
ap<U>(b: U) {
const f = this.value();
if (f instanceof Function) {
return Identity.of<U>((f as Function)(b));
}
}
};

πŸ“š IApplicative<T> μΈν„°νŽ˜μ΄μŠ€μ™€ κ΅¬ν˜„β€‹

  • νŒνƒ€μ§€λžœλ“œ κ·œκ²©μ—μ„œ μ• ν”Œλ¦¬μ»€ν‹°λΈŒλŠ” κ·Έ μžμ‹ μ΄ μ–΄ν”ŒλΌμ΄μ΄λ©΄μ„œ ofλΌλŠ” 클래슀 λ©”μ„œλ“œλ₯Ό μΆ”κ°€λ‘œ μ œκ³΅ν•˜λŠ” μΈν„°νŽ˜μ΄μŠ€μ΄λ‹€.
  • 그런데 ν˜„μž¬ νƒ€μž…μŠ€ν¬λ¦½νŠΈμ—μ„œλŠ” μΈν„°νŽ˜μ΄μŠ€μ— 정적 λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•˜μ§€ λͺ»ν•œλ‹€.
import { IApply } from "./IApply";

export interface IApplicative<T> extends IApply<T> {
// static of(value: T);
}
  • λ‹€μŒ μ½”λ“œλŠ” Identity ν΄λž˜μŠ€μ— of 클래슀 λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•œ μ˜ˆμ΄λ‹€.
import { ISetoid } from '../interfaces/ISetoid';
import { IApplicative } from '../interfaces/IApplicative';

export class Identity<T> implements ISetoid<T>, IApplicative<T> {
// μƒλž΅..
// IApplicative
static of<T>(value: T): Identity<T> {
return new Identity<T>(value);
}
};

πŸ“š IChain<T> μΈν„°νŽ˜μ΄μŠ€μ™€ κ΅¬ν˜„β€‹

  • νŒνƒ€μ§€λžœλ“œ κ·œκ²©μ—μ„œ 체인은 κ·Έ μžμ‹ μ΄ μ–΄ν”ŒλΌμ΄μ΄λ©΄μ„œ chainμ΄λΌλŠ” λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•˜λŠ” μΈν„°νŽ˜μ΄μŠ€μ΄λ‹€.
import { IApply } from './IApply';

export interface IChain<T> extends IApply<T> {
chain<U>(fn: (T) => U);
}
  • chain λ©”μ„œλ“œλŠ” νŽ‘ν„°μ˜ mapκ³Ό 달리 μ—”λ„νŽ‘ν„°λ‘œ κ΅¬ν˜„ν•΄μ•Ό ν•  μ˜λ¬΄κ°€ μ—†λ‹€.
import { ISetoid } from '../interfaces/ISetoid';
import { IApplicative } from '../interfaces/IApplicative';
import { IChain } from '../interfaces/IChain';

export class Identity<T> implements ISetoid<T>, IChain<T>, IApplicative<T> {
// μƒλž΅..
// IChain
chain<U>(fn: (T) => U): U {
return fn(this.value());
}
};
  • μ—”λ„νŽ‘λ„μΈ map은 항상 같은 μΉ΄ν…Œκ³ λ¦¬μ— 머무λ₯Έλ‹€. λ°˜λ©΄μ— chain은 μžμ‹ μ΄ 머무λ₯΄κ³  싢은 μΉ΄ν…Œκ³ λ¦¬λ₯Ό 슀슀둜 μ •ν•΄μ•Ό ν•œλ‹€.
import { Identity } from '../classes/Identity';

console.log(
Identity.of(1).map((value) => `the count is ${value}`).value(),
Identity.of(1).chain((value) => Identity.of(`the count is ${value}`)).value(),
);
// the count is 1 the count is 1

πŸ“š IMonad<T> μΈν„°νŽ˜μ΄μŠ€μ™€ κ΅¬ν˜„β€‹

  • νŒνƒ€μ§€λžœλ“œ κ·œκ²©μ—μ„œ λͺ¨λ‚˜λ“œλŠ” λ‹€μŒμ²˜λŸΌ 체인과 μ• ν”Œλ¦¬μ»€ν‹°λΈŒλ₯Ό κ΅¬ν˜„ν•œ 것이닀.
import { IChain } from './IChain';
import { IApplicative } from './IApplicative';

export interface IMonad<T> extends IChain<T>, IApplicative<T> {};
  • λ‹€μŒμ€ Identity<T> λͺ¨λ‚˜λ“œκ°€ μ™„μ„±λœ 것이닀.
import { ISetoid } from '../interfaces/ISetoid';
import { IMonad } from '../interfaces/IMonad';

export class Identity<T> implements ISetoid<T>, IMonad<T> {
constructor(private _value: T) {}
// IValuable
value() { return this._value };
// ISetiod
equals<U>(that: U): boolean {
if(that instanceof Identity) {
return this.value() == that.value();
}

return false;
}
// IFunctor
map<U>(fn: (x: T) => U) {
return new Identity<U>(fn(this.value()));
}
// IApply
ap<U>(b: U) {
const f = this.value();
if (f instanceof Function) {
return Identity.of<U>((f as Function)(b));
}
}
// IApplicative
static of<T>(value: T): Identity<T> {
return new Identity<T>(value);
}
// IChain
chain<U>(fn: (T) => U): U {
return fn(this.value());
}
};
  • λ‹€μŒ μ½”λ“œλŠ” μ™„μ„±λœ Identity<T> λͺ¨λ‚˜λ“œκ°€ M.of(a).chain(f) == f(a) μ™Όμͺ½ 법칙을 λ§Œμ‘±ν•˜λŠ”μ§€ ν…ŒμŠ€νŠΈν•˜λŠ” λ‚΄μš©μ΄λ‹€.
import { Identity } from '../classes/Identity';

const a = 1;
const f = a => a * 2;

console.log(
Identity.of(a).chain(f) == f(a), // true
);
  • λ‹€μŒμ€ Identity<T>κ°€ m.chain(M.of) == m λͺ¨λ‚˜λ“œ 였λ₯Έμͺ½ 법칙을 μΆ©μ‘±ν•˜λŠ”μ§€λ₯Ό ν…ŒμŠ€νŠΈν•˜λŠ” λ‚΄μš©μ΄λ‹€.
import { Identity } from '../classes/Identity';

const m = Identity.of(1);

console.log(
m.chain(Identity.of).equals(m),
);
  • λ‹€μŒ μ½”λ“œλŠ” 마치 λ°°μ—΄μ˜ map, filter λ©”μ„œλ“œλ₯Ό λ©”μ„œλ“œ 체인으둜 μ½”λ”©ν•˜λ“―, Identity νƒ€μž… 객체 jack의 λ©”μ„œλ“œλ“€μ„ 체인 ν˜•νƒœλ‘œ ν˜ΈμΆœν•œλ‹€.
  • λͺ¨λ‚˜λ“œλŠ” 이처럼 μ„ μ–Έν˜• ν”„λ‘œκ·Έλž˜λ°μ„ 염두에 두고 μ„€κ³„λœ 것이닀.
import { Identity } from '../classes/Identity';

type IPerson = {
name: string,
age: number,
};

const jack = Identity.of(['Jack', 32]);

console.log(
jack
.map(([name, age]) => ({ name, age }))
.chain((p: IPerson) => Identity.of(p))
.map(({ name, age }) => [name, age])
.value()[0] === jack.value()[0] // true
);

πŸ¦„ Maybe λͺ¨λ‚˜λ“œ 이해와 κ΅¬ν˜„β€‹

πŸ“š Maybe λͺ¨λ‚˜λ“œλž€?​

  • MaybeλŠ” 였λ₯˜μΌ λ•Œμ™€ 정상적일 λ•Œλ₯Ό λͺ¨λ‘ κ³ λ €ν•˜λ©΄μ„œλ„ μ‚¬μš©ν•˜λŠ” μͺ½ μ½”λ“œλ₯Ό κ°„κ²°ν•˜κ²Œ μž‘μ„±ν•  수 있게 ν•΄μ€€λ‹€.
  • Maybe λͺ¨λ‚˜λ“œλŠ” 10μž₯의 Option의 Some, Noneκ³Ό λΉ„μŠ·ν•œ 의미λ₯Ό 가진 Just와 Nothingμ΄λΌλŠ” 두 가지 νƒ€μž…μ„ μ œκ³΅ν•œλ‹€.
  • MaybeλŠ” κ·Έ μžμ²΄κ°€ λͺ¨λ‚˜λ“œκ°€ μ•„λ‹ˆλΌ, Maybeκ°€ μ œκ³΅ν•˜λŠ” Just<T>와 Nothingνƒ€μž…μ΄ λͺ¨λ‚˜λ“œμ΄λ‹€.
export class Maybe<T> {
static Just<U>(value: U) {
return new Just<U>(value);
}
static Nothing = new Nothing;
}
  • Maybe의 이런 섀계 λͺ©μ μ€ μ½”λ“œμ˜ μ•ˆμ •μ„±μ„ ν•¨μˆ˜ν˜• λ°©μ‹μœΌλ‘œ 보μž₯ν•˜κΈ° μœ„ν•΄μ„œμ΄λ‹€.
  • μ½”λ“œμ— μ μš©λ˜λŠ” 값에 따라 μ–΄λ–€ λ•ŒλŠ” 정상적이고 μ–΄λ–€ λ•ŒλŠ” undefined, null, Infinity λ“±μ˜ 값을 μœ λ°œν•  λ•Œ Maybeλ₯Ό μ‚¬μš©ν•˜λ©΄ 맀우 효율적인 λ°©μ‹μœΌλ‘œ μ½”λ“œλ₯Ό μž‘μ„±ν•  수 μžˆλ‹€.

πŸ“š Maybeκ°€ ν•¨μˆ˜μ˜ λ°˜ν™˜ νƒ€μž…μΌ λ•Œμ˜ λ¬Έμ œμ β€‹

  • ν˜„μž¬ νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” Just<number> | Nothingκ³Ό 같은 두 클래슀의 합집합 νƒ€μž…μ„ λ§Œλ‚˜λ©΄ 였λ₯˜κ°€ λ°œμƒν•œλ‹€.
  • νƒ€μž…μŠ€ν¬λ¦½νŠΈμ˜ μ΄λŸ¬ν•œ νŠΉμ„± λ•Œλ¬Έμ— Maybe ν΄λž˜μŠ€λŠ” λ‹€μŒ _IMaybe μΈν„°νŽ˜μ΄μŠ€μ™€ IMonad μΈν„°νŽ˜μ΄μŠ€λ₯Ό ν•©ν•΄ 놓은 IMaybe νƒ€μž…μ„ μ œκ³΅ν•œλ‹€.
export interface _IMaybe<T> {
isJust(): boolean;
isNothing(): boolean;
getOrElse(defaultValue: T): T;
};

πŸ“š Just λͺ¨λ‚˜λ“œ κ΅¬ν˜„β€‹

  • Identityλͺ¨λ‚˜λ“œμ™€ 달리 ISetoidμΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜μ§€ μ•ŠλŠ”λ°, μ΄λŠ” Justκ°€ Nothing일 λ•Œλ₯Ό κ³ λ €ν•΄ value()κ°€ μ•„λ‹Œ getOrElse(0)κ³Ό 같은 ν˜•νƒœλ‘œ λ™μž‘ν•˜λŠ” 것을 염두해 λ‘” 것이닀.
import { _IMaybe } from './_IMaybe';
import { IMonad } from '../interfaces/IMonad';

export class Just<T> implements _IMaybe<T>, IMonad<T> {
constructor(private _value: T) {}
value(): T { return this._value; }

// IApplicative
static of<T>(value: T): Just<T> {
return new Just<T>(value);
}

// IMaybe
isJust() { return true }
isNothing() { return false }
getOrElse<U>(defaultValue: U) { return this.value() }

// IFunctor
map<U, V>(fn: (x: T) => U): Just<U> {
return new Just<U>(fn(this.value()));
}

// IApply
ap<U>(b: U) {
const f = this.value();
if (f instanceof Function) {
return Just.of<U>((f as Function)(b))
}
}

// IChain
chain<U>(fn: (T) => U): U {
return fn(this.value());
}
}

πŸ“š Nothing λͺ¨λ‚˜λ“œ κ΅¬ν˜„β€‹

  • Nothing λͺ¨λ‚˜λ“œλŠ” Just λͺ¨λ‚˜λ“œμ™€ 달리 μ½”λ“œλ₯Ό μ™„λ²½ν•˜κ²Œ μ‹€ν–‰μ‹œν‚€μ§€ μ•ŠλŠ” 것이 섀계 λͺ©μ μ΄λ‹€.
import { _IMaybe } from './_IMaybe';
import { IMonad } from '../interfaces/IMonad';

export class Nothing implements _IMaybe<null>, IMonad<null> {
// IApplicative
static of<T>(value: T = null): Nothing { return new Nothing; }

// IMaybe
isJust() { return false; }
isNothing() { return true; }
getOrElse<U>(defaultValue: U) { return defaultValue; }

// IFunctor
map<U, V>(fn: (x) => U): Nothing { return new Nothing }

// IApply
ap<U>(b: U) {
return new Nothing;
}

// IChain
chain<U>(fn: (T) => U): Nothing { return new Nothing; }
}

πŸ“š Just와 Nothing λͺ¨λ‚˜λ“œ λ‹¨μœ„ ν…ŒμŠ€νŠΈβ€‹

  • λ‹€μŒ ν…ŒμŠ€νŠΈ μ½”λ“œλŠ” Justκ°€ Identity처럼 정상적인 λͺ¨λ‚˜λ“œλ‘œ λ™μž‘ν•˜λ©΄μ„œ _IMaybe μΈν„°νŽ˜μ΄μŠ€ κΈ°λŠ₯을 μΆ”κ°€λ‘œ μ œκ³΅ν•˜λŠ” 것을 보여쀀닀.
import * as R from 'ramda';

import { Just } from '../classes/Just';

console.log(
Just.of(100).isJust(), // true
Just.of(100).isNothing(), // false
Just.of(100).getOrElse(1), // 100
Just.of(100).map(R.identity).getOrElse(1), // 100
Just.of(R.identity).ap(100).getOrElse(1), // 100
Just.of(100).chain(Just.of).getOrElse(1), // 100
);
  • Nothing λͺ¨λ‚˜λ“œλŠ” Just와 달리 μžμ‹ μ˜ λͺ¨λ‚˜λ“œ κ΄€λ ¨ μ½”λ“œλ₯Ό λ™μž‘μ‹œν‚€μ§€ 말아야 ν•œλ‹€.
  • λ˜ν•œ, undefinedλ‚˜ null, NaN, Infinity와 같은 값을 λ°˜ν™˜ν•΄μ„œλ„ μ•ˆ λœλ‹€.
import { Nothing } from '../classes/Nothing';
import { Just } from '../classes/Just';

console.log(
Nothing.of().isJust(), // false
Nothing.of().isNothing(), // true
Nothing.of().getOrElse(1), // 1
Nothing.of().map((x) => x + 1).getOrElse(1), // 1
Nothing.of().ap(1).getOrElse(1), // 1
Nothing.of().chain(Just.of).getOrElse(1), // 1
);

πŸ“š Maybe ν…ŒμŠ€νŠΈβ€‹

  • 전체적인 예제 λ‚΄μš©μ€ μ±… λ˜λŠ” μ½”λ“œ μ°Έκ³  (P.318 ~ P.319)
  • λ‹€μŒ getJokeAsMaybe ν•¨μˆ˜λŠ” 정상적인 λ°μ΄ν„°λŠ” Maybe.Just둜 μ²˜λ¦¬ν•˜κ³ , 였λ₯˜κ°€ λ°œμƒν•˜λ©΄ reject ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜μ§€ μ•Šκ³  Maybe.Nothing을 λ°˜ν™˜ν•œλ‹€.
import * as R from 'ramda';

import { JokeType, getRandomJoke } from './getRandomJoke';
import { IMaybe, Maybe } from './classes/Maybe';

const _getJokeAsMaybe = async() => {
const jockItem: JokeType = await getRandomJoke();
const jock = R.view(R.lensProp('joke'), jockItem);
return jock;
}

export const getJokeAsMaybe = () => new Promise<IMaybe<string>>((resolve, reject) => {
_getJokeAsMaybe()
.then((jock: string) => resolve(Maybe.Just(jock)))
.catch(e => resolve(Maybe.Nothing)); // rejectκ°€ μ•„λ‹Œ resolve
});

export { IMaybe, Maybe };
  • getJokeAsMaybeλŠ” μ—λŸ¬κ°€ λ°œμƒν•˜λ©΄ reject 호좜 λŒ€μ‹  Maybe.Nothing을 λ°˜ν™˜ν•˜λ―€λ‘œ λ‹€μŒ ν…ŒμŠ€νŠΈ μ½”λ“œλŠ” catch문이 μ—†μ–΄ κ°„κ²°ν•˜λ‹€.
import { getJokeAsMaybe, IMaybe } from '../getJokeAsMaybe';

(async() => {
const joke: IMaybe<string> = await getJokeAsMaybe();
console.log(joke.getOrElse('something wrong'));
})();
  • MaybeλŠ” 이처럼 였λ₯˜μΌ λ•Œμ™€ 정상일 λ•Œλ₯Ό λͺ¨λ‘ κ³ λ €ν•˜λ©΄μ„œλ„ μ‚¬μš©ν•˜λŠ” μͺ½ μ½”λ“œλ₯Ό 맀우 κ°„κ²°ν•˜κ²Œ μž‘μ„±ν•  수 있게 ν•΄μ€€λ‹€.

πŸ¦„ Validation λͺ¨λ‚˜λ“œ 이해와 κ΅¬ν˜„β€‹

πŸ“š Validation λͺ¨λ‚˜λ“œλž€?​

  • λ°μ΄ν„°λŠ” μžˆλŠ”λ° κ·Έ 데이터가 μœ νš¨ν•œμ§€λ₯Ό νŒλ‹¨ν•˜λŠ” μš©λ„λ‘œ μ„€κ³„λœ λͺ¨λ‚˜λ“œκ°€ Validation이닀.
  • Validation λͺ¨λ‚˜λ“œλŠ” νŒνƒ€μ§€λžœλ“œμ˜ μ–΄ν”ŒλΌμ΄ κ·œκ²©μ— μ˜μ‘΄ν•΄ λ™μž‘ν•œλ‹€.
  • Validation ν΄λž˜μŠ€λŠ” Maybe와 λΉ„μŠ·ν•˜κ²Œ Success와 Failure 두 가지 λͺ¨λ‚˜λ“œλ‘œ κ΅¬μ„±λœλ‹€.
  • Success와 Failure λͺ¨λ‚˜λ“œλŠ” 기본적으둜 Identity λͺ¨λ‚˜λ“œμ˜ ap λ©”μ„œλ“œ λ°©μ‹μœΌλ‘œ λ™μž‘ν•œλ‹€. ap λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν• γ„· λ•ŒλŠ” Identity λͺ¨λ‚˜λ“œμ˜ valueκ°€ ν•¨μˆ˜μ—¬μ•Ό ν•œλ‹€.
import { Identity } from '../classes/Identity';

const add = (a: number) => (b: number) => a + b;

console.log(
add(1)(2), // 3
Identity.of(add).ap(1).ap(2).value(), // 3
);

πŸ“š Validation 클래슀 ꡬ쑰​

  • Validation ν΄λž˜μŠ€λŠ” Maybe와 λΉ„μŠ·ν•˜κ²Œ Success와 Failure 두 가지 λͺ¨λ‚˜λ“œλ‘œ κ΅¬μ„±λœλ‹€.
import { Success } from './Success';
import { Failure } from './Failure';

export class Validation {
static Success = Success;
static Failure = Failure;
static of<T>(fn: T): Success<T> {
return this.Success.of<T>(fn);
}
}

export { Success, Failure };
  • Success와 Failure λͺ¨λ‚˜λ“œλŠ” λ‹€μŒ μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜κ³  μžˆλ‹€.
export interface IValidation<T> {
isSuccess: boolean;
isFailure: boolean;
};

πŸ“š Success λͺ¨λ‚˜λ“œ κ΅¬ν˜„β€‹

  • Success λͺ¨λ‚˜λ“œλŠ” IChain ν˜•νƒœλ‘œλŠ” λ™μž‘ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ IFunctor와 IApply, IApplicative만 κ΅¬ν˜„ν•œλ‹€.
  • 그리고 λ‹€λ₯Έ λ©”μ„œλ“œλ“€κ³Ό 달리 ap λ©”μ„œλ“œλŠ” λ§€κ°œλ³€μˆ˜κ°€ Failure 인지에 따라 쑰금 λ‹€λ₯΄κ²Œ λ™μž‘ν•œλ‹€.
import { IApply } from '../interfaces/IApply';
import { IFunctor } from '../interfaces/IFunctor';
import { IValidation } from '../interfaces/IValidation';

export class Success<T> implements IValidation<T>, IFunctor<T>, IApply<T> {
constructor(public value: T, public isSuccess = true, public isFailure = false) {}

// IApplicative
static of<U>(value: U): Success<U> {
return new Success<U>(value);
}

// IFunctor
map<U>(fn: (x: T) => U) {
return new Success<U>(fn(this.value));
}

// IApply
ap(b) {
return b.isFailure ? b : b.map(this.value);
}
}
  • Success 클래슀의 valueλŠ” ν˜„μž¬ ν•¨μˆ˜λ‹€.
  • λ‹€μŒ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μ‹€ν–‰ν•΄ 보면, checkSuccess 2μ°¨ κ³ μ°¨ ν•¨μˆ˜κ°€ μ΅œμ’…μ μœΌλ‘œ boolean νƒ€μž…μ˜ 값을 λ°˜ν™˜ν•˜λ―€λ‘œ μ΅œμ’… Success 객체의 value값은 true이닀.
import { Success } from '../classes/Success';

const checkSuccess = <T>(a: Success<T>) => (b: Success<T>): boolean =>
[a, b].filter(({ isFailure }) => isFailure === true).length === 0;

console.log(
Success.of(checkSuccess)
.ap(Success.of(1))
.ap(Success.of(2))
);
// Success { value: true, isSuccess: true, isFailure: false }

πŸ“š Failure λͺ¨λ‚˜λ“œ κ΅¬ν˜„β€‹

  • Failure λͺ¨λ‚˜λ“œλŠ” μ΅œμ’…μ μœΌλ‘œ μ‹œλž˜ν•œ 원인을 λ¬Έμžμ—΄ λ°°μ—΄λ‘œ μ €μž₯ν•œλ‹€.
import { IApply } from '../interfaces/IApply';
import { IFunctor } from '../interfaces/IFunctor';
import { IValidation } from '../interfaces/IValidation';

export class Failure<T> implements IValidation<T>, IFunctor<T>, IApply<T> {
constructor(public value: T[], public isSuccess = false, public isFailure = true) {}

// IApplicative
static of<U>(value: U[]): Failure<U> {
return new Failure<U>(value);
}

// IFunctor
map(fn) {
return new Failure<T>(fn(this.value));
}

// IApply
ap(b) {
return b.isFailure ? new Failure<T>([...this.value, ...b.value]) : this;
}
}

πŸ“š λΉ„λ°€λ²ˆν˜Έ 검증 κΈ°λŠ₯ κ΅¬ν˜„β€‹

  • λΉ„λ°€λ²ˆν˜Έ 검증에 passwordλΌλŠ” 속성이 μžˆμ–΄μ•Ό ν•˜κ³ , 이 속성에 string νƒ€μž…μ˜ 값이 λ“€μ–΄ μžˆμ–΄μ•Ό ν•œλ‹€.
import { Failure } from '../classes/Failure';
import { Success } from '../classes/Success';

export const checkNull = <S, F>(o: { password?: string }) => {
const { password } = o;

return (password === undefined || typeof password !== 'string') ?
new Failure(['Password can not be null']) : new Success(o);
};
  • λ¬Έμžμ—΄ 길이가 μ΅œμ†Œ 6자 이상이어야 ν•œλ‹€λŠ” λ“± 검증은 λ‹€μŒ checkLength ν•¨μˆ˜λ‘œ κ΅¬ν˜„ν•œλ‹€.
import { Failure } from '../classes/Failure';
import { Success } from '../classes/Success';

export const checkLength = (o: { password?: string }, minLength: number = 6) => {
const { password } = o;

return (!password || password.length < minLength) ?
new Failure(['Password must have more than 6 characters']) : new Success(o);
};
  • λ‹€μŒ μ½”λ“œμ—μ„œ checkPassword ν•¨μˆ˜λŠ” μ΄λŸ¬ν•œ λ‚΄μš©μ„ κ΅¬ν˜„ν•œ μ˜ˆμ΄λ‹€.
import { Validation } from './classes/Validation';
import { checkNull } from './utils/checkNull';
import { checkLength } from './utils/checkLength';

export const checkPassword = (o): [object, string[]] => {
const result = Validation.of(a => b => o)
.ap(checkNull(o))
.ap(checkLength(o));

return result.isSuccess ? [result.value, undefined] : [undefined, result.value];
};
  • λ‹€μŒμ€ checkPassword ν•¨μˆ˜λ₯Ό ν…ŒμŠ€νŠΈν•˜λŠ” μ½”λ“œμ΄λ‹€.
import { checkPassword } from '../checkPassword';

[
{ password: '123456' },
{ password: '1234' },
{},
{ pa: '123456' },
]
.forEach((target, index) => {
const [ value, failureReason ] = checkPassword(target);

if (failureReason) {
console.log(index, 'validation fail.', JSON.stringify(failureReason));
} else {
console.log(index, 'validation ok.', JSON.stringify(value));
}
});

// 0 validation ok. {"password":"123456"}
// 1 validation fail. ["Password must have more than 6 characters"]
// 2 validation fail. ["Password can not be null","Password must have more than 6 characters"]
// 3 validation fail. ["Password can not be null","Password must have more than 6 characters"]

πŸ“š 이메일 μ£Όμ†Œ 검증 κΈ°λŠ₯ κ΅¬ν˜„β€‹

  • μ •κ·œμ‹μ„ μ‚¬μš©ν•œ μœ νš½μ„± 검증 νŒλ³„
import { Success } from '../classes/Success';
import { Failure } from '../classes/Failure';

export const checkEmailAddress = (o: { email?: string }) => {
const { email } = o;

const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

return re.test(email) ? new Success(email) : new Failure(['invalid email address']);
};
  • λ‹€μŒ checkEmail ν•¨μˆ˜λŠ” checkEmailAddress μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•΄ 데이터 μœ νš¨μ„±μ„ νŒλ³„ν•˜λŠ” λ‚΄μš©μ΄λ‹€.
import { Validation } from './classes/Validation';
import { checkEmailAddress } from './utils/checkEmailAddress';

export const checkEmail = (o): [object, string[]] => {
const result = Validation.of(a => o)
.ap(checkEmailAddress(o));

return result.isSuccess ? [result.value, undefined] : [undefined, result.value];
};
  • λ‹€μŒμ€ ν…ŒμŠ€νŠΈ μ½”λ“œμ΄λ‹€.
import { checkEmail } from '../checkEmail';

[
{ email: 'abc@efg.com' },
{ email: 'abcefg' },
].forEach((target, index) => {
const [ value, failureReason ] = checkEmail(target);

if (failureReason) {
console.log(index, 'validation fail.', JSON.stringify(failureReason));
} else {
console.log(index, 'validation ok.', JSON.stringify(value));
}
});

// 0 validation ok. {"email":"abc@efg.com"}
// 1 validation fail. ["invalid email address"]

πŸ¦„ IO λͺ¨λ‚˜λ“œ 이해와 κ΅¬ν˜„β€‹

πŸ“š IO λͺ¨λ‚˜λ“œλž€?​

  • Promise νƒ€μž… κ°μ²΄λŠ” 생성할 λ•Œ λ„˜κ²¨μ£ΌλŠ” 콜백 ν•¨μˆ˜κ°€ then λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•΄μ•Ό λΉ„λ‘œμ†Œ λ™μž‘ν•˜λŠ”λ°, 이번 μ ˆμ—μ„œ μ„€λͺ…ν•˜λŠ” IO λͺ¨λ‚˜λ“œλ„ 이런 λ°©μ‹μœΌλ‘œ λ™μž‘ν•œλ‹€.
import { IO } from './classes/IO';

const work = () => {
console.log('work called...');
return { name: 'Jack', age: 32 };
}

const result = IO.of(work).runIO(); // runIO λ©”μ„œλ“œκ°€ 호좜되면 κ·Έλ•Œ λ™μž‘ν•œλ‹€.
console.log(result); // { name: 'Jack', age: 32 }

πŸ“š μ™œ λͺ¨λ‚˜λ“œ 이름이 IO인가?​

  • IO λͺ¨λ‚˜λ“œλŠ” μ—¬λŸ¬ 개의 파일 μž…μΆœλ ₯을 μ„ μ–Έν˜• ν”„λ‘œκ·Έλž˜λ° λ°©μ‹μœΌλ‘œ μž‘μ„±ν•  수 있게 κ³ μ•ˆλ˜μ—ˆλ‹€.
  • runIO λ©”μ„œλ“œκ°€ ν˜ΈμΆœλ˜μ–΄μ•Ό λΉ„λ‘œμ†Œ λ™μž‘ν•˜κΈ° μ‹œμž‘ν•œλ‹€.
import * as fs from 'fs';
import * as R from 'ramda';

const work1 = () => fs.readFileSync('package.json');
const work2 = (json1) => () => {
const json2 = fs.readFileSync('tsconfig.json');
return [json1, json2];
};

const result = IO.of(work1)
.chain(json1 => IO.of(work2(json1)))
.map(R.map(JSON.parse))
.map(R.reduce((result: object, obj: object) => ({ ...result, ...obj }), {}))
.runIO()

console.log(result); // package.jsonκ³Ό tsconfig.json 파일 λ‚΄μš© 좜λ ₯

πŸ“š IO λͺ¨λ‚˜λ“œλ₯Ό μ‚¬μš©ν•  λ•Œ μ£Όμ˜ν•  점​

  • ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ„ ν•  λ•Œ ν•¨μˆ˜κ°€ 순수 ν•¨μˆ˜μ—¬μ•Ό ν•œλ‹€. 그런데 비동기 μž…μΆœλ ₯, ν”„λ‘œλ―ΈμŠ€, 생성기 등은 λΆ€μˆ˜νš¨κ³Όλ₯Ό λ°œμƒν•˜λŠ” ν•¨μˆ˜λ₯Ό λ§Œλ“€μ–΄ 버린닀.
  • κ·Έλž˜μ„œ μœ„ μ˜ˆμ œμ—μ„œλ„ 동기 버전인 readFileSync ν•¨μˆ˜λ₯Ό μ‚¬μš©ν–ˆλ‹€.

πŸ“š runIO λ©”μ„œλ“œ μ΄ν•΄ν•˜κΈ°β€‹

  • IO λͺ¨λ‚˜λ“œμ˜ runIO λ©”μ„œλ“œλŠ” λ‹€μŒ μ½”λ“œμ²˜λŸΌ μ—¬λŸ¬ 개의 λ§€κ°œλ³€μˆ˜λ₯Ό μ‚¬μš©ν•΄ λ™μž‘μ‹œν‚¬ 수 μžˆλ‹€.
export interface IRunIO {
runIO<R>(...args: any[]): R;
};

πŸ“š IO λͺ¨λ‚˜λ“œ κ΅¬ν˜„β€‹

  • IO λͺ¨λ‚˜λ“œ κ΅¬ν˜„ μ½”λ“œμ—μ„œλŠ” IApply λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•˜μ§€ μ•ˆν”λ‹€.
  • IO λͺ¨λ‚˜λ“œμ˜ map λ©”μ„œλ“œλŠ” runIOκ°€ 호좜되기 μ „κΉŒμ§€λŠ” λ™μž‘ν•˜μ§€ 말아야 ν•œλ‹€.
  • 이에 따라 λ‹€λ₯Έ λͺ¨λ‚˜λ“œμ™€ λ‹€λ₯΄κ²Œ μž…λ ₯받은 콜백 ν•¨μˆ˜λ₯Ό pipeλ₯Ό μ‚¬μš©ν•΄ μ‘°ν•©ν•˜λŠ” λ°©μ‹μœΌλ‘œ κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€.
import { IRunIO } from '../interfaces/IRunIO';
import { IFunctor } from '../interfaces/IFunctor';

const pipe = (...funcs) => (arg) => funcs.reduce((value, fn) => fn(value), arg);

export class IO implements IRunIO, IFunctor<Function> {
constructor(public fn: Function) {}

static of(fn: Function) { return new IO(fn); }

// IRunIO
runIO<T>(...args: any[]): T {
return this.fn(...args) as T;
}

// IFunctor
map(fn: Function): IO {
const f: Function = pipe(this.fn, fn);

return IO.of(f);
}

// IChain
chain(fn) {
const that = this;

return IO.of((value) => {
const io = fn(that.fn(value));

return io.fn();
});
}
}
  • chain λ©”μ„œλ“œλŠ” νƒ€μž… 주석을 달면 μ½”λ“œκ°€ μ»΄νŒŒμΌλ˜μ§€ μ•ŠλŠ”λ‹€. 이 μ½”λ“œλŠ” μžλ°”μŠ€ν¬λ¦½νŠΈμ²˜λŸΌ μ ‘κ·Όν•΄μ•Ό λ™μž‘ν•œλ‹€.
  • chain에 μž…λ ₯λ˜λŠ” 콜백 ν•¨μˆ˜ fn은 IOνƒ€μž… 객체λ₯Ό λ°˜ν™˜ν•œλ‹€. fn 호좜의 λ°˜ν™˜κ°’μ€ IO νƒ€μž… 객체이닀. λ˜ν•œ, 이 IO νƒ€μž… 객체에 μ €μž₯λ˜λŠ” ν•¨μˆ˜ λ˜ν•œ IO νƒ€μž… 객체λ₯Ό λ°˜ν™˜ν•˜λŠ” ν˜•νƒœλ‘œ κ΅¬ν˜„λ˜μ—ˆμœΌλ―€λ‘œ io.fn() ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•΄ chain λ©”μ„œλ“œκ°€ 또 λ‹€λ₯Έ IO νƒ€μž… 객체λ₯Ό λ°˜ν™˜ν•˜λ„λ‘ κ΅¬ν˜„λ˜μ–΄ μžˆλ‹€.

πŸ“š μ•ž λ©”μ„œλ“œλ“€μ˜ λ°˜ν™˜κ°’ 얻기​

  • IO λͺ¨λ‚˜λ“œλŠ” μ‹œμž‘ν•  λ•Œμ˜ 콜백 ν•¨μˆ˜κ°€ runIO 호좜 λ•Œ μ „λ‹¬ν•œ λ§€κ°œλ³€μˆ˜λ₯Ό λ°›λŠ” 방법과 κ·Έ μ΄ν›„μ˜ map ν˜Ήμ€ chain λ©”μ„œλ“œκ°€ μ•ž μž‘μ—…μ˜ 결괏값을 λ°›λŠ” ν˜•νƒœκ°€ λ‹€λ₯΄λ‹€.
import { IO } from '../classes/IO';

const result = IO.of((a1) => {
console.log('io started', a1);
return a1;
})
.runIO(1); // runIOκ°€ 전달해 μ€€ μ‹œμž‘κ°’

console.log(result);
// io started 1
// 1
  • λ‹€μŒ μ½”λ“œμ—μ„œ IO 객체의 μ½œγ„Ήλ°± ν•¨μˆ˜λŠ” a1 λ³€μˆ«κ°’μ„ λ°˜ν™˜ν•˜λŠ”λ°, map λ©”μ„œλ“œλŠ” 이 값을 λ‹€λ₯Έ λͺ¨λ‚˜λ“œμ—μ„œ 봀던 것과 λ˜‘κ°™μ€ λ°©μ‹μœΌλ‘œ μ–»λŠ”λ‹€.
import { IO } from '../classes/IO';

const result = IO.of((a1) => {
console.log('io started', a1);
return a1;
})
.map((a2) => {
console.log('first map called', a2);
return a2 + 1;
})
.runIO(1);

console.log(result);
// io started 1
// first map called 1
// 2
  • κ·ΈλŸ¬λ‚˜ chain λ©”μ„œλ“œμΌ λ•ŒλŠ” IO λͺ¨λ‚˜λ“œλ₯Ό λ°˜ν™˜ν•΄μ•Ό ν•˜λ―€λ‘œ λ‹€μŒκ³Ό 같은 μ½”λ“œκ°€ λœλ‹€.
import { IO } from '../classes/IO';

const result = IO.of((a1) => {
console.log('io started', a1);
return a1;
})
.chain((a2) => {
return IO.of(() => {
console.log('first chain called', a2);
return a2 + 1;
})
})
.runIO(1);

console.log(result);
// io started 1
// first chain called 1
// 2
  • 결둠적으둜 chain λ©”μ„œλ“œμ—μ„œ μ•ž μž‘μ—…μ˜ κ²°κ³Όλ₯Ό μ–»μœΌλ €λ©΄ λ‹€μŒ μ½”λ“œμ²˜λŸΌ 마치 2μ°¨ κ³ μ°¨ ν•¨μˆ˜ ν˜•νƒœλ‘œ λ³΄μ΄λŠ” λ°©μ‹μœΌλ‘œ κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€.
import { IO } from '../classes/IO';

const chainCB = a2 => IO.of(() => {
console.log('first chain called');
return a2 + 1;
});

const result = IO.of((a1) => {
console.log('io started', a1);
return a1;
})
.chain(chainCB)
.runIO(1);

console.log(result);
// io started 1
// first chain called 1
// 2