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

Haskell 如何不对使用更通用函数的受限函数应用实例约束?,haskell,typeclass,Haskell,Typeclass,假设我有一个函数: logResult::(MonadIO m,ToJSON a)=>MyError(响应a)->m() 日志结果=。。。 在此函数中,如果我得到: Right(Response a)-我调用toJSON来记录结果 左侧我的错误-我也会记录它MyError已经定义了一个ToJSON实例 现在我想写一个助手函数: logError:(MonadIO m)::MyError->m() logError err=logResult(左错误) 但GHC的抱怨大致如下: • C

假设我有一个函数:

logResult::(MonadIO m,ToJSON a)=>MyError(响应a)->m()
日志结果=。。。
在此函数中,如果我得到:

  • Right(Response a)
    -我调用
    toJSON
    来记录结果
  • 左侧我的错误
    -我也会记录它
    MyError
    已经定义了一个
    ToJSON
    实例
  • 现在我想写一个助手函数:

    logError:(MonadIO m)::MyError->m()
    logError err=logResult(左错误)
    
    但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
    helper函数


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

    为什么这是一个功能?如果此函数的行为如此清晰地拆分为两个,那么它应该是两个函数。也就是说,您已经编写了一个单片函数,并试图将一个更简单的函数定义为使用它的实用程序。相反,编写一个简单函数,并将单片函数作为它与另一个函数的组合来编写。这个类型非常需要它:
    要么a b->c
    同构于
    (a->c,b->c)

    logResult
    仍有其用途;如果您从某个库中得到一个
    MyError(Response a)
    ,那么
    logResult
    可以毫不费力地处理它。但是,否则,您不应该经常编写
    logResult(左)
    logResult(右)
    ;它本质上处理
    logResult。左
    日志结果。右键
    作为它们自己的函数,这会让您回到实际将它们作为单独的函数编写

    但是在
    logError
    中,我显式地传递了
    Left MyError
    。这不应该消除歧义吗

    不,不应该。问题的结束和开始是
    logResult
    如下所示:

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

    当您调用它时,实现并不重要。类型表示您需要
    ToJSON a
    ——您需要提供
    ToJSON a
    。就这样。如果您知道
    Left
    值不需要
    ToJSON a
    ,那么您就拥有了类型中没有反映的有用信息。您应该将该信息添加到类型中,在本例中,这意味着将其拆分为两部分。事实上(依我看),如果允许你所想的内容,那将是糟糕的语言设计,因为停顿的问题将使它不可能正确表达。

    我也无法正确表达标题。我想这表明我对这个概念/想法缺乏理解。你为什么在这里使用
    Left
    ,你不应该使用
    logError=logResult
    ?事实上,像
    logError MonadIO m::MyError a->m()
    那样编写它更有意义,这样就可以解压它,如果
    Left
    记录错误,否则什么都不做。乍一看,它看起来像是在
    where
    子句中定义的
    logError
    ?在这种情况下,很可能是由于。不。我已经有了这方面的
    logResult
    。我需要
    logError
    以防我想使用
    任何一个logError返回结果
    啊,我得到了它,通过使用
    Left err
    ,你知道类型是
    MyError a
    ,但是
    a
    是什么,不清楚。您也许可以通过强制一个类型来解决这个问题,您知道存在一个
    ToJSON
    ,比如:
    logError err=logResult(Left err::any MyError Int)
    。感谢您提供的详细答案。我现在明白了。把它分成两个函数是有意义的。
    logResult :: (MonadIO m, ToJSON a) => Either MyError (Response a) -> m ()