我如何让 GHCi 向我显示不明确的类型签名?

How do I get GHCi to show me an ambiguous type signature?

如果我有一个我知道有歧义类型的表达式,有没有办法让 GHCi 实际告诉我完整的类型,这样我就可以自己看到确切的歧义,而不必拼凑它从错误消息的位一起?示例:

GHCi, version 9.0.1: https://www.haskell.org/ghc/  :? for help
ghci> default ()
ghci> :t +v show . read

<interactive>:1:1: error:
    • Ambiguous type variable ‘b0’ arising from a use of ‘show’
      prevents the constraint ‘(Show b0)’ from being solved.
      Probable fix: use a type annotation to specify what ‘b0’ should be.
      These potential instances exist:
        instance Show Ordering -- Defined in ‘GHC.Show’
        instance Show a => Show (Maybe a) -- Defined in ‘GHC.Show’
        instance Show Integer -- Defined in ‘GHC.Show’
        ...plus 23 others
        ...plus 21 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the first argument of ‘(.)’, namely ‘show’
      In the expression: show . read
      In the expression: show . read

<interactive>:1:8: error:
    • Ambiguous type variable ‘b0’ arising from a use of ‘read’
      prevents the constraint ‘(Read b0)’ from being solved.
      Probable fix: use a type annotation to specify what ‘b0’ should be.
      These potential instances exist:
        instance Read Ordering -- Defined in ‘GHC.Read’
        instance Read a => Read (Maybe a) -- Defined in ‘GHC.Read’
        instance Read Integer -- Defined in ‘GHC.Read’
        ...plus 23 others
        ...plus 10 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the second argument of ‘(.)’, namely ‘read’
      In the expression: show . read
      In the expression: show . read
ghci> 

在那个例子中,我想让 GHCi 告诉我 show . read :: (Show a, Read a) => String -> String 或类似的东西。

我不认为你可以让 GHCI 告诉你。我认为这是因为即使您打开 AllowAmbiguoustypes 以便表达式编译,GHCI 的默认规则也会将可读类型解析为 (),而不是类型参数。没有比 String -> String 更有趣的类型了。让它保持多态是没有意义的,因为这个函数的客户端没有地方可以放置类型注释来指示他们想要读取的类型。

将此与 read . show 进行对比,GHCI 会很乐意为您提供类型,因为它可以根据给定的参数推断要使用的类型。

你不会让 ghci 告诉你这种模棱两可的类型,但别担心。这个 show . read 示例相当人为,您不会 运行 在实践中使用它。你至少可以从中做出定义

readShow :: forall a. Read a => Show a => String -> String
readShow = show @a . read @a

GHCi 正在为您提供您想要的信息,只是不是以您想要的格式。

使用 -fdefer-type-errors 错误后得到 show . read :: String -> String。你很失望它不是 show . read :: (Read a, Show a) => String -> String.

麻烦的是,它有歧义的全部原因是类型变量a 没有出现在整体表达式的类型中,为了要附加的约束。约束必须在表达式的“内部”解决,与整体类型无关 String -> String.

GHC 当然可以在类型中添加一个虚假的类型变量,并且说 show . read :: (Read a, Show a) => String -> String 只是为了告诉您关于模棱两可的类型。但它 已经 告诉你关于模棱两可的类型,如果你真的阅读了错误消息,它会告诉你:

• Ambiguous type variable ‘a0’ arising from a use of ‘show’
  prevents the constraint ‘(Show a0)’ from being solved.

到底是什么问题

  Probable fix: use a type annotation to specify what ‘a0’ should be.

要修复它,您可能需要添加更多类型信息

  These potential instances exist:
    instance Show Ordering -- Defined in ‘GHC.Show’
    instance Show Integer -- Defined in ‘GHC.Show’
    instance Show a => Show (Maybe a) -- Defined in ‘GHC.Show’
    ...plus 22 others
    ...plus 21 instances involving out-of-scope type

一个“潜在”实例的列表,告诉您可以在它建议您可能需要的附加类型信息中使用哪些类型(无可否认,这在这里不是很有用)。

• In the first argument of ‘(.)’, namely ‘show’
  In the expression: show . read

正是问题所在;查明确切的子表达式(因为可以想象在同一行上可以不止一次使用 show)。

show 是代码中需要 Show 实例的确切点,并且对于应该使用什么类型来查找实例存在歧义。不是对 . 调用的 return 类型,它明确地是 String -> String。这就是 GHC 告诉您的内容(对于下一条错误消息中的 Read 约束也是如此)。

考虑这个例子:

ghci> :t length [1] + 10
length [1] + 10 :: Int

在正常的默认规则下,没关系。使用 default () 你会得到一个关于无法解决约束 Num a0 的模糊类型错误。但是看到以下类型有帮助吗?

length [1] + 10 :: (Num a) => Int

我怎么知道哪里有歧义?是 1 还是 10,还是 length 可能需要 Num 约束(它可以写成 genericLength)? GHC 实际上告诉你的是1:

• Ambiguous type variable ‘a0’ arising from the literal ‘1’
  prevents the constraint ‘(Num a0)’ from being solved

比您想要的更多信息。

你说你想“为自己找出确切的歧义,而不是必须从错误消息的位拼凑起来”,但这实际上不是你想要的。 GHC告诉你确切的歧义(既有不能解决的约束,也有需要解决的地方)。您所要求的是在 => 的右侧未出现的约束中查看带有变量的类型签名,以便您可以推断出内部一定出了什么问题导致而不是阅读错误消息,它 告诉您 是内部出了什么问题导致的。

想必你想要那个,因为它会更短,更容易识别,我当然可以理解!我认为处理该问题的正确方法只是学习 GHC 的错误消息,直到您可以像 (Read a, Show a) => String -> String.

一样轻松地“模式匹配”Ambiguous type variable ‘a0’ arising from a use of ‘read’ prevents the constraint ‘(Read a0)’ from being solved.

1 实际上 length 的 return 类型是明确的 Int,它设置了 Num 使用的约束+10Int,因此整个表达式明确键入 Int。歧义仅在于列表中的 1 类型,而不是 + 的 return 类型,因此结果中不应存在 Num 约束。