免费Monad的MonadError实例

Joh*_*ler 3 haskell typeclass overlapping-instances free-monad

我用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
Run Code Online (Sandbox Code Playgroud)

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

instance MonadError String DataStore where
  throwError str = errorDS str
  catchError (Free (ErrorDS str)) f = f str
  catchError x _ = x
Run Code Online (Sandbox Code Playgroud)

但我遇到了Overlapping Instances错误.

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

Ale*_*ing 5

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

instance (Functor m, MonadError e m) => MonadError e (Free m) where { ... }
Run Code Online (Sandbox Code Playgroud)

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

instance MonadError String DataStore where { ... }
Run Code Online (Sandbox Code Playgroud)

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

instance MonadError String (Free DataStoreF) where { ... }
Run Code Online (Sandbox Code Playgroud)

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

为了避免这种情况,你应该定义一个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 { ... }
Run Code Online (Sandbox Code Playgroud)

这应该避免重叠实例的问题,而不需要写出所有Functor,ApplicativeMonad手动实例.