简体   繁体   English

免费Monad的MonadError实例

[英]MonadError instance for a Free Monad

I have created a very useful Free Monad out of a sum data type. 我用sum数据类型创建了一个非常有用的Free Monad。 That abstracts access to a persistent data store: 这抽象了对持久性数据存储的访问:

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

I would like to make DataStore an instance of MonadError with the error message handled as (Free (Error str)) : 我想使DataStore成为MonadError一个实例, MonadError错误消息处理为(Free (Error str))

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

But I am running into Overlapping Instances errors. 但我遇到了Overlapping Instances错误。

What is the proper way to make the DataStore monad and instance of MonadError ? 制作DataStore monad和MonadError实例的正确方法是什么?

The Free type already provides a MonadError instance for all free monads: Free类型已经为所有免费monad提供了MonadError实例:

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

When you write type DataStore = ... , you are simply defining a type alias , which is basically a type-level macro. 当你写type DataStore = ... ,你只是定义一个类型别名 ,它基本上是一个类型级别的宏。 All uses of the DataStore type are replaced with its definition. DataStore类型的所有用法都将替换为其定义。 This means that using DataStore is indistinguishable from using Free DataStoreF directly, so when you do this: 这意味着使用DataStore与直接使用Free DataStoreF无法区分,因此当您执行此操作时:

instance MonadError String DataStore where { ... }

…you are actually doing this: ......你实际上是这样做的:

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

…and that conflicts with the instance defined above. ......并且与上面定义的实例冲突。

To circumvent that, you should define a newtype to produce an entirely fresh type that can have its own instances on it, unrelated to the ones defined on Free . 为了避免这种情况,你应该定义一个newtype来生成一个完全新鲜的类型,它可以有自己的实例,与Free定义的实例无关。 If you use the GeneralizedNewtypeDeriving extension, you can avoid a lot of the boilerplate that would otherwise be required by a separate newtype : 如果使用GeneralizedNewtypeDeriving扩展,则可以避免使用单独的newtype所需的许多样板:

{-# LANGUAGE GeneralizedNewtypeDeriving -}

data DataStoreF next = ...

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

instance MonadError String DataStore where { ... }

This should avoid the overlapping instance problem without the need to write out all the Functor , Applicative , and Monad instances manually. 这应避免重叠实例问题,而无需手动写出所有FunctorApplicativeMonad实例。

Your instance and the instance given by the library: 您的实例和库提供的实例:

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

are indeed overlapping, but this does not mean that they are incompatible. 确实是重叠的,但这并不意味着它们是不相容的。 Note that the above instance is 'more general' in a sense than yours - any type which would match your instance would match this one. 请注意,上述实例在某种意义上比您的更“一般” - 任何与您的实例匹配的类型都会匹配此实例。 When one uses the OverlappingInstances extension (or with modern GHC, an {-# OVERLAP{S/PING/PABLE} #-} pragma), instances may overlap, and the most specific ( least general) instance will be used. 当使用OverlappingInstances扩展(或使用现代GHC, {-# OVERLAP{S/PING/PABLE} #-}编译指示)时,实例可能会重叠,并且将使用最具体( 最不通用)的实例。

Without the extension, eg throwError "x" :: DataStore () gives the type error: 没有扩展名,例如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

but with the addition of a pragma 但是增加了一个pragma

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

the expression throwError "x" :: DataStore () still matches both instances, but since one is more specific than the other (the one you wrote) it is selected: 表达式throwError "x" :: DataStore () 仍然匹配两个实例,但由于一个比另一个(您编写的那个)更具体,因此选择它:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM