首页 > 解决方案 > 如何不对使用更通用函数的受限函数应用实例约束?

问题描述

假设我有一个功能:

logResult :: (MonadIO m, ToJSON a) => Either MyError (Response a) -> m ()
logResult = ...

在这个函数中,如果我得到:

  1. Right (Response a)- 我打电话toJSON记录结果。
  2. 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需要保证ainResponse a必须ToJSON定义一个实例。但在logError我明确通过Left MyError. 这不应该消除歧义吗?

有什么办法可以编写logError辅助函数吗?

PS:我已经简化了示例中的类型签名。错误消息有血淋淋的细节。

标签: haskelltypeclass

解决方案


为什么这是一个功能?如果这个函数的行为如此干净地分成两个,那么它应该两个函数。也就是说,您已经编写了一个整体函数,并试图将一个更简单的函数定义为使用它的实用程序。相反,编写一个简单的函数并将单体函数编写为它与另一个函数的组合。该类型非常需要它: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 . LeftandlogResult . Right视为它们自己的函数,这使您回到实际将它们编写为单独的函数。

但在logError我明确通过Left MyError. 这不应该消除歧义吗?

不,不应该。问题的结束和开始是logResult这样的:

logResult :: (MonadIO m, ToJSON a) => Either MyError (Response a) -> m ()

当你调用它时,实现并不重要。类型说你需要ToJSON a——你需要提供ToJSON a. 而已。如果您知道不需要ToJSON aLeft,那么您就拥有未反映在类型中的有用信息。您应该将该信息添加到类型中,在这种情况下,这意味着将其分成两部分。(IMO)实际上是错误的语言设计来允许您的想法,因为停止问题应该使其无法正确执行。


推荐阅读