免费Monad的MonadError实例

时间:2016-08-05 22:40:02

标签: haskell typeclass free-monad overlapping-instances

我用sum数据类型创建了一个非常有用的Free Monad。这抽象了对持久性数据存储的访问:

data DataStoreF next = 
     Create    Asset                           ( String -> next)
  |  Read      String                          ( Asset  -> next)
  |  Update    Asset                           ( Bool   -> next)
  |  UpdateAll [Asset]                         ( Bool   -> next)
  |  Delete    Asset                           ( Bool   -> next)
  |  [...] -- etc. etc.
  |  Error     String

type DataStore = Free DataStoreF

我想DataStore MonadError的实例,错误消息处理为(Free (Error str))

instance MonadError String DataStore where
  throwError str = errorDS str
  catchError (Free (ErrorDS str)) f = f str
  catchError x _ = x

但我遇到了Overlapping Instances错误。

制作DataStore monad和MonadError实例的正确方法是什么?

2 个答案:

答案 0 :(得分:5)

Free类型已经为所有免费monad提供了MonadError个实例:

instance (Functor m, MonadError e m) => MonadError e (Free m) where { ... }

当您编写type DataStore = ...时,您只需定义一个类型别名,它基本上是一个类型级别的宏。 DataStore类型的所有用法都将替换为其定义。这意味着使用DataStore与直接使用Free DataStoreF无法区分,因此当您执行此操作时:

instance MonadError String DataStore where { ... }

......你实际上是这样做的:

instance MonadError String (Free DataStoreF) where { ... }

......并且与上面定义的实例冲突。

为了避免这种情况,你应该定义一个newtype来生成一个完全新鲜的类型,它可以有自己的实例,与Free上定义的实例无关。如果您使用GeneralizedNewtypeDeriving扩展名,则可以避免使用单独的newtype所需的大量样板:

{-# LANGUAGE GeneralizedNewtypeDeriving -}

data DataStoreF next = ...

newtype DataStore a = DataStore (Free DataStoreF a)
  deriving (Functor, Applicative, Monad)

instance MonadError String DataStore where { ... }

这样可以避免重叠的实例问题,而无需手动写出所有FunctorApplicativeMonad个实例。

答案 1 :(得分:2)

您的实例和库提供的实例:

instance (Functor m, MonadError e m) => MonadError e (Free m)

确实是重叠的,但这并不意味着它们是不相容的。请注意,上面的例子更为通用'从某种意义上说,任何与你的实例相匹配的类型都会匹配这个。当使用OverlappingInstances扩展名(或使用现代GHC,{-# OVERLAP{S/PING/PABLE} #-}编译指示)时,实例可能会重叠,并且将使用最具体的(至少常规)实例。

没有扩展名,例如throwError "x" :: DataStore ()给出了类型错误:

* Overlapping instances for MonadError [Char] (Free DataStoreF)
    arising from a use of `throwError'
  Matching instances:
    instance [safe] (Functor m, MonadError e m) =>
                    MonadError e (Free m)
      -- Defined in `Control.Monad.Free'
    instance [safe] MonadError String DataStore

但添加了一个pragma

instance {-# OVERLAPS #-} 
  MonadError String DataStore where

表达式throwError "x" :: DataStore () 仍然匹配两个实例,但由于一个比另一个(您编写的那个)更具体,所以它被选中:

>throwError "x" :: DataStore ()
Free (Error "x")