为什么 Haskell 的作用域类型变量不允许在模式绑定中绑定类型变量?

Why do Haskell's scoped type variables not allow binding of type variables in pattern bindings?

我注意到 GHC 的 ScopedTypeVariables 能够在函数模式中绑定类型变量,但不能绑定模式。

作为一个最小的例子,考虑类型

data Foo where Foo :: Typeable a  => a -> Foo

如果我想访问 Foo 中的类型,则以下函数无法编译:

fooType :: Foo -> TypeRep
fooType (Foo x) =
    let (_ :: a) = x
    in typeRep (Proxy::Proxy a)

但是使用这个技巧将类型变量绑定移动到函数调用,它可以正常工作:

fooType (Foo x) =
    let helper (_ :: a) = typeRep (Proxy::Proxy a)
    in helper x

既然let绑定实际上是变相的函数绑定,为什么上面两个代码片段不等价呢?

(在本例中,其他解决方案是使用 typeOf x 创建 TypeRep,或者在顶级函数中将变量直接绑定为 x :: a。这些都不是选项在我的真实代码中可用,使用它们并不能回答问题。)

重要的是,函数是 case 伪装的表达式,而不是 let 表达式。 case 匹配和 let 匹配具有不同的语义。这也是为什么您无法匹配在 let 表达式中进行类型细化的 GADT 构造函数的原因。

不同之处在于 case 匹配在继续之前评估检查者,而 let 匹配将一个 thunk 扔到堆上,上面写着 "do this evaluation when the result is demanded"。 GHC 不知道如何在懒惰可能与它们交互的所有潜在方式中保留局部范围的类型(如 a 在你的例子中),所以它只是不尝试。如果涉及局部范围的类型,请使用 case 表达式,这样惰性就不会成为问题。

至于您的代码,ScopedTypeVariables 实际上为您提供了一个更简洁的选项:

{-# Language ScopedTypeVariables, GADTs #-}

import Data.Typeable
import Data.Proxy

data Foo where
    Foo :: Typeable a => a -> Foo

fooType :: Foo -> TypeRep
fooType (Foo (x :: a)) = typeRep (Proxy :: Proxy a)