이 글은 책 Learn You a Haskell for Great Good!에서 섹션 Error error on the wall을 읽고 정리한 것이다.
이 섹션의 제목 Error error on the wall는 동화 백설공주에서 왕비가 거울을 부를 때 하는 말인 "거울아, 거울아(Mirror, Mirror on the Wall)"를 패러디한 것이다.
Either의 Monad 인스턴스에 Error 제약이 있지만 현재는 그렇지 않다.fail이 Monad 인스턴스에 정의되어 있지만 현재 fail은 타입 클래스 MonadFail에 정의되어 있다.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라고 할 수 있고 모나드이기도 하다.
Either의 Monad 인스턴스는 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로 감싼다. Maybe의 return과 비슷하다.
>>=는 Left와 Right 두 가지 경우를 검사한다. Right의 경우에는 Just의 경우처럼 함수 f를 Right 안에 든 값에 적용한다. 에러일 때는 Left가 그대로 결과가 된다. Left 안에 든 값은 실패에 대해 설명하는 값이다.
타입 Either e의 Monad 인스턴스는 한 가지 조건이 더 있는데 타입 변수 e가 타입 클래스 Error의 인스턴스여야 한다는 것이다. 타입 클래스 Error는 값이 에러 메세지인 타입들의 집합 같은 것이다. 타입 클래스 Error는 함수 strMsg를 정의하는데 이 함수는 인자로 문자열 형태의 에러를 받아서 에러를 리턴한다. 타입 String이 Error의 좋은 예인데 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