【问题标题】:Type error while trying to implement the (>>=) function in order to create a custom monad transformer尝试实现 (>>=) 函数以创建自定义 monad 转换器时键入错误
【发布时间】:2010-04-18 10:26:28
【问题描述】:

我正在尝试为未来的项目创建一个 monad 转换器,但不幸的是,我的 Monad 类型类 (>>=) 函数的实现不起作用。

首先,这里是底层 monad 的实现:

newtype Runtime a = R { 
  unR :: State EInfo a
} deriving (Monad)

这里,Monad 类型类的实现由 GHC 自动完成(使用 GeneralizedNewtypeDeriving 语言编译指示)。 monad 转换器是这样定义的:

newtype RuntimeT m a = RuntimeT {
  runRuntimeT :: m (Runtime a)
} 

问题来自我实例化 Monad 类型类的 (>>=) 函数的方式:

instance (Monad m) => Monad (RuntimeT m) where
    return a = RuntimeT $ (return . return) a
    x >>= f =  runRuntimeT x >>= id >>= f

在我看来,第一个 >>= 在底层 m monad 中运行。因此,runRuntimeT x >>= 返回一个Runtime a 类型的值(对吗?)。然后,下面的代码id >>= 应该返回一个a 类型的值。该值被传递给f :: (Monad m) => a -> RuntimeT m b 类型的函数f。

类型问题来了:f 函数的类型与 (>>=) 函数所需的类型不匹配。我能让这个连贯吗?我知道为什么这不起作用,但我无法将它变成实用的东西。

编辑:错误信息:

Core.hs:34:4:
    Occurs check: cannot construct the infinite type: m = RuntimeT m
    When generalising the type(s) for `>>='
    In the instance declaration for `Monad (RuntimeT m)'
Failed, modules loaded: none.

感谢您的帮助,并毫不犹豫地纠正我信息中的任何缺陷,
查理 P.

【问题讨论】:

  • 请向我们展示您收到的错误消息。

标签: haskell monad-transformers monads


【解决方案1】:

通常的StateT s m monad 将a 发送到s -> m (a, s),但您正在使用m (s -> (a, s))。我不认为后者形成了一般s 的单子。你确定不只是想使用StateT?


这就是为什么我不认为 a → m (s -> (a, s)) 是 monad:要编写 >>=,我需要一个接受类型参数的函数

m (s -> (a, s))
a -> m (s -> (b, s))

并返回一个类型的值

m (s -> (b, s))

结果的“效果”(即fmap (const ()))必须是第一个参数的效果,因为我们无法将a 传递给第二个参数。由于m 仅出现在结果类型的外部,因此我们根本无法将第二个参数用于任何内容——将无法摆脱它引入的m。

【讨论】:

  • 想象一下我有一个函数在运行时单子中运行。这个 monad 是完全不透明的,用户不应该知道它实际上是一个带有糖的 State monad。 RuntimeT monad 转换器背后的想法是能够透明地使用在 Runtime monad 中运行的函数,比如在 IO monad 中运行的函数。这就是为什么我不能使用 StateT monad 变压器来代替我的自定义变压器的原因。
  • 感谢您指出我的 monad 中不合逻辑的部分,我将使用您和 Alasdair 所述的方法,结合身份 monad。
【解决方案2】:

继续 Reid Barton 所说的 StateT - 以下是使用 StateT 定义 RuntimeT 的方法。 Runtime monad 然后可以使用 identity monad 简单地定义。

newtype RuntimeT m a = R { 
  unR :: StateT EInfo m a
}

type Runtime = RuntimeT Identity

instance Monad m => Monad (RuntimeT m) where
    return = R . return

    (R m) >>= f = R (m >>= unR . f)

【讨论】:

  • 我不知道 Identity monad 的存在,看起来这就是我想要得到的。然而,正如我在评论 Reid Barton 的帖子时所说,创建这个新的 monad 和 monad 转换器背后的想法是对用户隐藏 State monad。我怎样才能使用你在消息中所说的来实现这一点?
  • @CharlieP:问题在于 StateT 将底层 monad 夹在 s -> 和 (a, s) 之间,而您尝试的转换器未能做到这一点。如果您愿意,您也可以选择为 Alasdair 提供的新类型派生 Monad(甚至 MonadState EInfo),但如果不使用 StateT 或手动编写等效的代码,您将无法将您的 monad 用作转换器。扩展 StateT 的定义会产生 newtype RuntimeT m a = RuntimeT { unRuntimeT :: EInfo -> m (a, EInfo) } 但您的定义会产生 newtype RuntimeT m a = RuntimeT { unRuntimeT :: m (EInfo -> (a, EInfo)) }
  • 查理,你通常会使用 Haskell 的模块系统来隐藏 RuntimeT 的实现细节。如果您不导出unR 或R,则用户永远不需要知道您使用了StateT。例如,您可能需要与RuntimeT 等效的runStateT - 可以实现为runRuntimeT = runStateT . unR。注意它的类型是RuntimeT m a -> EInfo -> m (a, EInfo)。 StateT 不会出现在任何地方,使用此功能的人可以在不知道StateT 存在的情况下使用它。可以为 get/put 编写类似的函数。希望对您有所帮助。
猜你喜欢
  • 1970-01-01
  • 2019-11-09
  • 2017-06-02
  • 2017-10-27
  • 1970-01-01
  • 1970-01-01
  • 2019-10-22
  • 1970-01-01
  • 2021-05-11
相关资源
最近更新 更多