Applicative Functor 또는 줄여서 Applicatives라 부릅니다.
Applicatives의 기본 개념은 learnyouahaskell를 참고해 주시고, 여기서는 Applicatives와 Monad의 다른 점을 주로 보겠습니다.
아직 결정되지 않은 Effect
2022-12-18 추가
모나드는join
과return
이 있으면 모나드입니다.join
이 있으면 Effect를 합칠join 수 있습니다. 모나딕 동작이란 아직 결정되지 않은 Effect를 이 전 계산값에 의존해서 바꿀 수 있고, 이를 합칠 때 나타납니다. 그런데,join :: m (m a) -> m a
만으로는m
을 변형 혹은 만들어 낸다는 의미가 보이지 않고,(>>=) :: m a -> (a -> m b) -> m b
에는a -> m b
로a
에 의존해서 바뀌는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을 계산해서 합친다”는 의미가 들어가 있습니다. 기존의 생각대로, 다른 조건 없이join
과return
만 있으면 모나드입니다. 기존에 “우연히” 제대로 알고 있던 걸, 좀 더 확실히 알게 되는 계기가 되었습니다. ※ 엄밀히는 추가로 몇 개의 모나드 법칙law을 만족해야 모나드가 됩니다.잘 못 따라가는데도, 끝까지 바로 잡아주신 @Ailrun님 감사합니다.
※ 엄밀하게는 이렇게 알고 있어야 하고, 타입 서명을 읽을 때는
f a
도 Computation이지만,a -> m b
와 비교하면서 읽을 때는a
에 의존하며 변형되지 않는 Computation으로 우선 읽는게 코드 동작을 이해하는데 도움이 되긴 합니다.
타입으로 읽기 (확신은 아니고 추측만)
2022-12-12 추가
m a, a -> m b f a, a (a -> b)
모나드와 applicative를 타입으로 읽어 보겠습니다.
의존성은 함수로 표현됩니다.
b
가a
에 의존한다면a -> b
함수로 표현됩니다.a
에 의존해서b
를 만들어낸다고 읽기도 합니다. - 2022-12-18 추가 : 일반적인 함수라는 개념으로 표현된다는 맞는 얘기지만, 하스켈 함수로만 표현된다고 하면 틀린 얘기입니다.m a
도a
에 의존해서 바뀌는 Computation을 의미하니, 하스켈에서는 의존이 반드시->
가 들어간 함수 모양으로 나타나진 않습니다.모나드에는
m a
로,m
하나를 인자로 받고,a -> m b
가 있으니,a
에 의존해서 새로운m
effect를 만듭니다. 반면 Applicative는f a
로f
하나,f (a -> b)
로f
하나, 이렇게 두 개를 받습니다. 새로 만들어지는 f가 없습니다. 타입만으로 읽을 수 있는 가장 큰 차이입니다.모나드도
m
두 개를 하나로 만들어 내보내고, Applicative도f
두 개를 하나로 만들어 내보내는 건 같습니다.※
m a
나f a
도 Computation이니, 타입만으로 이렇게 읽는 게 꼭 맞는 건 아닙니다. 타입을 이렇게 읽어서 동작을 추측만 한다고 생각하면 좋을 것 같습니다. m a라고 썼는데, a에 의존해서 m을 바꾸는 동작이 들어 있을 수도 있습니다.
저는 하스켈 학교 디스코드 서버에서 두 개가 어떻게 다른가란 질문만 하고, @Ailrun님, @재원님, @준규님, @기정님, @찬우님이 답변해 주신 것들을 정리한 글입니다. 답변해 주신 모든분께 감사드리고, 특히 최종 결론에 이르도록 많은 도움을 주신 @Ailrun님께 감사드립니다.
Applicatives의 정의, 구현을 설명하지 않습니다. 모나드와 Applicatives 차이점에만 주목하고, 어떤 상황에 모나드를 쓸지 Applicatives를 쓸지 아는 게 목표입니다.
파서를 아래와 같이 구현할 수 있습니다. 흐름만 보기 위해 return, pure는 생략했습니다.
<*> 파서1 <*> 파서2 <*> 파서3
결과를 모으는 작업
>>= 파서2 >>= 파서3 >>= 결과를 모으는 작업 파서1
Applicatives 스타일로 파서를 구현할지, 모나드 스타일로 구현할지 뭘 보고 결정하면 될까요?
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
= moiarg state1
(arg, state2) 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 a
나f a
로 되려면, 어떻게든 두 개를 하나로 만드는 작업이 있어야 합니다.
모나드는join
에서m (m a)
가 나타나지만, Applicatives는f (f a)
가 보이지 않는 것으로 알았습니다. 하지만 Applicatives도 effect 두 개를 하나로 만드는 작업은 필요합니다. 꼭, 모나드의join
과 같은 동작을 해야 하는 것은 아닙니다. 모나드 인스턴스가 없는 Applicatives 타입도f (f a) -> f a
동작은 있습니다.
join
과return
만 있으면 모나드입니다. 모나드 인스턴스가 없는 Applicatives는join
이 없다는 얘기지만,f (f a) -> f a
가 없다는 얘긴 아닙니다. 또 하나, 모나드의 바인드로 구현한ap
가(<*>)
로 돈다고,(<*>)
가 모나딕한 작업을 하는 건 아닙니다. 다시 말해,join
이 돈다고 반드시 모나딕 작업을 하는 것도 아닙니다.
join
이 이전 계산값에 의존해서 다음 Effect가 변형되는 걸 표현하면, 보통a
에a -> 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이 두 번 들어가는 예시는ap
를ma >>= func = join (fmap func ma)
를 써서fmap
과join
으로 표현하면 같은 코드를 얻을 수 있습니다.`ap` mx = mf >>= \f -> mx >>= \x -> return f x mf -- ma >>= func = join (fmap func ma)로 풀어 보면 fmap (\f -> join (fmap (\x -> return f x) mx)) mf) join (^^^^ ^^^^^^
밑 줄 친 부분을 지우면 위와 같은 코드가 됩니다.
>>= f = \x -> f (g x) x -- f :: a -> (r -> b)
g <*> g = \x -> f x (g x) -- f :: r -> (a -> b) f
둘이 거의 같아 보이지 않나요? 인자 순서만 좀 다르고 하는 일은 같은 걸로 보입니다. 저는 샘플 인스턴스를 찾아 볼 때, 이 걸 보는 바람에 잠깐동안 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
로 <*>
를 구현하기도 하고, >>=
로도 <*>
를 구현할 수 있습니다.
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
를 만들어 내려면, 어찌됐건 a
에 a -> 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 a
와f b
가 독립적으로 동작할 수 있을까요?A.
a
와b
가f
에, 즉 Effect에 영향을 주지 않습니다. Lazy하게 생각하면, Effect에 따라a
작업을 만들어 놓고, 그 다음 바뀐 Effect에 따라b
작업을 만들어 놓습니다. 그럼, 이렇게 만들어진a
와b
사이에는 아무런 연관이 없습니다.f a와 f b는 독립적으로 동작하지 못하지만,a
와b
는 독립적으로 동작합니다.f a
와f b
는 서로 독립입니다. Effect 끼리는 영향을 줄 수 있지만,f a
와f b
끼리가 아닌f a <*> f b
가 될 때만 그 둘이 가지는 Effect가 연관성이 생깁니다.f a <*> f b
는f a
에 의존할 수 있고,f b
에 의존할 수 있지만,f a
와f 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`이지만 편의를 위해 숫자를 붙이겠습니다.)
bind
는m1 a
에서a
를 꺼내어f :: a -> m2 b
를 적용하고,m1
과m2
를 합처m3 b
를 반환합니다.
f
의 서명을 보이는대로 해석하면m2
와b
는a
에 의존하고 있습니다. 그런데, *f 내부에서 a를 받아 (\_ -> ...
로 받는 식으로m2
에 영향을 주지 않고 있다면)m2
가return
이 만들었다든지 하는 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`의 동작을 살펴 보겠습니다. >>= \(unwrapf, s') -> mx >>= \(unwrapx, s'') -> return (unwrapf unwrapx, s'') mf
\(unwrapf, s') -> mx
는 패턴 바인딩으로mf
에서m
을 벗겨unwrapf
에 바인딩해 놓을 뿐 Effect에 영향을 미치는 동작이 없습니다.
\(unwrapx, s'') -> return (unwrapf unwrapx, s'')
는 패턴 바인딩으로mx
에서m
을 벗겨unwrapx
에 바인딩해 놓을 뿐 Effect에 영향을 미치는 동작이 없습니다.
여기서s''
이unwrapf
에 의존해서 바뀐다거나,s''
을 읽어오는 동작이 바뀐다면, 모나드를 써야만 합니다.
Applicative 인스턴스이며 Monad인 예시
instance Applicative STM where
<*>) = ap
(= liftM2
liftA2
instance Applicative IO where
<*>) = ap
(= liftM2
liftA2
Applicative Maybe where
nstance Just f <*> m = fmap f m
Nothing <*> _m = Nothing
Just x) (Just y) = Just (f x y)
liftA2 f (= Nothing
liftA2 _ _ _
instance Monoid a => Applicative ((,) a) where
<*> (v, x) = (u <> v, f x)
(u, f) = (u <> v, f x y)
liftA2 f (u, x) (v, 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
<*> xs = [f x | f <- fs, x <- xs]
fs -- ^^^
-- apply
instance Applicative ((->) r) where
<*> g = \x -> f x (g x)
f -- ^^^
-- apply
instance (Functor m, Monad m) => Applicative (WriterT w m) where
WriterT mf <*> WriterT mx = WriterT $ \ w -> do
<- mf w
(f, w') <- mx w'
(x, w'') return (f x, w'')
-- ^^^
-- apply
-- 참고
-- 모나드에 있는 ap를 쓰지 않고 구현한다면 아래같이 ap와 똑같은 모양이 됩니다.
--instance Applicative IO where
-- a <*> b = do
-- f <- a
-- x <- b
-- return (f x)