Either 타입

계산의 실패나 오류를 던지는 부분 함수를 다루기 위해 어떻게 Option 자료형을 활용하는지 살펴보았습니다.

하미잔 이 자료형은 어떤 상황에서는 제한적일 수 있습니다. 성공하는 경우 A 의 정보를 포함한 Some<A> 을 얻지만 None 은 어떠한 데이터도 가지고 있지 않습니다. 즉 실패했다는 것은 알지만 그 이유를 알 수 없습니다.

이를 해결하기 위해 실패를 표현하는 새로운 자료형이 필요하며 이를 Left<E> 로 부르겠습니다. 또한 Some<A>Right<A> 로 변경됩니다.

// 실패를 표현
interface Left<E> {
  readonly _tag: 'Left'
  readonly left: E
}

// 성공을 표현
interface Right<A> {
  readonly _tag: 'Right'
  readonly right: A
}

type Either<E, A> = Left<E> | Right<A>

생성자와 패턴 매칭은 다음과 같습니다:

const left = <E, A>(left: E): Either<E, A> => ({ _tag: 'Left', left })

const right = <A, E>(right: A): Either<E, A> => ({ _tag: 'Right', right })

const match = <E, R, A>(onLeft: (left: E) => R, onRight: (right: A) => R) => (
  fa: Either<E, A>
): R => {
  switch (fa._tag) {
    case 'Left':
      return onLeft(fa.left)
    case 'Right':
      return onRight(fa.right)
  }
}

이전 callback 예제로 돌아가봅시다:

declare function readFile(
  path: string,
  callback: (err?: Error, data?: string) => void
): void

readFile('./myfile', (err, data) => {
  let message: string
  if (err !== undefined) {
    message = `Error: ${err.message}`
  } else if (data !== undefined) {
    message = `Data: ${data.trim()}`
  } else {
    // should never happen
    message = 'The impossible happened'
  }
  console.log(message)
})

이제 다음과 같이 signature 를 변경할 수 있습니다:

declare function readFile(
  path: string,
  callback: (result: Either<Error, string>) => void
): void

그리고 다음과 같이 사용합니다:

readFile('./myfile', (e) =>
  pipe(
    e,
    match(
      (err) => `Error: ${err.message}`,
      (data) => `Data: ${data.trim()}`
    ),
    console.log
  )
)