Either 모나드

박준규·2023년 3월 15일

이 글은 책 Learn You a Haskell for Great Good!에서 섹션 Error error on the wall을 읽고 정리한 것이다.

제목에 대하여

이 섹션의 제목 Error error on the wall는 동화 백설공주에서 왕비가 거울을 부를 때 하는 말인 "거울아, 거울아(Mirror, Mirror on the Wall)"를 패러디한 것이다.

달라진 점

  • 이 책에서는 타입 EitherMonad 인스턴스에 Error 제약이 있지만 현재는 그렇지 않다.
  • 이 책에서는 함수 failMonad 인스턴스에 정의되어 있지만 현재 fail은 타입 클래스 MonadFail에 정의되어 있다.

Error error on the wall

Maybe는 값에 실패할 수도 있다는 문맥을 추가한다. 값은 Just something이 될 수도 있고 Nothing이 될 수도 있다. 그런데 Nothing만으로는 실패했다는 사실만 알 수 있고 그 안에 왜 실패했는지 등의 정보를 더 넣을 수가 없다.

반면에 타입 Either e a는 값에 실패할 수 있다는 맥락도 추가할 수 있고 그 실패에도 값을 추가할 수 있다. 그래서 뭐가 잘못됐는지 설명하거나 실패에 대한 유용한 정보를 제공할 수 있다.

ghci> :t Right 4
Right 4 :: (Num t) => Either a t
ghci> :t Left "out of cheese error"
Left "out of cheese error" :: Either [Char] b

Either는 값에 실패할 수도 있다는 맥락을 추가하고 거기에 더해서 에러에도 값을 더할 수 있다는 점에서 강화된 Maybe라고 할 수 있고 모나드이기도 하다.

EitherMonad 인스턴스는 Maybe의 인스턴스와 비슷하다.

instance (Error e) => Monad (Either e) where
  return x       = Right x

  Right x  >>= f = f x
  Left err >>= f = Left err

  fail msg       = Left (strMsg msg)

return은 언제나처럼 값을 받아서 그 값에 최소한의 문맥을 준다. 여기서는 성공한 계산을 표현할 때 Right를 사용하기 때문에 값을 생성자 Right로 감싼다. Maybereturn과 비슷하다.

>>=LeftRight 두 가지 경우를 검사한다. Right의 경우에는 Just의 경우처럼 함수 fRight 안에 든 값에 적용한다. 에러일 때는 Left가 그대로 결과가 된다. Left 안에 든 값은 실패에 대해 설명하는 값이다.

타입 Either eMonad 인스턴스는 한 가지 조건이 더 있는데 타입 변수 e가 타입 클래스 Error의 인스턴스여야 한다는 것이다. 타입 클래스 Error는 값이 에러 메세지인 타입들의 집합 같은 것이다. 타입 클래스 Error는 함수 strMsg를 정의하는데 이 함수는 인자로 문자열 형태의 에러를 받아서 에러를 리턴한다. 타입 StringError의 좋은 예인데 String의 경우에는 인자로 넣은 값이 그대로 리턴된다.

ghci> :t strMsg
strMsg :: (Error a) => String -> a
ghci> strMsg "boom!" :: String
"boom!"

보통 Either에서 에러를 나타낼 때 String을 쓰기 때문에 크게 신경 쓰지 않아도 된다. do 표기법에서 패턴 매칭이 실패하면 Left 값으로 실패를 표현한다.

ghci> Left "boom" >>= \x -> return (x+1)
Left "boom"
ghci> Right 100 >>= \x -> Left "no way!"
Left "no way!"

>>=로 함수에 Left 값을 넣으면 어떤 함수이든지간에 그냥 같은 Left 값이 리턴된다. 함수에 Right 값을 넣으면 Right 안에 있는 값에 함수가 적용되는데 이 예제에서는 함수가 Left를 리턴한다.

성공하는 함수에 Right 값을 넣을 경우 아래처럼 이상한 타입 에러가 난다.

ghci> Right 3 >>= \x -> return (x + 100)

<interactive>:1:0:
    Ambiguous type variable `a' in the constraints:
      `Error a' arising from a use of `it' at <interactive>:1:0-33
      `Show a' arising from a use of `print' at <interactive>:1:0-33
    Probable fix: add a type signature that fixes these type variable(s)

Either e a에서 타입 변수 e을 어떤 타입으로 할지 잘 모르겠다는 뜻이다. Monad 인스턴스에 있는 Error e 제약 때문에 생긴 에러이다. 아래처럼 타입 시그니처를 추가하면 된다.

ghci> Right 3 >>= \x -> return (x + 100) :: Either String Int
Right 103

참고

profile
코딩하는 물총새

0개의 댓글