【问题标题】:Class contraints for monads and monad functionsmonad 和 monad 函数的类约束
【发布时间】:2014-03-11 00:06:06
【问题描述】:

我正在尝试编写一个只能包含 Num 的新 monad。当它失败时,它返回 0,就像 Maybe monad 在失败时返回 Nothing 一样。

这是我目前所拥有的:

data (Num a) => IDnum a = IDnum a

instance Monad IDnum where
  return x = IDnum x
  IDnum x >>= f  = f x
  fail :: (Num a) => String -> IDnum a
  fail _ = return 0

Haskell 抱怨说有

No instance for (Num a) arising from a use of `IDnum'

它建议我在每个 monad 函数的类型签名的上下文中添加一个 add (Num a),但我尝试了它,然后它抱怨它们需要“forall”a 工作。

例如:

Method signature does not match class; it should be
  return :: forall a. a -> IDnum a
In the instance declaration for `Monad IDnum'

有谁知道如何解决这个问题?

【问题讨论】:

标签: haskell monads


【解决方案1】:

现有的Monad 类型类希望您的类型适用于每个 可能的类型参数。考虑Maybe:在Maybe a 中,a 根本不受约束。基本上你不能有一个有约束的 Monad

这是 Monad 类定义的一个基本限制——我不知道有什么方法可以在不修改它的情况下绕过它。

这也是为其他常见类型定义Monad 实例的问题,例如Set

在实践中,这个限制实际上非常重要。考虑(通常)函数是Num实例。这意味着我们不能使用你的 monad 来包含一个函数!这确实限制了像 ap<*> 来自 Applicative)这样的重要操作,因为这取决于包含函数的 monad:

ap :: Monad m => m (a -> b) -> m a -> m b

您的 monad 将不支持我们从普通 monad 中获得的许多常见用途和习语!这反而会限制它的实用性。

另外,作为旁注,您通常应该避免使用fail。它并不真正适合Monad 类型类:它更像是一个历史性事故。大多数人都同意你应该避免它:它只是一种在 do-notation 中处理失败的模式匹配的技巧。

也就是说,看看如何定义一个受限的 monad 类对于理解一些 Haskell 扩展和学习一些中级/高级 Haskell 是一个很好的练习。

替代方案

考虑到缺点,这里有几个替代方案——替换标准的Monad 类,确实支持受限单子。

约束种类

我能想到几个可能的替代方案。最现代的方法是利用 GHC 中的 ConstraintKind 扩展,它允许您将类型类约束具体化为种类。 This blog post 详细介绍了如何使用约束类型来实现受限的 monad;看完了,在这里总结一下。

基本思想很简单:使用ConstraintKind,我们可以将约束(Num a)转换为类型。然后我们可以有一个新的Monad 类,它包含这个类型作为成员(就像returnfail 是成员一样)并允许使用Num a 重载约束。代码是这样的:

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TypeFamilies    #-}

module Main where

import Prelude hiding (Monad (..))

import GHC.Exts

class Monad m where
  type Restriction m a :: Constraint
  type Restriction m a = ()

  return :: Restriction m a => a -> m a
  (>>=)  :: Restriction m a => m a -> (a -> m b) -> m b
  fail   :: Restriction m a => String -> m a

data IDnum a = IDnum a

instance Monad IDnum where
  type Restriction IDnum a = Num a
  return        = IDnum
  IDnum x >>= f = f x
  fail _        = return 0

RMonad

现有的 hackage 库名为 rmonad(用于“受限制的 monad”),它提供了更通用的类型类。您可能可以使用它来编写您想要的 monad 实例。 (我自己没用过,所以有点不好说。)

它不使用ConstraintKinds 扩展并且(我相信)支持旧版本的 GHC。但是,我认为这有点难看;我不确定这是否是最佳选择。

这是我想出的代码:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}

import Prelude hiding (Monad (..))

import Control.RMonad
import Data.Suitable

data IDnum a = IDnum a

data instance Constraints IDnum a = Num a => IDnumConstraints
instance Num a => Suitable IDnum a where
  constraints = IDnumConstraints

instance RMonad IDnum where
  return        = IDnum
  IDnum x >>= f = f x
  fail _        = withResConstraints $ \ IDnumConstraints -> return 0

进一步阅读

更多详情请关注this SO question

Oleg 有一篇专门与 Set monad 相关的文章,这可能很有趣:"How to restrict a monad without breaking it"

最后,您还可以阅读几篇论文:

【讨论】:

  • a basically just more modern version of rmonad 确实使用了约束类型;虽然看起来并不完全成熟。还有我的constrained-categories project,它可以处理这些限制性的东西等等,但更复杂(而且它还不够稳定,无法使用)。
  • 看起来您的 Constraint Kinds 示例缺少另一个限制:(>>=) 的约束应该是 (Restriction m a, Restriction m b) => ...
【解决方案2】:

这个答案会很简短,但这是与 Tikhon 一起使用的另一种选择。您可以对您的类型应用 codensity 转换,基本上可以获得一个免费的 monad。只需使用它(在下面的代码中它是IDnumM)而不是您的基本类型,然后在最后将最终值转换为您的基本类型(在下面的代码中,您将使用runIDnumM)。您还可以将基本类型注入转换后的类型(在下面的代码中,即toIDnumM)。

这种方法的一个好处是它适用于标准的Monad 类。

data Num a => IDnum a = IDnum a

newtype IDnumM a = IDnumM { unIDnumM :: forall r. (a -> IDnum r) -> IDnum r }

runIDnumM :: Num a => IDnumM a -> IDnum a
runIDnumM (IDnumM n) = n IDnum

toIDnumM :: Num a => IDnum a -> IDnumM a
toIDnumM (IDnum x) = IDnumM $ \k -> k x

instance Monad IDnumM where
  return x = IDnumM $ \k -> k x
  IDnumM m >>= f = IDnumM $ \k -> m $ \x -> f x `unIDnumM` k

【讨论】:

    【解决方案3】:

    有一种更简单的方法可以做到这一点。一个人可以使用多种功能。首先,在 Maybe monad 中写一个。 Maybe monad 在失败时返回 Nothing。其次,编写一个函数,如果不是 Nothing,则返回 Just 值;如果 Nothing,则返回某个安全值。第三,编写一个组合这两个函数的函数。

    这会产生预期的结果,同时更容易编写和理解。

    【讨论】:

      猜你喜欢
      • 2023-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多