使用类型化的孔来定义类实例

通过交互式过程,键入的孔可以更容易地定义功能。

假设你要定义一个类实例 Foo Bar(对于你的自定义 Bar 类型,以便将其与一些需要 Foo 实例的多态库函数一起使用)。你现在传统上会查找 Foo 的文档,找出你需要定义哪些方法,仔细检查它们的类型等等 - 但是对于打字的洞,你实际上可以跳过它!

首先只定义一个虚拟实例:

instance Foo Bar where

编译器现在会抱怨

Bar.hs:13:10: Warning:
No explicit implementation for
  ‘foom’ and ‘quun’
In the instance declaration for ‘Foo Bar’

好的,所以我们需要为 Bar 定义 foom。但是,什么,即使应该是什么?我们再一次懒得查看文档,只要问编译器:

instance Foo Bar where
  foom = _

在这里,我们使用了一个打字的洞作为一个简单的文档查询。编译器输出

Bar.hs:14:10:
    Found hole ‘_’ with type: Bar -> Gronk Bar
    Relevant bindings include
      foom::Bar -> Gronk Bar (bound at Foo.hs:4:28)
    In the expression: _
    In an equation for ‘foom’: foom = _
    In the instance declaration for ‘Foo Bar’

请注意编译器如何使用我们想要实例化的具体类型 Bar 填充类类型变量。这可以使签名比类文档中的多态变量更容易理解,特别是如果你正在处理更复杂的方法,例如多参数类型类。

但到底是怎么回事?在这一点上,问问 Hayoo 可能是个好主意。但是我们仍然可以在没有这个的情况下转义:作为盲目的猜测,我们假设这不仅是一个类型构造函数,而且是单值构造函数,即它可以用作以某种方式产生 Gronk a 值的函数。所以我们试试

instance Foo Bar where
  foom bar = _ Gronk

如果我们很幸运,Gronk 实际上是一个值,编译器现在会说

    Found hole ‘_’
      with type: (Int -> [(Int, b0)] -> Gronk b0) -> Gronk Bar
    Where: ‘b0’ is an ambiguous type variable

好吧,这很难看 - 首先请注意 Gronk 有两个参数,所以我们可以改进我们的尝试:

instance Foo Bar where
  foom bar = Gronk _ _

现在这很清楚了:

    Found hole ‘_’ with type: [(Int, Bar)]
    Relevant bindings include
      bar::Bar (bound at Bar.hs:14:29)
      foom::Bar -> Gronk Bar (bound at Foo.hs:15:24)
    In the second argument of ‘Gronk’, namely ‘_’
    In the expression: Gronk _ _
    In an equation for ‘foom’: foom bar = Gronk _ _

你现在可以通过例如解构 bar 值来进一步推进(然后组件将在 Relevant bindings 部分中显示类型)。通常情况下,在某种程度上,正确定义将是完全明显的,因为你看到所有可用的论点和类型都像拼图一样融合在一起。或者,你可能会看到定义不可能以及原因。

所有这些在具有交互式编译的编辑器中效果最佳,例如具有 haskell 模式的 Emacs。然后,你可以使用类型化的洞,就像 IDE 中的鼠标悬停值查询一样,用于解释的动态命令式语言,但没有所有限制。