我正在尝试编写一个只能包含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'
有谁知道如何解决这个问题?
答案 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
类,它包含此类型作为成员(就像Monad
和return
是成员)并允许使用fail
重载约束。这就是代码的样子:
Num a
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值,如果没有则返回一些安全值。第三,编写一个组成这两个函数的函数。
这样可以产生理想的结果,同时更容易编写和理解。