添加未使用的实例可修复类型错误

Jam*_*pel 9 haskell typeclass ghc

考虑以下代码:

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}

module Foo where

class Foo a

class SomeClass a
instance {-# OVERLAPPABLE #-} (Foo a) => SomeClass a

bar :: (SomeClass a) => a -> Int
bar = const 0

foo :: (SomeClass a) => a -> Int
foo t = let x = bar t in x
Run Code Online (Sandbox Code Playgroud)

在这里,foo调用bar,并且应该能够SomeClass在其上下文中使用约束.相反,GHC假定它必须使用Foo a => SomeClass a实例:

Foo.hs:16:17:
    Could not deduce (Foo a) arising from a use of ‘bar’
    from the context (SomeClass a)
      bound by the type signature for foo :: SomeClass a => a -> Int
      at Foo.hs:15:8-32
    Possible fix:
      add (Foo a) to the context of
        the inferred type of x :: Int
        or the type signature for foo :: SomeClass a => a -> Int
    In the expression: bar t
    In an equation for ‘x’: x = bar t
    In the expression: let x = bar t in x
Run Code Online (Sandbox Code Playgroud)

有两种方法可以解决这个问题:

  1. foo,let x = bar t in x 改为bar t
  2. 将该行添加instance SomeClass Int到我的程序中

这里发生了什么?为什么会出现此问题,为什么这些修复工作正常?


当然,这个例子是由我的实际问题简化的.我在Cubix多语言转换框架工作期间遇到过这个问题(arxiv.org/pdf/1707.04600).

我的实际问题涉及一个带约束的函数InjF f IdentL FunctionExpL("f是一种语言,其中标识符可用于表示函数调用中的函数").我有一个(可重叠的)实例(FunctionIdent :<: f) => InjF f IdentL FunctionExpL,这是typechecker抓住的,给我一个虚假的Could not deduce FunctionIdent :<: f错误.

即使在InjF Foo IdentL FunctionExpL特定的范围内存在实例时,此错误仍然存​​在Foo,因此左下角的答案预测其他实例应该解决问题,这不是完整的故事.

lef*_*out 3

这样做的原因是编译器试图x尽可能通用:它希望它是这样的(SomeClass a) => Int(请注意,如果您自己写出来,这将是一个不明确的类型)。防止这种奇怪的本地类型的一种方法是启用-XMonoLocalBinds,但我不会真正推荐它。

现在,编译器继续进行类型检查。到那时,instance SomeClass范围内就只有一个,即 catch-all (Foo a) => SomeClass a,因此不存在歧义。(原则上,Haskell 完全禁止实例解析中的歧义;OVERLAPPABLE颠覆了这一点,但只有在实际需要时才会采取行动,例如当您有额外的instance SomeClass Int。)因此,编译器立即提交该实例,从而消除了(SomeClass a)类型 -检查c. 对于您实际上希望用实例约束替换类约束的情况,这样做是必要的。在给定的示例中,这可能看起来没什么用,但当您有以下形式的实例时,它实际上至关重要

instance Bar a => SomeClass (Maybe a)
Run Code Online (Sandbox Code Playgroud)

...这比您的代码更合理,因为它不能表示为超类约束,并且完全可以没有任何重叠。如果编译器没有减少Bar a在这些情况下解析的约束,它永远无法真正解析该Maybe类型。

结论:避免实例重叠;如果可能的话,通过超类声明来表达类关系,否则将约束具体化为 GADT(这很尴尬,但可以让您精确控制要使用哪个约束)。