최근 합성성을 지향하는 스트림 형태의 좋은 라이브러리들이 많이 나와 모나드에 대한 이야기가 예전보다 훨씬 실용적이게 되는것 같고 좋은 자료들이 많아 진것 같다. Haskell의 에러(예외)처리와 Scala의 for comprehensions을 공부하면서 정리한 Monad 이야기
1. 정의
Monad는 타입인데, Monad m 은 원소 a,b / functor f,g (a->m) 와 bind 함수 flatMap (혹은 »=, m->m), unit 함수(a->m)에 대해 아래의 3가지 성질을 만족한다.
- bind 함수와 functor에 대한에 대한 결합법칙
- bind 함수 왼쪽 unit에 대한 Left Unit
- bind 함수 오른쪽 unit에 대한 Right Unit
m.flatMap(a -> f(a))
.flatMap( b -> g(b))
== m.flatMap( a -> f(a).flatMap(b -> g(b))
)
unit(a).flatMap
( b -> f(b) ) == f(a)
m.flatMap(a -> unit(a)
) == m
2. 용도
현재 나의 경우는
예외 처리를 깔끔하게 하여 합성성을 높이는데
모나드를 사용한다. 사실 헤스켈에서도 모나드라는것이 IO의 예외처리를 위해 차용되었고, 자바의 스트림이나 FRP를 쓰기 전까지는 합성성
에 대한 강력함을 몰랐는데, 프로그래밍을 선언적이고 (따라서) 읽기 쉽게 하는데 합성성이 미치는 영향이 매우 크고, 합성성에서의 예외처리(혹은 Side effect 처리)는 항상 고민이 되는 주제인것 같다.
무시하거나 “nothing”으로 처리해도 되는 예외는 모나드로 처리하는것이 좋은것 같다.
3. 사용 예
Haskell의 Maybe와 Side effect 처리
위의 Haskell 에러처리 예제가 Haskell에서 Monad를 어떻게 사용하는지 아주 잘 보여준다. 요약을하면,
- Haskell은 예외처리를 값(타입)으로 한다.
- 예외의 전파는 정형화된 패턴을 가지므로 구조화 할 수 있다.
- Haskell은 Monad를 예외를 전파하는 정형화된 구조로 사용한다.
Scala의 for comprehension
역시 위의 링크에서, scala와 for-comprehensions이 Haskell의 do notation과 동일하다는 설명이 나온다.
물론 Haskell의 예외처리 예제에서도 do notation을 사용하며, 의존성이 있는 Future등에서 flatMap와 for comprehension을 사용하면 flatMap안에서 flatMap을 할 필요 없이 읽기 편리하게 사용할 수 있다.
4. 기타
- kotlin의 flatMap은 Iterable을 리턴하면 List형태로 변환시켜 주는데, Monad적인 성격(합성의 예외처리)보다는 내부에 포함하고 있는 겹겹의 List객체를 복잡한 변환 없이 “flat”하게 다음으로 전달할때 주로 사용한다. (물론.. 내 경우)
- Rx Observable의 flatMap은 subscription source Scheduler에 영향을 받으면서 다른 작업을 “순차적”으로 할때 주로 사용한다. (이것도 역시.. 물론.. 내 경우)