使用Data.Functor.Compose滚动自己的Applicatives

时间:2017-09-28 22:36:44

标签: haskell

我正在玩here给出的精彩答案。我天真地期待这个工作:

{-# LANGUAGE MultiParamTypeClasses #-}

import Data.Functor.Compose
import Control.Monad

class (Functor f, Functor g) => Adjoint f g where
    counit :: f (g a) -> a
    unit   :: a -> g (f a)

instance (Adjoint f g) => Monad (Compose g f) where
    return x = Compose $ unit x
    x >>= f  = Compose . fmap counit . getCompose $ fmap (getCompose . f) x
但是,它并没有。我收到以下错误:

adjoint.hs:10:10: error:
    • Could not deduce (Applicative g)
        arising from the superclasses of an instance declaration

这里似乎正在发生什么。 GHC要求所有Monad具有Applicative实例,因此编译器会为Compose g f寻找一个实例。  Data.Functor.Compose定义了这样的实例,但它需要g(和f)为Applicative

instance (Applicative f, Applicative g) => Applicative (Compose f g) where
    pure x = Compose (pure (pure x))
    Compose f <*> Compose x = Compose ((<*>) <$> f <*> x)

但在一般情况下,Compose g f可以是Monad(从而Applicative),即使gf都不是Applicative f。通常的示例是(,) sg时,(->) sCompose g f。然后StateMonad (,) s,即使Applicative不是(始终)Adjoint

这似乎有点不理想。首先,使用Monad为两个Functor定义Applicative个实例并不是Functors会很好。但更一般地说,有两种Applicative的组合方式为Data.Functor.Compose,即使其中一个或两个都不成功。但是,目前GHC的行为方式与Applicative的设置方式相结合,使您无法实现这些用例。如果您尝试为任何Compose g f定义Data.Functor.Compose实例,GHC将会抱怨重复的实例声明。

显而易见的解决方案就是滚动您自己的Applicative版本并废弃Bool ? String? : String行。这有点直截了当,如果有点hacky。还有其他更有原则性的方法来解决这个问题吗?

1 个答案:

答案 0 :(得分:4)

通常,如果您需要其他实例,则需要新类型。应该非常简单;只需几行代码:

newtype AdjointCompose f g a = AdjointCompose { runAdjointCompose :: f (g a) }

instance (Functor f, Functor g) => Functor (AdjointCompose f g) where
    fmap = liftM

instance Adjoint f g => Applicative (AdjointCompose g f) where
    pure = return
    (<*>) = ap

instance Adjoint f g => Monad (AdjointCompose g f) where
    -- as in your proposed instance

现在你可以吃蛋糕并吃掉它:当你想要Applicative的组合时,使用Compose,当你想要Applicative时,你可以从伴随中获得,使用AdjointCompose

如果您想要Compose免费定义的其他一些实例,您可以将AdjointCompose写为Compose上的新类型,然后通过GeneralizedNewtypeDeriving获取它们。< / p>