存在构造函数的模式绑定

Pattern bindings for existential constructors

作为一名以前接触过 Lisp 的程序员,在写作 Haskell 时,我注意到一些奇怪的事情,但我未能理解。

编译正常:

{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ExistentialQuantification #-}
data Foo = forall a. Show a => Foo { getFoo :: a }

showfoo :: Foo -> String
showfoo Foo{getFoo} = do
  show getFoo

而这失败了:

{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ExistentialQuantification #-}
data Foo = forall a. Show a => Foo { getFoo :: a }

showfoo :: Foo -> String
showfoo foo = do
  let Foo{getFoo} = foo
  show getFoo

对我来说,第二个片段失败的原因并不明显。

问题是:

我是否遗漏了某些东西或由于 haskell 不是谐音这一事实而导致了这种行为?

我的推理是,鉴于:

  1. Haskell 需要将记录模式匹配作为编译器扩展来实现,因为它选择使用语法而不是数据。

  2. 函数头或 let 子句中的匹配是两种特殊情况。

很难理解这些特殊情况,因为它们既不能在语言本身中实现也不能直接查找。

因此,无法保证整个语言的行为一致。根据示例,尤其是与其他编译器扩展一起使用。

ps: 编译器错误:

error:
    • My brain just exploded
      I can't handle pattern bindings for existential or GADT data constructors.
      Instead, use a case-expression, or do-notation, to unpack the constructor.
    • In the pattern: Foo {getFoo}
      In a pattern binding: Foo {getFoo} = foo
      In the expression:
        do { let Foo {getFoo} = foo;
             show getFoo }

编辑: 不同的编译器版本针对同一问题给出此错误

* Couldn't match expected type `p' with actual type `a'
    because type variable `a' would escape its scope
  This (rigid, skolem) type variable is bound by
    a pattern with constructor: Foo :: forall a. Show a => a -> Foo

Do I miss something or stems this behaviour from the fact that haskell is not homoiconic?

没有。同象性是一个转移注意力的问题:每种语言都与其源文本及其 AST1 同象,实际上,Haskell 在内部实现的作为各种中间语言之间的一系列脱糖过程。

真正的问题是 let...incase...of 只是有根本不同的语义,这是故意的。使用 case...of 的模式匹配是严格的,因为它强制对受检者进行评估以选择评估哪个 RHS,但 let...in 形式的模式绑定是惰性的。从这个意义上说,let p = e1 in e2 实际上与 case e1 of ~p -> e2 最相似(请注意使用 ~ 的惰性模式匹配!),它会产生类似但不同的错误消息:

ghci> case undefined of { ~Foo{getFoo} -> show getFoo }

<interactive>:5:22: error:
    • An existential or GADT data constructor cannot be used
        inside a lazy (~) pattern
    • In the pattern: Foo {getFoo}
      In the pattern: ~Foo {getFoo}
      In a case alternative: ~Foo {getFoo} -> show getFoo

这在 Odd ghc error message, "My brain just exploded"? 的回答中有更详细的解释。


1如果这不满足你,请注意 Haskell 谐音感觉大多数 Lispers 使用这个词,因为它以 [| ... |] 引号括号的形式支持 Lisp 的 quote 运算符的模拟,这是模板 Haskell.[ 的一部分=22=]

我考虑了一下,虽然一开始这种行为看起来很奇怪,但经过一番思考后我想也许有人可以这样证明它:

假设我以您的第二个(失败的)示例为例,经过一些修改和值替换后,我将其简化为:

data Foo = forall a. Show a => Foo { getFoo :: a }

main::IO()
main = do
    let Foo x = Foo (5::Int)
    putStrLn $ show x

产生错误:

Couldn't match expected type ‘p’ with actual type ‘a’ because type variable ‘a’ would escape its scope

如果允许模式匹配,x 的类型是什么?嗯..类型当然是Int。然而,Foo 的定义表明 getFoo 字段的类型是 任何类型 ,它是 Show 的一个实例。 IntShow 的一个实例,但它不是任何类型。它是一个特定的类型。在这方面,包含在 Foo 中的值的实际特定类型将变为 "visible"(即转义),因此违反了我们对 forall a . Show a =>...

的明确保证

如果我们现在看一下通过在函数声明中使用模式匹配来工作的代码版本:

data Foo = forall a . Show a => Foo { getFoo :: !a }

unfoo :: Foo -> String
unfoo Foo{..} = show getFoo

main :: IO ()
main = do
    putStrLn . unfoo $ Foo (5::Int)

查看 unfoo 函数,我们发现没有任何内容表明 Foo 内部的类型是任何特定类型..(Int 或其他)。 . 在该函数的范围内,我们所拥有的只是 getFoo 可以是任何类型的原始保证,它是 Show 的实例。包装值的实际类型保持隐藏和不可知,因此不会违反任何类型保证,幸福随之而来。

PS:我忘了提到 Int 位当然是一个例子..在你的例子中,[=27 中的 getFoo 字段的类型=] 值是 a 类型,但这是 GHC 的类型推断所指的特定(非存在)类型(而不是类型声明中的存在 a)。我刚想出来使用特定 Int 类型的示例,以便更容易和更直观地理解。