代码之家  ›  专栏  ›  技术社区  ›  zabeltech

存在主义构造器的模式绑定

  •  1
  • zabeltech  · 技术社区  · 7 年前

    当作为一个曾经接触过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
    

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

    问题是:

    我是怀念什么,还是因为哈斯凯尔不是同形人?

    我的理由是:

    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 回复  |  直到 7 年前
        1
  •  0
  •   Erick Gonzalez    7 年前

    我考虑过这一点,尽管一开始这种行为似乎很奇怪,但经过一些思考后,我想人们或许可以这样证明这一点:

    假设我拿你的第二个(失败的)例子,经过一些按摩和价值置换,我把它减少到:

    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 . 安 int 是的实例 ,但它不是任何类型的..这是一个特别的……在这方面,包装在 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 函数,我们看到没有任何东西可以说明 是任何特定类型的..(安) int 或其他方面)。在这个功能范围内,我们所拥有的一切都是原始的保证 盖福 可以是的实例的任何类型 . 包装价值的实际类型仍然是隐藏的和不可知的,因此没有违反任何类型的保证和幸福随之而来。

    附言:我忘了提到 利息 比特当然是个例子。在您的案例中,类型 盖福 字段在 foo 值的类型为 a 但这是一种特殊的(非存在主义的)类型,GHC的类型推理指的是(而不是存在主义的) 在类型声明中)。我只是举了一个例子 int 键入以便更容易和更直观地理解。

        2
  •  10
  •   Alexis King    7 年前

    我是怀念什么,还是因为哈斯凯尔不是同形人?

    不,同象性是一条红鲱鱼:每一种语言都与源文本和AST具有同象性。 事实上,哈斯克尔 内部实现为各种中间语言之间的一系列删减过程。

    真正的问题是 let...in case...of 只是有着根本不同的语义,这是有意的。模式匹配 案例… 是严格的,从某种意义上说,它强制对审查者进行评估,以便选择要评估的RHS,但在 让…进来 形式是懒惰的。从这个意义上说, 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"? .


    如果您不满意,请注意haskell 在大多数Lisper使用这个词的意义上是同形的,因为它支持类似于Lisp_ quote 运算符的形式为 [| ... |] 报价括号,是模板haskell的一部分。

    推荐文章