我注意到 GHC 的 ScopedTypeVariables
能够在函数模式中绑定(bind)类型变量,但不能在 let 模式中绑定(bind)类型变量。
作为一个最小的例子,考虑类型
data Foo where Foo :: Typeable a => a -> Foo
如果我想访问 Foo 内部的类型,则以下函数无法编译:
fooType :: Foo -> TypeRep
fooType (Foo x) =
let (_ :: a) = x
in typeRep (Proxy::Proxy a)
但是使用这个技巧将类型变量绑定(bind)移动到函数调用,它可以正常工作:
fooType (Foo x) =
let helper (_ :: a) = typeRep (Proxy::Proxy a)
in helper x
既然 let
绑定(bind)实际上是变相的函数绑定(bind),为什么上面的两个代码片段不等价呢?
(在此示例中,其他解决方案是使用 typeOf x
创建 TypeRep
,或者直接将变量绑定(bind)为 x::a
在顶级函数中。这些选项在我的真实代码中都不可用,并且使用它们并不能回答问题。)
最佳答案
重要的是,函数是伪装的 case
表达式,而不是 let
表达式。 case
匹配和 let
匹配具有不同的语义。这也是您无法匹配在 let
表达式中执行类型细化的 GADT 构造函数的原因。
区别在于,case
匹配在继续之前评估审查者,而 let
匹配在堆上抛出一个 thunk,表示“在需要结果时执行此评估” 。 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)
关于haskell - 为什么 Haskell 的作用域类型变量不允许在模式绑定(bind)中绑定(bind)类型变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46077442/