Applicative Functor와 Monad의 차이

Posted on September 6, 2022

Applicative Functor 또는 줄여서 Applicatives라 부릅니다.
Applicatives의 기본 개념은 learnyouahaskell를 참고해 주시고, 여기서는 Applicatives와 Monad의 다른 점을 주로 보겠습니다.

아직 결정되지 않은 Effect

2022-12-18 추가
모나드는 joinreturn이 있으면 모나드입니다. join이 있으면 Effect를 합칠join 수 있습니다. 모나딕 동작이란 아직 결정되지 않은 Effect를 이 전 계산값에 의존해서 바꿀 수 있고, 이를 합칠 때 나타납니다. 그런데, join :: m (m a) -> m a만으로는 m을 변형 혹은 만들어 낸다는 의미가 보이지 않고, (>>=) :: m a -> (a -> m b) -> m b에는 a -> m ba에 의존해서 바뀌는 m이 있다는 게 분명히 보입니다. join(>>=)는 같은 동작을 하는 걸로, (>>=)는 그저 join을 편하게 쓰기 위한 컴비네이터 쯤으로 생각했는데, 두 개가 주는 정보량이 다른 것으로 보였습니다.

어라, 기존에 내가 생각하던 것과 다른데?

모나딕 동작은 반드시 변형 가능한 Effect를, 즉 Effect Computation을 받아야만 모나딕 동작이 이루어집니다. 그래서, 생각을 a -> m b 모양이 반드시 필요하다로 가정하고, join이 있으면 모나딕 동작이 가능한 능력을 가진 상태로 a -> m b를 만나야 모나딕 동작을 한다고 가정했습니다.
또 한가지, Applicatives의 (<*>)로 모나드 join을 써서 구현한 ap를 그대로 가져다 쓰는 경우가 있습니다. 그렇다는 얘기는 join이 동작한다고 항상 모나드는 아닌 걸로 가정했습니다.

결론부터 얘기하면, 틀린 가정이었습니다.

Applicatives와 다르게 모나드는 Effect Computation(즉, 아직 결정된 Effect가 아니라 계산하면, 계산 결과값과 Effect가 나오는 식)을 받아야, 이전 결과값에 따라 Effect가 다르게 될 수 있는 건 틀리지 않았습니다. 틀린 부분은 a -> m b만이 Effect Computation이고 m (m a)는 Effect Computation을 받지 못한다는 생각입니다. m a으로 표현한 게, 더 이상 외부 개입이 불가능하게 계산이 끝난 상태가 아닙니다. 이미 계산이 끝나 결정된 상태는 Effect라 부르고, 계산하면 Effect가 나오는 식을 Effect Computation이라 부릅니다. m a는 Effect가 아니라, Effect Computation입니다. m a는 필요하기 전까진 <thunk>이며, 패턴 매칭이 필요한 때가 오면, 계산을 해서 결과값 + Effect를 만들어냅니다.

<thunk> --평가evaluate--> 결과값 + Effect

m a도 평가가 필요한 Effect Computation입니다. a에 의존해서 바뀔 수도 있는 “식”입니다.
Effect Computation과 Effect를 합치는 작업이 있어야 모나드인데, join :: m (m a) -> m a만으론 이 걸 표현하지 못한다는 생각이 틀렸습니다. join에 이미 “Effect Computation을 계산해서 합친다”는 의미가 들어가 있습니다. 기존의 생각대로, 다른 조건 없이 joinreturn만 있으면 모나드입니다. 기존에 “우연히” 제대로 알고 있던 걸, 좀 더 확실히 알게 되는 계기가 되었습니다. ※ 엄밀히는 추가로 몇 개의 모나드 법칙law을 만족해야 모나드가 됩니다.

잘 못 따라가는데도, 끝까지 바로 잡아주신 @Ailrun님 감사합니다.

※ 엄밀하게는 이렇게 알고 있어야 하고, 타입 서명을 읽을 때는 f a도 Computation이지만, a -> m b와 비교하면서 읽을 때는 a에 의존하며 변형되지 않는 Computation으로 우선 읽는게 코드 동작을 이해하는데 도움이 되긴 합니다.

타입으로 읽기 (확신은 아니고 추측만)

2022-12-12 추가

m a, a -> m b

f a, a (a -> b)

모나드와 applicative를 타입으로 읽어 보겠습니다.

의존성은 함수로 표현됩니다. ba에 의존한다면 a -> b 함수로 표현됩니다. a에 의존해서 b를 만들어낸다고 읽기도 합니다. - 2022-12-18 추가 : 일반적인 함수라는 개념으로 표현된다는 맞는 얘기지만, 하스켈 함수로만 표현된다고 하면 틀린 얘기입니다. m aa에 의존해서 바뀌는 Computation을 의미하니, 하스켈에서는 의존이 반드시 ->가 들어간 함수 모양으로 나타나진 않습니다.

모나드에는 m a로, m 하나를 인자로 받고, a -> m b가 있으니, a에 의존해서 새로운 m effect를 만듭니다. 반면 Applicative는 f af하나, f (a -> b)f 하나, 이렇게 두 개를 받습니다. 새로 만들어지는 f가 없습니다. 타입만으로 읽을 수 있는 가장 큰 차이입니다.

모나드도 m 두 개를 하나로 만들어 내보내고, Applicative도 f 두 개를 하나로 만들어 내보내는 건 같습니다.

m af a도 Computation이니, 타입만으로 이렇게 읽는 게 꼭 맞는 건 아닙니다. 타입을 이렇게 읽어서 동작을 추측만 한다고 생각하면 좋을 것 같습니다. m a라고 썼는데, a에 의존해서 m을 바꾸는 동작이 들어 있을 수도 있습니다.

저는 하스켈 학교 디스코드 서버에서 두 개가 어떻게 다른가란 질문만 하고, @Ailrun님, @재원님, @준규님, @기정님, @찬우님이 답변해 주신 것들을 정리한 글입니다. 답변해 주신 모든분께 감사드리고, 특히 최종 결론에 이르도록 많은 도움을 주신 @Ailrun님께 감사드립니다.

이 글의 목적

Applicatives의 정의, 구현을 설명하지 않습니다. 모나드와 Applicatives 차이점에만 주목하고, 어떤 상황에 모나드를 쓸지 Applicatives를 쓸지 아는 게 목표입니다.

파서를 아래와 같이 구현할 수 있습니다. 흐름만 보기 위해 return, pure는 생략했습니다.

결과를 모으는 작업 <*> 파서1 <*> 파서2 <*> 파서3

파서1 >>= 파서2 >>= 파서3 >>= 결과를 모으는 작업

Applicatives 스타일로 파서를 구현할지, 모나드 스타일로 구현할지 뭘 보고 결정하면 될까요?

Moi와 State

State 모나드와 비슷해 보이는 Moi Applicatives입니다. 둘 코드를 읽어보면 많이 비슷하지요? State Applicatives도 비슷한 모양인데, 일부러 구별되는 이름을 가진 Moi를 @준규님이 알려 주셨습니다.

-- Moi Applicative Functor @준규: State스러운 Applicatives
newtype Moi s a = Moi { runMoi :: s -> (a, s) }

Moi moifunc <*> Moi moiarg = Moi $ \state0 ->
  let (func, state1) = moifunc state0
      (arg, state2) = moiarg state1
  in  (func arg, state2)
       ^^^^^^^^
        apply

<*> 체인을 거치면서 바뀐 state2를 반환합니다.

-- State Monad
newtype State s a = State { runState :: s -> (a,s) }

(State statefunc) >>= make_st = State $ \state0 ->
  let (val, state1) = statefunc state0
      (State newfunc) = make_st val
  in  newfunc state1

>>= 체인을 거치면서 바뀐 state1새로 생성된 함수를 적용해서 새로운 state를 반환합니다.

추상화해서 말하면, 계산 결과가 Effect에 영향을 주느냐 아니냐의 차이가 있습니다. Monad는 계산 결과를 Effect에 반영할 수 있고, Applicatives는 그렇게 할 수 없습니다. (왜 그런지는 위에 타입으로 읽기에 설명해 두었습니다.)

Moi 구현에선 state1, state2를 통해 모나드처럼 실행 순서가 생겨, 이게 일반적인 속성으로 오해할 수 있는데, 순서는 Applicatives의 일반적인 특징은 아닙니다. 순차적으로 하든, 병렬로 돌아가든 그 건 Applicatives가 요구하는 특성은 아닙니다.

위에 <*>의 구현을 보면서 state를 주고 받는 것에 정신이 팔렸는데, <*>의 목적은 끝에 func arg, 즉 apply입니다.

2022-12-17 추가
모나드는 m a, (a -> m b)에서 m 두 개가 있는 게 보이고,
Applicatives는 f a, f (a -> b)에서 f 두 개가 있는 게 보입니다.
최종 m af a로 되려면, 어떻게든 두 개를 하나로 만드는 작업이 있어야 합니다.
모나드는 join에서 m (m a)가 나타나지만, Applicatives는 f (f a)가 보이지 않는 것으로 알았습니다. 하지만 Applicatives도 effect 두 개를 하나로 만드는 작업은 필요합니다. 꼭, 모나드의 join과 같은 동작을 해야 하는 것은 아닙니다. 모나드 인스턴스가 없는 Applicatives 타입도 f (f a) -> f a 동작은 있습니다.
joinreturn만 있으면 모나드입니다. 모나드 인스턴스가 없는 Applicatives는 join이 없다는 얘기지만, f (f a) -> f a가 없다는 얘긴 아닙니다. 또 하나, 모나드의 바인드로 구현한 ap(<*>)로 돈다고, (<*>)가 모나딕한 작업을 하는 건 아닙니다. 다시 말해, join이 돈다고 반드시 모나딕 작업을 하는 것도 아닙니다.

join이 이전 계산값에 의존해서 다음 Effect가 변형되는 걸 표현하면, 보통 aa -> m ...을 적용하는 모양으로 나타납니다. (반드시 꼭 이 모양이란 건 아니고, Computation만 들어가 있으면 되니 m a, a -> m b, (a -> m b) -> m b… 얼마든지 다른 모양이 가능합니다. 개념을 말한다고 봐주세요.) 그런데, a\_ -> ...를 넣어주면, join을 썼지만, 딱히 이전 값에 의존하는 동작이 나오지 않을 수도 있습니다. join 구현은 단순히 절차만 열어두었을 뿐이지, 어떤 함수를 넣어주냐에 따라 모나딕할 수도, 아닐 수도 있습니다.

Just (+1)에서 (+1)을 꺼내 1에 적용하려면,

fmap (\f -> f $ 1) (Just (+1))

Just (+1)에서 (+1)을 꺼내 Just 1에 적용하려면,

fmap (\f -> fmap f (Just 1)) (Just (+1))

이 됩니다. 실행한 결과 값은

Just (Just 1)

Applicatives 동작만 하고 있지만, 마지막에 join과 같이 f (f a)f a로 만들어야 하는 게 보입니다. ap가 곧 (<*>)인 경우는 join이 동작합니다. 다시 말해, join이 있어야 모나드가 되지만, 그렇다고 join이 돈다고 모두 모나딕 동작을 하는 건 아닙니다.
※ 위 fmap이 두 번 들어가는 예시는 apma >>= func = join (fmap func ma)를 써서 fmapjoin으로 표현하면 같은 코드를 얻을 수 있습니다.

mf `ap` mx = mf >>= \f -> mx >>= \x -> return f x
-- ma >>= func = join (fmap func ma)로 풀어 보면
join (fmap (\f -> join (fmap (\x -> return f x) mx)) mf)
                  ^^^^              ^^^^^^

밑 줄 친 부분을 지우면 위와 같은 코드가 됩니다.

Reader의 <*>와 >>=

g >>= f = \x -> f (g x) x -- f :: a -> (r -> b)
f <*> g = \x -> f x (g x) -- f :: r -> (a -> b)

둘이 거의 같아 보이지 않나요? 인자 순서만 좀 다르고 하는 일은 같은 걸로 보입니다. 저는 샘플 인스턴스를 찾아 볼 때, 이 걸 보는 바람에 잠깐동안 Applicatives도 Effect를 합성하는 것이 아닐까 생각했습니다. Reader 모나드의 Effect는 외부 스코프 변수를 참조하는 것입니다. (물론, 하스켈에서는 실제로 현재 함수의 스코프를 벗어나는 값에 접근하는 게 아니라, 외부 스코프인 척 하는 값을 컴비네이터bind가 끌고 다닙니다.) 이 값을 r이라고 할 때, r을 두 번 받는 Effect를 합치면 r을 한 번 받도록 만들면 됩니다. r을 한 번만 받아서 컴비네이터가 두 번 써먹으면 되게 만든게 바로 Reader의 join입니다. bind는 이 join을 이용합니다. 위 소스를 보시면 \x -> ... 로 둘 다 한 번만 r을 받고, g에 한 번, f에 한 번 써먹고 있습니다. 이 코드를 보면서 <*>도 바인드와 비슷하게 Effect를 합성하는구나로 오해 했습니다.

Applicatives의 속성은 Effect를 합성하는 걸 강요하지 않습니다. 물론, 합성해도 됩니다만, 합성을 하냐 안하냐는 Applicatives의 속성이 아닙니다. Applicatives를 공부하면서 기존 인스턴스1를 찾아봤는데, 하필 모나드이기도 한 것들만 찾아 보는 바람에 Applicatives도 Effect를 합성하는 일종이라 오해했습니다. 대부분 모나드이기도 한 Applicatives는 Effect를 모나드와 비슷하게 합성하고, 마지막에만 apply가 나타납니다. 모나드에 있는 ap<*>를 구현하기도 하고, >>=로도 <*>를 구현할 수 있습니다.

Applicatives는 Effect를 다루는가

Effect는 join이 있어야만 합니다. 즉 Monad 클래스의 인스턴스여야만 Effect로 볼 수 있습니다. 하지만, Applicatives에서 다루는 것은 join이 없어 딱 Effect라고 볼 수는 없습니다. 딱 지칭할 용어를 아직 찾지 못했습니다. 이 문서에서는 용어를 알게 될 때까지 Effect Like 라고 표기하겠습니다. 보통 모나드는 모두 Applicatives의 인스턴스를 만들 수 있기 때문에, 그냥 Effect로 표기하는 문서들도 많습니다. 그리고, 공식 문서들에서도 Effectful 함수를 지칭하는 action이란 용어를 쓰기도 합니다. Effect 정의에 따라, Effct로 불러도 되는 건지 아직 확실하지 않습니다. – 2022.10.21 추가 : Monad의 Effect와 구별해서 Applicative Effect라 부릅니다.

<*>의 타입

※ 아래는 모나드에서 다루는 Effect와는 반드시 같지 않을 수 있기에 Effect Like라고 임시 표기했는데, 특별한 이름 없이 Applicative Effect라 부르기도 합니다.

타입만 보고도 추론할 수 있는게 있습니다.

(<*>) :: f(a -> b) -> f a -> f b
         ^^^^^^^^^    ^^^
            함수       값

이름 그대로, Effect Like를 가진 함수를 Effect Like를 가진 값에 적용apply하는 컴비네이터입니다.

Effect Like를 가지고 있는 함수 f (a -> b)와 Effect Like를 가진 f a를 받아서 f b를 만들어 내려면, 어찌됐건 aa -> b를 적용해서 b를 얻어야 합니다. 그럼 f 두 개가 있는데, 어느 f가 가지고 있던 Effect Like를 살려야 할까요? 모나드의 바인드는 (편의상, 다 같은 m이지만 번호를 붙이겠습니다.) m1 a -> (a -> m2 b) -> m3 b에서 m1이 이미 있고, m2가 새로 생깁니다. 그럼, 이 두 개의 Effect를 합쳐join서 m3를 만들어 냅니다. <*>도 Effect Like를 합칠까요? 답은, 합쳐도 되고, 버려도 되고, 둘 중 하나를 골라도 되고, 따로 Applicatives가 되기 위한 제약은 없습니다.

※ Theorem for free로 유니크한 <*>가 정의되는 걸 보일 수 있다하니 신기하기도 합니다. 타입만으로 구현이 정해진다는 게 신기하다까지만 보고 넘어간 상태인데, 언젠가 자세히 봐야겠습니다.

2023.9.19 추가 ※ theorem for free를 한글로 풀어 놓은 페이지를 발견 했습니다. 당신은 이미 펑터Functor를 알고 있다 - YOKITOMI.log

리스트의 <*>와 >>=

> [(\x -> replicate x 1)] <*> [1,2,3]
[[1],[1,1],[1,1,1]]

위와 비슷하지만, join이 동작해서 [[]][]로 만듭니다.

-- @찬우: Applicatives가 할 수 없는 일
> [1,2,3] >>= \x -> replicate x 1
[1,1,1,1,1,1]

병렬 작업

<*>로 엮은 체인에서 실행 순서는 따로 정해져 있지 않습니다. 물론, Moi나 State처럼 순서가 생기도록 할 수도 있지만, 일반적인 경우는 아닙니다. 따로, 실행 순서에 대한 제약은 없으니 병렬 작업을 표현하는데도 Applicatives를 활용할 수 있습니다.

아래는 @Ailrun님이 예를 들어 주신 Concurrently 타입입니다.

testFunc =
  runConcurrently
  $ (,,)
    <$> Concurrently (print 1)
    <*> Concurrently (print 2)
    <*> Concurrently (print 3)

결론

이름 그대로 함수를 어딘가에 적용하는 겁니다. 일반 함수를 위한 ($) :: (a -> b) -> a -> b처럼, 함수와 인자를 받아, 함수를 인자에 적용하는 일을 하는데, 단 함수와 인자가 Effect Like를 가지고 있습니다.

다음 Effect는 이전 액션 결과와 상관 없이, 항상 일정하게 나오는 경우 모나드보다 Applicative가 어울립니다.

a 5개 파싱, b 5개 파싱, c 5개 파싱 같은 건 Applicatives로 구현하지만,
a 여러 개 파싱, 앞에 b 개수만큼 파싱, 앞에 c 개수만큼 파싱처럼, 앞 파서의 결과값에 따라 파싱 동작이 달라질 경우 모나드로 구현해야 합니다.

참고 Applicative vs. Monadic Parsing(2023.11.30 확인 결과, 깨진 링크입니다.)

@todo a 여러 개 파싱, b 여러 개 파싱 은 Applicatives로 구현 가능하다고 합니다.
하지만, a 여러 개 파싱, b 여러 개 파싱, c 여러 개 파싱은 Applicatives로 안된다고 합니다. 조금 더 공부가 필요한데, 얼핏 보기에 정보 누적을 재귀를 이용하여 구현하는 것 같습니다.

일반적인 용도는 컨텍스트, Applicative Functor, Traversable에서 일부 다루었습니다.

P.S
Functor, Applicatives, Monad를 하나의 계보에 올려 놓고,
“Functor보다 Applicatives가 더 강력하다”
“Applicatives보다 Monad가 더 강력하다”
등으로 표현하기도 하고,
“모나드가 꼭 있어야 되는 일이 아니고, Applicatives로 해결할 수도 있다”
등의 말이 종종 보입니다.

물론, 하나의 타입이 Applicatives도, Monad도 구현되어 있다면, Applicatives가 할 수 있는 일은, Monad로 표현할 수 있긴 하나, 둘의 용도는 전혀 다른게 아닌가 합니다. 작업 체이닝을 할 때, Applicatives는 고정된 Effect 동작을 할 경우에 적합하고, Monad든 Effect가 변형되는 경우에 적합합니다. 그리고, 병렬로 동작해야 하는 경우 Monad 구현은 아예 없습니다. 이런 경우에는 Applicatives가 할 수 있는 일은 Monad가 다 할 수 있다고 말하는 게 맞지 않습니다.

Applicative do-notation
Functional Pearl. Applicative programming with effects - Conor Mcbride, Ross paterson
Difference between Monad and Applicative in Haskell(Stackoverflow)

2022.9.10 추가

Ailrun님께 2~3일에 걸쳐 집요하게 질문드려 이해하게 된 내용들을 정리해서 추가합니다. 혹시 틀린 내용이 있다면, 분명 제가 이해를 잘 못한 것입니다.

Q. Applicatives에서도 이전 Effect가 다음 Effect에 영향을 주고 있습니다. f a <*> f b에서 어떻게 f af b가 독립적으로 동작할 수 있을까요?

A. abf에, 즉 Effect에 영향을 주지 않습니다. Lazy하게 생각하면, Effect에 따라 a작업을 만들어 놓고, 그 다음 바뀐 Effect에 따라 b작업을 만들어 놓습니다. 그럼, 이렇게 만들어진 ab사이에는 아무런 연관이 없습니다. f a와 f b는 독립적으로 동작하지 못하지만ab는 독립적으로 동작합니다., f af b는 서로 독립입니다. Effect 끼리는 영향을 줄 수 있지만, f af b끼리가 아닌 f a <*> f b가 될 때만 그 둘이 가지는 Effect가 연관성이 생깁니다. f a <*> f bf a에 의존할 수 있고, f b에 의존할 수 있지만, f af b는 서로 의존성이 없다는 말입니다.

Q. bind는 Effect합성을 하며 값이 Effect에 영향을 미칩니다. 어떻게 bind를 쓰면서 <*>를 구현할 수 있을까요?

A. bind가 하는 일은 Effect를 합치는 일이지, 누가 무슨 Effect를 어떻게 만들어 내는가에는 관여하지 않습니다. bind 컴비네이터는 Effect가 있는 m a와 Effect를 만들어내는 a -> m b를 받아서 어떤 작업을 합니다. 어떻게 Effect를 만들어내느냐는 bind가 하는 일이 아닙니다. Effect를 만들어내는 건 인자로 들어오는 a -> m b에 달렸습니다.

bind :: m1 a -> (a -> m2 b) -> m3 b
-- (모두 똑같은 타입 `m`이지만 편의를 위해 숫자를 붙이겠습니다.)

bindm1 a에서 a를 꺼내어 f :: a -> m2 b를 적용하고, m1m2를 합처 m3 b를 반환합니다.
f의 서명을 보이는대로 해석하면 m2ba에 의존하고 있습니다. 그런데, *f 내부에서 a를 받아 (\_ -> ... 로 받는 식으로 m2에 영향을 주지 않고 있다면) m2return이 만들었다든지 하는 trivial한 Effect만 가진다면, bind를 썼지만, 값이 Effect에 영향을 주지 않는 상태가 됩니다. bind를 썼기 때문에 반드시 값이 Effect에 영향을 미치고 있는 게 아니라, f가 어떤 동작을 하냐에 따라 다릅니다. Effect에 값이 영향을 미치면 안되는 Applicatives를 bind를 써서 구현할 수도 있다는 얘기입니다.

State 모나드에서 bind<*>를 구현하는 걸 살펴 보겠습니다.

-- State의 <*>
StateT mf <*> StateT mx = StateT $ \ s -> do
   ~(unwrapf, s') <- mf s
   ~(unwrapx, s'') <- mx s'
   return (unwrapf unwrapx, s'')

-- Lazy 패턴 매칭은 무시하고, `bind`의 동작을 살펴 보겠습니다.
mf >>= \(unwrapf, s') -> mx >>= \(unwrapx, s'') -> return (unwrapf unwrapx, s'')

\(unwrapf, s') -> mx는 패턴 바인딩으로 mf에서 m을 벗겨 unwrapf에 바인딩해 놓을 뿐 Effect에 영향을 미치는 동작이 없습니다.
\(unwrapx, s'') -> return (unwrapf unwrapx, s'')는 패턴 바인딩으로 mx에서 m을 벗겨 unwrapx에 바인딩해 놓을 뿐 Effect에 영향을 미치는 동작이 없습니다.
여기서 s''unwrapf에 의존해서 바뀐다거나, s''을 읽어오는 동작이 바뀐다면, 모나드를 써야만 합니다.


  1. Applicative 인스턴스이며 Monad인 예시

    instance Applicative STM where
      (<*>) = ap
      liftA2 = liftM2
    
    instance Applicative IO where
      (<*>) = ap
      liftA2 = liftM2
    
    nstance Applicative Maybe where
      Just f  <*> m       = fmap f m
      Nothing <*> _m      = Nothing
    
      liftA2 f (Just x) (Just y) = Just (f x y)
      liftA2 _ _ _ = Nothing
    
    instance Monoid a => Applicative ((,) a) where
      (u, f) <*> (v, x) = (u <> v, f x)
      liftA2 f (u, x) (v, y) = (u <> v, f x y)
    
    instance (Functor m, Monad m) => Applicative (StateT s m) where
      StateT mf <*> StateT mx = StateT $ \ s -> do
          ~(f, s') <- mf s
          ~(x, s'') <- mx s'
          return (f x, s'')
    --            ^^^
    --           apply
    
    instance Applicative [] where
      fs <*> xs = [f x | f <- fs, x <- xs]
    --             ^^^
    --            apply
    
    instance Applicative ((->) r) where  
      f <*> g = \x -> f x (g x)
    --                ^^^
    --               apply
    
    instance (Functor m, Monad m) => Applicative (WriterT w m) where
      WriterT mf <*> WriterT mx = WriterT $ \ w -> do
          (f, w') <- mf w
          (x, w'') <- mx w'
          return (f x, w'')
    --            ^^^
    --           apply
    
    -- 참고
    -- 모나드에 있는 ap를 쓰지 않고 구현한다면 아래같이 ap와 똑같은 모양이 됩니다.
    --instance Applicative IO where
    --  a <*> b = do
    --    f <- a
    --    x <- b
    --    return (f x)
    ↩︎
Github 계정이 없는 분은 메일로 보내주세요. lionhairdino at gmail.com