假设我有一个功能:
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:在示例中,我简化了类型签名。错误消息中包含详细信息。
答案 0 :(得分:11)
为什么这是一个功能?如果此函数的行为如此清晰地分为两个,则应为两个函数。也就是说,您已经编写了一个整体函数,并试图使用它来将一个简单的函数定义为实用程序。而是编写一个简单的函数,然后将整体函数与另一个函数一起编写。该类型几乎要求它: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
视为它们自己的函数,这使您回到将它们实际编写为单独的函数的方式。
但是在
logError
中,我明确传递了Left MyError
。这不应该消除歧义吗?
不,不应该。问题的开头和结尾是logResult
看起来像这样:
logResult :: (MonadIO m, ToJSON a) => Either MyError (Response a) -> m ()
调用它时,只需单击一下,实现就无关紧要。类型表示您需要ToJSON a
-您需要提供ToJSON a
。而已。如果您知道不需要ToJSON a
来获取Left
值,那么您将拥有未反映在类型中的有用信息。您应该将该信息添加到类型中,在这种情况下,这意味着将其分成两个部分。 IMO(IMO)实际上是一种糟糕的语言设计,无法满足您的想法,因为停顿的问题将使它无法正确执行。