4

我用 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

但是我遇到了重叠实例错误。

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

4

2 回答 2

5

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

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

当您编写 时type DataStore = ...,您只是在定义一个类型别名,它基本上是一个类型级别的宏。该类型的所有用途DataStore都替换为其定义。这意味着 using与直接 usingDataStore没有区别Free DataStoreF,所以当你这样做时:

instance MonadError String DataStore where { ... }

…你实际上是在这样做:

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

…这与上面定义的实例冲突。

为了避免这种情况,您应该定义 anewtype来生成一个全新的类型,该类型可以有自己的实例,与定义的实例无关Free。如果你使用GeneralizedNewtypeDeriving扩展,你可以避免很多样板文件,否则这些样板文件需要单独的newtype

{-# LANGUAGE GeneralizedNewtypeDeriving -}

data DataStoreF next = ...

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

instance MonadError String DataStore where { ... }

这应该可以避免重叠实例问题,而无需手动写出所有FunctorApplicative和实例。Monad

于 2016-08-05T23:07:25.793 回答
2

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

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

确实是重叠的,但这并不意味着它们不兼容。请注意,从某种意义上说,上述实例比您的实例“更通用”——任何与您的实例匹配的类型都将匹配这个实例。当使用OverlappingInstances扩展(或使用现代 GHC,一个{-# OVERLAP{S/PING/PABLE} #-}pragma)时,实例可能会重叠,并且将使用最具体(最不通用)的实例。

没有扩展,例如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")
于 2016-08-06T03:04:05.573 回答