Это имеет смысл, но в конечном итоге это не особенно полезно в его нынешнем виде. Проблема именно в том, что вы заметили: нет способа предоставить значение по умолчанию, которое делает разумные вещи с различными типами, или вообще преобразовать из f
в g
. Таким образом, у вас будет квадратичный взрыв в количестве экземпляров, которые вам нужно написать.
Вы не завершили DApplicative
экземпляр. Вот полная реализация для Maybe
и []
:
instance DApplicative Maybe [] where -- an "instance pair" of this class
(Just func) <*:*> g = fmap func g
Nothing <*:*> g = []
Это объединяет поведение как Maybe
, так и []
, потому что с Just
он делает то, что вы ожидаете, но с Nothing
он ничего не возвращает, пустой список.
Итак, вместо того, чтобы писать DApplicative
, который принимает два разных типа, что если бы у вас был способ объединить два аппликатива f
и g
в один тип? Если вы обобщите это действие, вы можете использовать стандарт Applicative
с новым типом.
Это можно сделать с помощью стандартной формулировки аппликаций, как
liftAp :: f (g (a -> b)) -> f (g a) -> f (g b)
liftAp l r = (<*>) <$> l <*> r
но вместо этого давайте изменим Maybe
:
import Control.Applicative
newtype MaybeT f a = MaybeT { runMaybeT :: f (Maybe a) }
instance (Functor f) => Functor (MaybeT f) where
fmap f (MaybeT m) = MaybeT ((fmap . fmap) f m)
instance (Applicative f) => Applicative (MaybeT f) where
pure a = MaybeT (pure (pure a))
(MaybeT f) <*> (MaybeT m) = MaybeT ( (<*>) <$> f <*> m)
Теперь вам просто нужен способ преобразовать что-то во внутреннем аппликативном f
в комбинированное аппликативное MaybeT f
:
lift :: (Functor f) => f a -> MaybeT f a
lift = MaybeT . fmap Just
Это похоже на множество шаблонов, но GHC может автоматически получить почти все из них.
Теперь вы можете легко использовать комбинированные функции:
*Main Control.Applicative> runMaybeT $ pure (*2) <*> lift [1,2,3,4]
[Just 2,Just 4,Just 6,Just 8]
*Main Control.Applicative> runMaybeT $ MaybeT (pure Nothing) <*> lift [1,2,3,4]
[Nothing,Nothing,Nothing,Nothing]
Такое поведение в Nothing может быть удивительным, но если вы думаете о списке как о представлении индетерминизма, вы, вероятно, сможете увидеть, как он может быть полезен. Если вам нужно двойное поведение возврата либо Just [a]
или Nothing
, вам просто нужен преобразованный список ListT Maybe a
.
Это не совсем то же самое, что экземпляр, который я написал для DApplicative
. Причина в из-за типов. DApplicative
преобразует f
в g
. Это возможно только тогда, когда вы знаете конкретные f
и g
. Чтобы обобщить его, результат должен сочетать поведение как f
, так и g
, как это делает реализация.
Все это работает и с Монадами. Преобразованные типы, такие как MaybeT
, предоставляются библиотеками монадных преобразователей, такими как mtl .