免费 Monad 的 MonadError 实例
MonadError instance for a Free Monad
我用求和数据类型创建了一个非常有用的 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
monad 和 MonadError
实例的正确方法是什么?
Free
类型已经为 所有 个免费单子提供了一个 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 { ... }
这应该可以避免实例重叠问题,而无需手动写出所有 Functor
、Applicative
和 Monad
实例。
你的实例和图书馆给出的实例:
instance (Functor m, MonadError e m) => MonadError e (Free m)
确实有重叠,但这并不意味着它们不兼容。请注意,上面的实例在某种意义上比您的实例 'more general' - 任何与您的实例匹配的类型都将与该实例匹配。当使用 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
但添加了编译指示
instance {-# OVERLAPS #-}
MonadError String DataStore where
表达式 throwError "x" :: DataStore ()
仍然 匹配两个实例,但由于一个比另一个(你写的那个)更具体,它被选中:
>throwError "x" :: DataStore ()
Free (Error "x")
我用求和数据类型创建了一个非常有用的 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
monad 和 MonadError
实例的正确方法是什么?
Free
类型已经为 所有 个免费单子提供了一个 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 { ... }
这应该可以避免实例重叠问题,而无需手动写出所有 Functor
、Applicative
和 Monad
实例。
你的实例和图书馆给出的实例:
instance (Functor m, MonadError e m) => MonadError e (Free m)
确实有重叠,但这并不意味着它们不兼容。请注意,上面的实例在某种意义上比您的实例 'more general' - 任何与您的实例匹配的类型都将与该实例匹配。当使用 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
但添加了编译指示
instance {-# OVERLAPS #-}
MonadError String DataStore where
表达式 throwError "x" :: DataStore ()
仍然 匹配两个实例,但由于一个比另一个(你写的那个)更具体,它被选中:
>throwError "x" :: DataStore ()
Free (Error "x")