monad和monad函数的类约束

Ian*_*Ian 8 monads haskell

我正在尝试编写一个只能包含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
Run Code Online (Sandbox Code Playgroud)

哈斯克尔抱怨说有

No instance for (Num a) arising from a use of `IDnum'
Run Code Online (Sandbox Code Playgroud)

它建议我为每个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'
Run Code Online (Sandbox Code Playgroud)

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

Tik*_*vis 14

现有的Monad类型类期望您的类型适用于每个可能的类型参数.考虑一下Maybe:in Maybe a,a根本不受约束.基本上你不能有一个有约束的Monad.

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

这也是定义Monad其他常见类型的实例的问题,例如Set.

实际上,这种限制实际上非常重要.考虑(通常)函数不是实例Num.这意味着我们无法使用你的monad来包含一个函数!这实际上限制了像ap(<*>from Applicative)这样的重要操作,因为这取决于包含函数的monad:

ap :: Monad m => m (a -> b) -> m a -> m b
Run Code Online (Sandbox Code Playgroud)

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

另外,作为附注,您通常应该避免使用fail.它并不真正适合Monad类型类:它更像是一次历史性事故.大多数人都同意你应该一般地避免它:它只是一个黑客来处理失败的模式匹配.

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

备择方案

在头脑里的缺点,这里有标准的一对夫妇的替代品,替代Monad该类支持受限制的单子.

约束种类

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

基本思路很简单:有了ConstraintKind,我们可以将constrain(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
Run Code Online (Sandbox Code Playgroud)

RMonad

hackage上有一个名为rmonad的现有库(用于"受限monad"),它提供了更通用的类型类.您可以使用它来编写所需的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
Run Code Online (Sandbox Code Playgroud)

进一步阅读

有关更多详细信息,请查看此SO问题.

奥列格有一篇关于这个专门针对Set monad的文章,这可能很有趣:"如何限制monad而不破坏它".

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