我正在尝试编写一个新的monad,它只能包含一个数字。当它失败时,它返回0,就像当它失败时可能monad不返回任何东西一样。
这是我到目前为止所知道的:
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 0Haskell抱怨说
No instance for (Num a) arising from a use of `IDnum'它建议我在每个monad函数的类型签名的上下文中添加一个add (Num a),但我尝试了一下,然后它报告说它们需要“为所有”a工作。
例如:
Method signature does not match class; it should be
return :: forall a. a -> IDnum a
In the instance declaration for `Monad IDnum'有人知道怎么解决这个问题吗?
发布于 2014-03-11 08:36:34
现有的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来说是一个很好的练习。
替代方案
考虑到缺点,这里有几个替代方案--支持受限monads的标准Monad类的替代品。
约束种类
我能想到几个可能的替代方案。最现代的方法是利用GHC中的ConstraintKind扩展,它允许您将类型类约束具体化为种类。This blog post详细介绍了如何使用约束类型实现受限制的monad;一旦我读完它,我将在这里总结它。
基本思想很简单:使用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 0RMonad
在hackage上有一个现成的类库,叫做rmonad (代表“受限单项”),它提供了一个更通用的类型库。你可以用它来编写你想要的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。
奥列格有一篇关于这方面的文章,特别与Set monad有关,这可能很有趣:"How to restrict a monad without breaking it"。
最后,有几篇论文你也可以读到:
发布于 2014-03-12 00:52:27
这个答案将是简短的,但这里有另一个替代方案来配合Tikhon的,你可以对你的类型应用一个协密度变换,基本上得到一个免费的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发布于 2014-04-08 06:46:23
有一种更简单的方法可以做到这一点。一个人可以使用多个函数。首先,在可能的monad中编写一个。在失败时,可能的monad不会返回任何内容。其次,编写一个函数,如果不是Nothing,则返回Just值,如果没有,则返回某个安全值。第三,编写一个由这两个函数组成的函数。
这将产生所需的结果,同时更易于编写和理解。
https://stackoverflow.com/questions/22313903
复制相似问题