카테고리 이론은 배제하고 설명합니다. 애초에 Functor, Applicative, Monad가 타입 클래스라는것 부터, 카테고리 이론에서 말하는 Functor, Applicative, Monad와는 거리가 있게 됩니다. (타입클래스는 연산 그 자체가 아닌, 해당 연산이 적용 가능함을 나타냄)
포장된 타입
포장된 타입은 많이 쓰입니다. 가장 가까운 예로는 Maybe가 있습니다. Maybe는 Integral을 포장할 수도, String을 포장할 수도 있습니다. 패턴 매칭으로 포장을 해제해, 포장된 타입이 보관하고 있던 값에 대한 처리를 할 수 있습니다. 보통은 유용성을 위해 처리 이후 다시 포장을 합니다. (닫힌 연산이라고 해석할 수 있습니다.)
Functor
위에서 말했듯 포장된 타입이 보관하고 있는 값을 조작(매핑)하는 일은 빈번하게 일어납니다. 그 때마다 패턴 매칭을 하는 일은 귀찮습니다. 다시 포장하는 것도 귀찮습니다. 그래서 쓰는 것이 Functor입니다. Functor는 타입 차원에서 포장해제와 매핑, 재포장을 지원합니다. 이 기능을 하는 함수가 fmap입니다.
class Functor f where
fmap :: (a -> b) -> f a -> f b
-- Example use
fmap (\x -> 2*x) (Just 3)
f a를 포장해제, a -> b 함수를 사용해 매핑, 다시 f b 타입으로 포장합니다.
Applicative
일변수함수를 이용한 매핑은 Functor로 충분합니다. 하지만 다변수함수는 곤란합니다. 한땀한땀 포장해제하고 다시 포장하는 일을 하지 않기 위해 fmap을 이용하면, 그 결과로 포장된 함수가 나오기 때문입니다. 그리고, Functor는 포장된 함수를 이용한 매핑을 지원하지 않습니다.
(fmap (*) (Just 3)) -- Num a => Maybe (a -> a)
Applicative는 이 문제점을 없애줍니다.
class Functor f => Applicative f where
(<*>) :: f (a -> b) -> f a -> f b
-- Example use
(fmap (*) (Just 2)) <*> (Just 3)
f (a -> b)를 포장해제, 나머지는 fmap과 동일합니다.
Monad
포장해제된 값 간의 연산에 대해서 이제는 더이상 걱정할 거리가 없습니다. 하지만 포장해제된 값에서 포장된 값을 만들어내는 연산은 Applicative를 이용해 처리할 수 없습니다. 포장되지 않은 값에서 포장된 값을 만들어내는 연산의 예로, 실패 가능한 연산이 있습니다.
f :: Num -> Maybe Num -- 오류가 발생할 수 있는 연산
-- fmap f (Just 3) -- 작동은 되지만 Just Just x가 나와..
Monad는 이를 해결해 줍니다.
class Applicative m => Monad m where
(>>=) :: forall a b. m a -> (a -> m b) -> m b
m a를 포장해제, a -> m b를 이용해 매핑, 매핑한 결과들을 m b로 구성합니다.
포장된 타입이 하나의 값을 포장할 필요는 없다.
위쪽 Monad의 >>=의 설명은 좀 이상하게 보일 수 있습니다. 그냥 a -> m b를 m a 포장해제 한 값으로 호출한 결과를 리턴하는 것 아닌가? 왜 저렇게 복잡하게 설명하지? 하고 말입니다. 그런데, 포장된 타입이 하나의 값만을 포장할 필요는 없습니다. Haskell의 리스트([])도 모나드입니다. 함수를 배열의 각각 요소에 적용하는 식으로 매핑을 합니다.
그런데 뭔가 이상합니다. 리스트가 모나드라면, 리스트를 매핑하는 함수가 리스트를 반환하는게 가능하고, 그렇게 한 매핑의 결과가 그냥 리스트다?? 여기에서 Monad의 >>=가 저렇게 설명된 이유를 찾을 수 있습니다. 리스트의 각각의 원소를 매핑하는 상황 같은 경우에, a -> m b는 여러번 호출될 수 있고, 그 결과 여러개의 m b가 생성되며 이들을 어떻게 구성해서 하나의 m b로 만들어내냐에 대한 문제가 발생하기 때문입니다.
이에 대한 규칙을 마음대로 정할 수 있다면 혼란스럽기 때문에, Monad laws의 세번째 규칙은 이에 대해 제약을 겁니다.
Monad laws - Associativity(결합법칙)
(m >>= g) >>= h
m >>= (\x -> g x >>= h)
-- 이 둘이 같아야 한다.
수학 함수의 합성처럼, >>=가 결합법칙을 만족해야 한다는 규칙입니다.
이 규칙을 만족하면서 여러개의 리스트들을 하나의 리스트로 만드는 방법은 하나밖에 없습니다.
리스트를 flat 하는 것입니다. 리스트의 Monad 구현은 Monad law 위에서 필연적으로 정의된 것입니다.
'PRGRM > Haskell' 카테고리의 다른 글
[Haskell] 모든 Monad는 Applicative다. >>= 만으로 (0) | 2021.08.18 |
---|