假设我有一个功能:
logResult :: (MonadIO m, ToJSON a) => Either MyError (Response a) -> m ()
logResult = ...
在这个函数中,如果我得到:
Right (Response a)
- 我调用 toJSON
记录结果。 Left MyError
- 我也记录下来。 MyError
已有 ToJSON
实例定义。 现在我想写一个辅助函数:
logError :: (MonadIO m) :: MyError -> m ()
logError err = logResult (Left err)
但 GHC 提示如下:
• Could not deduce (ToJSON a0) arising from a use of ‘logResult’
from the context: MonadIO m
bound by the type signature for:
logError :: forall (m :: * -> *).
MonadIO m =>
L.Logger
-> Wai.Request
-> MyError
-> m ()
...
...
The type variable ‘a0’ is ambiguous
我理解错误是因为
logResult
需要保证a
在 Response a
必须有 ToJSON
实例定义。但是在 logError
我明确通过 Left MyError
.这不应该消除歧义吗?有什么办法可以写
logError
辅助功能?PS:我已经简化了示例中的类型签名。错误消息有血淋淋的细节。
最佳答案
为什么这是一个功能?如果这个函数的行为如此干净地分成两个,那么它应该是两个函数。也就是说,您已经编写了一个整体函数,并试图将一个更简单的函数定义为使用它的实用程序。相反,编写一个简单的函数并将单体函数编写为它与另一个函数的组合。该类型非常需要它:Either a b -> c
与 (a -> c, b -> c)
同构.
-- you may need to factor out some common utility stuff, too
logError :: (MonadIO m) :: MyError -> m ()
logResponse :: (MonadIO m, ToJSON a) => Response a -> m ()
logResult :: (MonadIO m, ToJSON a) => Either MyError (Response a) -> m ()
logResult = either logError logResponse
logResult
仍然有它的用途;如果您收到 Either MyError (Response a)
来自某个图书馆,然后是 logResult
可以毫不费力地处理它。但是,否则,你不应该写 logResult (Left _)
或 logResult (Right _)
常常;本质上对待logResult . Left
和 logResult . Right
作为它们自己的函数,这使您回到实际将它们编写为单独的函数。But in
logError
I am explicitly passingLeft MyError
. Shouldn't this disambiguate?
不,不应该。问题的结束和开始是
logResult
看起来像这样:logResult :: (MonadIO m, ToJSON a) => Either MyError (Response a) -> m ()
当你调用它时,实现并不重要。类型说你需要
ToJSON a
—您需要提供ToJSON a
.而已。如果你知道你不需要ToJSON a
对于 Left
值,然后您拥有未反射(reflect)在类型中的有用信息。您应该将该信息添加到类型中,在这种情况下,这意味着将其分成两部分。 (IMO)实际上是错误的语言设计来允许您的想法,因为停止问题应该使其无法正确执行。
关于haskell - 如何不对使用更通用函数的受限函数应用实例约束?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56283441/