存在性构造函数的模式绑定

时间:2018-11-16 22:36:00

标签: haskell homoiconicity

在以以前接触过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

2 个答案:

答案 0 :(得分:10)

  

我是否错过了某些事情,或者是因为haskell不是谐音这一事实?

不。谐音是一个红色的鲱鱼:每种语言与其源文本和AST 1 都是谐音的,实际上,Haskell 是内部实现的,因为各种中间语言之间进行了一系列简化的转换

真正的问题是let...incase...of仅具有根本不同的语义,这是故意的。与case...of的模式匹配是严格的,从某种意义上说,它会强制对scrutinee进行评估,以便选择要评估的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运算符,以[| ... |]引号的形式出现,它是Template Haskell的一部分。

答案 1 :(得分:0)

我想了一下,尽管起初看起来很奇怪,但经过一番思考,我猜一个人也许可以证明这一点:

假设我举第二个(失败的)示例,经过一些按摩和价值替换后,我将其简化为:

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

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

会产生错误:

  

无法将期望的类型“ p”与实际类型“ a”匹配,因为类型变量“ a”会逃避其作用域

如果允许模式匹配,那么x的类型是什么?好吧。类型当然是Int。但是,Foo的定义表明getFoo字段的类型为任何类型,它是Show的实例。 IntShow的实例,但不是任何类型。它是特定类型。在这方面,Foo中包装的值的实际特定类型会变成“可见”(即逃脱),从而违反了我们明确保证的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位当然是一个例子..在您的情况下,getFoo值内的foo字段的类型是a,但这是GHC的类型推断所引用的特定(非存在)类型(而不是类型声明中的存在a)。.我只是想出一个带有特定特定类型的示例Int进行键入,以便更轻松,更直观地理解。