现有的
Monad
类型类希望您的类型能够适用于
每个可能的类型参数。考虑
Maybe
:在
Maybe a
中,
a
根本没有受到任何限制。基本上,
您不能使用带有约束条件的Monad。
这是
Monad
类定义的一个根本性限制 - 我不知道有什么方法可以解决它而不修改它。
这对于为其他常见类型(例如
Set
)定义
Monad
实例也是一个问题。
实际上,这种限制非常重要。请考虑函数
通常不是Num
的实例。这意味着我们不能使用您的单子来包含函数! 这确实限制了一些重要操作,比如
ap
(来自
Applicative
的
<*>
),因为它取决于包含函数的单子:
ap :: Monad m => m (a -> b) -> m a -> m b
你的单子不支持我们从普通单子中期望的许多常见用法和习惯用法!这将大大限制其实用性。
另外,顺便提一下,你通常应该避免使用fail
。它并不真正符合Monad
类型类:它更像是一个历史遗留问题。大多数人都同意一般应该避免使用它:它只是处理do-notation中失败的模式匹配的一个黑客方法。
话虽如此,研究如何定义受限单子类是一个理解一些Haskell扩展和学习一些中级/高级Haskell的很好练习。
替代方案
考虑到这些缺点,这里有几个选择——标准Monad
类的替代方案,支持受限单子。
约束种类
我可以想到几个可能的替代方案。最现代的方法是利用GHC中的ConstraintKind
扩展,它使您可以将类型类约束作为种类反射。 这篇博文详细介绍了如何使用约束种类实现受限单子;我读完后,将在此概括。
基本思想很简单:使用ConstraintKind
,我们可以将我们的约束(Num a
)转换为类型。然后我们可以有一个新的Monad
类,它包含该类型作为成员(就像return
和fail
是成员一样),并允许我们用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
现有一个名为rmonad的Hackage库(用于“受限制单子”),为提供更通用的类型类。你可以使用它来编写所需的单子实例(我自己没有使用过,所以很难说)。
rmonad不使用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
延伸阅读
欲了解更多详细信息,请查看此 SO 问题。
Oleg撰写了一篇有关于Set monad的文章,可能会很有趣:“如何限制一个单子而不破坏它”。
最后,你也可以阅读几篇论文: