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



假设我有一个函数:

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需要确保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 . LeftlogResult . Right视为其自己的功能,这会导致您将它们实际写为单独的功能。

但是在logError中,我明确通过了Left MyError。这不应该放弃吗?

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

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

当您调用它时,实现无关紧要。该类型说您需要ToJSON a - 您需要提供ToJSON a。就是这样。如果您知道Left值不需要ToJSON a,那么您就拥有没有反映在类型中的有用信息。您应该将该信息添加到类型中,在这种情况下,这意味着将其分成两部分。它(IMO(实际上是不好的语言设计,可以允许您想到的内容,因为停止问题应该使得无法正确执行。

最新更新