如何不对使用更通用函数的受限函数应用实例约束?
How to not apply instance constraints on a restricted function which uses a more generic function?
假设我有一个函数:
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
需要保证 Response a
中的 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 passing Left MyError
. Shouldn't this disambiguate?
不,不应该。问题的结尾和开头是 logResult
看起来像这样:
logResult :: (MonadIO m, ToJSON a) => Either MyError (Response a) -> m ()
当你调用它时,实现并不重要。类型说你需要 ToJSON a
——你需要提供 ToJSON a
。而已。如果您知道 Left
值不需要 ToJSON a
,那么您就拥有了类型中未反映的有用信息。您应该将该信息添加到类型中,在这种情况下,这意味着将其分成两部分。允许您的想法(IMO)实际上是糟糕的语言设计,因为停止问题应该使它无法正确执行。
假设我有一个函数:
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
需要保证 Response a
中的 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
。而已。如果您知道 Left
值不需要 ToJSON a
,那么您就拥有了类型中未反映的有用信息。您应该将该信息添加到类型中,在这种情况下,这意味着将其分成两部分。允许您的想法(IMO)实际上是糟糕的语言设计,因为停止问题应该使它无法正确执行。