Haskell 使用Data.Functor.Compose滚动您自己的应用程序

Haskell 使用Data.Functor.Compose滚动您自己的应用程序,haskell,Haskell,我在胡思乱想给出的精彩答案。我天真地以为这会奏效: {-# 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

我在胡思乱想给出的精彩答案。我天真地以为这会奏效:

{-# 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都有一个应用程序实例,因此编译器会为Compose g f寻找一个实例。 定义这样一个实例,但它要求g和f是可应用的:

但在一般情况下,组合g f可以是单子,因此即使g和f不同时适用,也是适用的。通常的例子是当f是,s,g是->s时。那么composegf就是状态单子,尽管s并不总是适用的

这似乎有点不太理想。首先,最好使用伴随为两个不同时适用的函子定义Monad实例。不过,更一般地说,有一些方法可以使两个函子的组合适用,即使其中一个或两个函子都不适用。但是,目前GHC的行为方式,加上Data.Functor.Compose的设置方式,使您无法实现这些用例。如果您试图为任何Compose g f定义应用程序实例,GHC将抱怨重复的实例声明


显而易见的解决方案是只使用您自己的Data.Functor.Compose版本,而应用程序行已废弃。这很直截了当,如果有点老套的话。还有其他更具原则性的方法来解决这个问题吗?

通常,如果您需要不同的实例,您需要一种新的类型。应该相当直截了当;只需几行代码:

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
现在,您也可以吃蛋糕了:当您想要应用程序的合成时,请使用Compose;当您想要从Adjuncess获得的应用程序时,请使用AdjunctCompose


如果您想要免费使用Compose定义的其他一些实例,您可以将AdjointCompose作为新类型写入Compose,而不是通过GeneralizedNewtypeDeriving获得它们。

我认为您显而易见的解决方案是正确的。当使用类型类时,我们需要将类型的概念从集合式扩展到代数结构式。Compose g f在某种程度上已经是一个应用程序,您建议以另一种方式使其成为一个应用程序。在这种思维方式下,它现在是一种不同的类型-很像我们使用Sum Int来表示,我们的意思是取一个幺半群,它可以相加,而不是其他几种可能性。你能说出hacky是什么意思吗?为什么您不喜欢roll your own Compose解决方案?问题可能是我对类型类参数有误解。我一直在阅读实例Applicative f,Applicative g=>Applicative Compose f g,因为只要f和g是Applicative的,Compose f g也是。所以,如果不知道f或g是否适用,GHC不应该得出合成f或g适用的结论。很明显,事情不是这样的!但这似乎是明智的行为,尽管可能会给下游带来不良后果。所以,也许我对自己编写代码的抵制只是因为我对类型类参数的幼稚理解太固执了。@SimonC是的。可以肯定地想象一个具有这种行为的类型类系统,语义上不会出错。问题是,然后找到一个实例需要回溯搜索,委员会希望避免这两种情况,以避免强制执行复杂的实现记住,在当时,这是存在的第一个类型类系统,没有人知道它会有多复杂!为了避免用户在开发过程中发现长时间的实例搜索会导致大量的编译时间,我认为为了提高效率,Functor实例应该是通常的派生实例。
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