Якщо порівнювати типи
(<*>) :: Applicative a => a (s -> t) -> a s -> a t
(>>=) :: Monad m => m s -> (s -> m t) -> m t
ми отримуємо підказку до того, що розділяє ці два поняття. Це (s -> m t)
за типом (>>=)
показує, що значення в s
може визначати поведінку обчислення в m t
. Монади дозволяють втручатися між значеннями та рівнями обчислень. (<*>)
Оператор не допускає таких перешкод: функція і аргумент обчислення не залежать від значень. Це справді кусає. Порівняйте
miffy :: Monad m => m Bool -> m x -> m x -> m x
miffy mb mt mf = do
b <- mb
if b then mt else mf
який використовує результат деякого ефекту для вирішення між двома обчисленнями (наприклад, запуск ракет і підписання перемир'я), тоді як
iffy :: Applicative a => a Bool -> a x -> a x -> a x
iffy ab at af = pure cond <*> ab <*> at <*> af where
cond b t f = if b then t else f
яке використовує значення ab
для вибору між значеннями двох обчислень at
таaf
, провівши обидва, можливо, для трагічного ефекту.
Монадічна версія по суті покладається на додаткову силу (>>=)
вибору обчислення зі значення, і це може бути важливо. Однак підтримка цієї влади робить монадів складно скласти. Якщо ми спробуємо побудувати "подвійне зв'язування"
(>>>>==) :: (Monad m, Monad n) => m (n s) -> (s -> m (n t)) -> m (n t)
mns >>>>== f = mns >>-{-m-} \ ns -> let nmnt = ns >>= (return . f) in ???
ми дістаємось так далеко, але тепер наші шари все підскочили. У нас є n (m (n t))
, тому нам потрібно позбутися зовнішньої n
. Як каже Олександр С, ми можемо це зробити, якщо маємо підходящий
swap :: n (m t) -> m (n t)
переставляти n
нутрощі та join
іншеn
.
Слабше "подвійне застосування" визначити набагато простіше
(<<**>>) :: (Applicative a, Applicative b) => a (b (s -> t)) -> a (b s) -> a (b t)
abf <<**>> abs = pure (<*>) <*> abf <*> abs
тому що між шарами немає перешкод.
Відповідно, добре визнати, коли вам справді потрібна додаткова потужність Monad
s, і коли ви можете піти з жорсткої структури обчислень, яка Applicative
підтримує.
Зауважте, до речі, що хоча складати монади складно, це може бути більше, ніж потрібно. Тип m (n v)
вказує обчислення з m
-ефектами, потім обчислення з n
-ефектами до -значення v
, де m
-ефекти закінчуються до початку n
-ефектів (звідси необхідність swap
). Якщо ви просто хочете m
переплутати -ефекти з n
-ефектами, то композиція, можливо, занадто багато, щоб запитати!