如何不将实例约束应用于使用更通用函数的受限函数?

时间:2019-05-23 21:57:00

标签: haskell typeclass

假设我有一个功能:

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需要保证a中的Response a必须定义一个ToJSON实例。但是在logError中,我明确地传递了Left MyError。这不应该消除歧义吗?

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

PS:在示例中,我简化了类型签名。错误消息中包含详细信息。

1 个答案:

答案 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 . LeftlogResult . Right视为它们自己的函数,这使您回到将它们实际编写为单独的函数的方式。

  

但是在logError中,我明确传递了Left MyError。这不应该消除歧义吗?

不,不应该。问题的开头和结尾是logResult看起来像这样:

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

调用它时,只需单击一下,实现就无关紧要。类型表示您需要ToJSON a-您需要提供ToJSON a。而已。如果您知道不需要ToJSON a来获取Left值,那么您将拥有未反映在类型中的有用信息。您应该将该信息添加到类型中,在这种情况下,这意味着将其分成两个部分。 IMO(IMO)实际上是一种糟糕的语言设计,无法满足您的想法,因为停顿的问题将使它无法正确执行。