首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >monad和monad函数的类约束

monad和monad函数的类约束
EN

Stack Overflow用户
提问于 2014-03-11 08:06:07
回答 3查看 322关注 0票数 8

我正在尝试编写一个新的monad,它只能包含一个数字。当它失败时,它返回0,就像当它失败时可能monad不返回任何东西一样。

这是我到目前为止所知道的:

代码语言:javascript
复制
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抱怨说

代码语言:javascript
复制
No instance for (Num a) arising from a use of `IDnum'

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

例如:

代码语言:javascript
复制
Method signature does not match class; it should be
  return :: forall a. a -> IDnum a
In the instance declaration for `Monad IDnum'

有人知道怎么解决这个问题吗?

EN

回答 3

Stack Overflow用户

发布于 2014-03-11 08:36:34

现有的Monad类型类要求您的类型适用于所有可能的类型参数。考虑一下Maybe:在Maybe a中,a根本不受约束。从根本上说,你不可能有一个带约束的Monad 。

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

这对于为其他常见类型定义Monad实例也是一个问题,比如Set

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

代码语言:javascript
复制
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类,它包含该类型作为成员(就像returnfail是成员一样),并允许使用Num a重载约束。代码如下所示:

代码语言:javascript
复制
{-# 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实例。(我自己没有用过,所以很难说。)

它不使用ConstraintKinds扩展,并且(我相信)支持旧版本的GHC。然而,我认为这有点丑陋;我不确定这是不是最好的选择。

这是我想出来的代码:

代码语言:javascript
复制
{-# 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"

最后,有几篇论文你也可以读到:

票数 14
EN

Stack Overflow用户

发布于 2014-03-12 00:52:27

这个答案将是简短的,但这里有另一个替代方案来配合Tikhon的,你可以对你的类型应用一个协密度变换,基本上得到一个免费的monad。只需使用它(在下面的代码中是IDnumM)而不是您的基类型,然后在最后将最终值转换为您的基类型(在下面的代码中,您将使用runIDnumM)。您还可以将您的基类型注入到转换后的类型中(在下面的代码中,这将是toIDnumM)。

这种方法的一个好处是它可以与标准的Monad类一起工作。

代码语言:javascript
复制
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
票数 1
EN

Stack Overflow用户

发布于 2014-04-08 06:46:23

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

这将产生所需的结果,同时更易于编写和理解。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/22313903

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档