monad和monad函数的类约束

时间:2014-03-11 00:06:07

标签: haskell monads

我正在尝试编写一个只能包含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'

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

3 个答案:

答案 0 :(得分:14)

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

这是Monad类如何定义的基本限制 - 我不知道如何在不修改它的情况下绕过它。

这也是为Monad等其他常见类型定义Set个实例的问题。

在实践中,这种限制实际上非常重要。考虑(通常)函数是{em>不 Num的实例。这意味着我们无法使用你的monad来包含一个函数!这实际上限制了ap<*>的{​​{1}})等重要操作,因为这取决于包含函数的monad:

Applicative

你的monad不会支持我们从普通monad中获得的许多常见用法和习语!这宁可限制其效用。

另外,作为旁注,通常应避免使用ap :: Monad m => m (a -> b) -> m a -> m b 。它并不真正适合fail类型类:它更像是一次历史性事故。大多数人都同意你应该一般地避免它:在处理符号时处理失败的模式匹配只是一个黑客。

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

替代

考虑到缺点,这里有一些替代品 - 替换标准Monad支持受限制的monad。

约束种类

我可以想到几种可能的选择。最现代的将利用GHC中的Monad扩展,它允许您将类型类约束作为种类来实现。 This blog post详细介绍了如何使用约束种类实现受限制的monad;一旦我读完它,我会在这里总结一下。

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

Num a

RMonad

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

它不使用{-# 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 扩展名,并且(我相信)支持旧版本的GHC。但是,我觉得它有点难看;我不确定这是最好的选择。

这是我提出的代码:

ConstraintKinds

进一步阅读

有关详细信息,请查看this SO question

奥列格有一篇关于此事的文章专门针对Set monad,这可能很有趣:"How to restrict a monad without breaking it"

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

答案 1 :(得分:1)

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

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

Monad

答案 2 :(得分:0)

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

这样可以产生理想的结果,同时更容易编写和理解。