[Monad] 사용 예제 - Try : 예외 처리하기

현대의 대부분의 언어는 예외 처리를 위해서 try-catch 시스템을 사용한다. 예외가 발생할 수 있는 코드를 try 블록에 집어넣고, 예외를 throw하면 catch 블록에서 예외를 잡아서 처리하는 방식으로, 사실상 현대의 언어들이 예외를 처리하는 방식의 de facto라고 할 수 있다. 하지만 try-catch 시스템에는 여러 가지 문제가 있다.

우선 다른 함수를 호출할 때, 어떤 예외가 발생할지 모른다. 그래서 Java 같은 언어는 함수의 시그니쳐에 발생 가능한 예외를 적는 checked exception이라는 개념을 만들었지만, RuntimeException은 어떤 예외가 발생할지 모른다거나, 모든 예외를 하나하나 등록하기 귀찮아서 그냥 Exception이 발생한다고 적거나 하는 이유로 그다지 쓸모없다는 인식이 강하고 C#을 비롯한 다른 언어들에서는 사용되지 않는다. 그저 API 문서에 함수가 어떤 예외를 발생시킬 수 있는지 적을 뿐이다.

게다가 try-catch 시스템은 예외를 던지는 것이기 때문에 컨트롤 플로우가 뛰게 된다. 물론 현대 언어에서는 클로져나 람다 함수가 자주 사용되기 때문에 컨트롤 플로우가 직선적으로 흐르지 않는다. 하지만 try-catch 시스템은 도가 지나치다. 예외를 던지면, 예외를 잡을 때까지 컨트롤 플로우가 거슬러 올라간다. 그래서 try-catch에 의한 예외 시스템을 가독성이라는 측면에서 gotosetjmp/longjmp와 다를 게 없다고 비판하는 사람들도 있다. 이에 비해 Try 모나드를 사용한 예외처리는 좀 더 예측할 수 있고 가독성 있는 코드를 작성할 수 있게 해준다.

Try 모나드는 Option과 마찬가지로 두 모나드의 sum type이다. 하지만 하나의 타입 파라미터를 받는 Option과 다르게 타입 파라미터를 두 개 받는다. 이 두 타입은 각각 성공했을 때 결과 타입인 T와 에러가 발생했을 에러 타입인 E다. 그래서 Try 모나드는 Try[T, E]로 표현한다. Try 모나드가 가지는 타입은 각각 실행이 정상적으로 되어 T 타입의 값을 가지는 경우인 Ok[T]와 에러가 발생하여 E 타입의 에러가 난 경우인 Error[E]이다.

Try는 두 개의 타입 파라미터를 가지고 있는 만큼 타입을 진행시키는 bind 함수도 두 개 있다.

첫 번째 bind 함수는 Try[T, E] 타입을 Try[U, E]로 진행시킨다. 이 함수는 현재의 TryOk일 경우에만 실행된다. 현재의 값을 콜백 함수에 넣어 실행시키며, 콜백 함수의 결과가 이 bind 함수의 결과가 된다. 에러가 발생한 TryError일 경우 콜백 함수를 실행시키지 않으며, 결괏값은 현재와 같은 Error 타입이 된다.

두 번째 bind 함수 Try[T, E] 타입을 Try[T, F]로 진행시킨다. 이 함수는 위의 함수와 반대로 현재 값이 Ok일 경우 아무 일도 하지 않고, Ok인 값을 그대로 반환한다. 반면에 현재 TryError일 경우, 에러값을 콜백 함수에 넣어 그 결괏값을 반환한다.

이처럼 Try 모나드는 정상적인 실행 결과를 가지거나 에러값을 가진다. 이를 서로 다른 bind 함수를 이용하여 진행시킨다. 따라서 컨트롤 플로우가 뛰지 않고 언제나 bind 함수에 넘긴 콜백을 실행시키는 것으로 진행된다. 또한, 에러가 타입으로 나오기 때문에 어떤 함수가 어떤 에러를 발생시킬지 쉽게 알 수 있고, 발생한 에러가 어떻게 처리되는지를 컴파일 타임에 알 수 있다.


댓글

이 블로그의 인기 게시물

[C++] enum class - 안전하고 쓰기 쉬운 enum

Log Aggregator 비교 - Scribe, Flume, Fluentd, logstash

RAII는 무엇인가

[Python] cache 데코레이터로 최적화하기

[Web] SpeechSynthesis - TTS API