我注意到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
绑定实际上是伪装的功能绑定,为什么以上两个代码片段不等同?
(在此示例中,其他解决方案是使用TypeRep
创建typeOf x
,或者将变量直接绑定为顶级函数中的x :: a
。这两个选项都不是在我的真实代码中可用,并且使用它们并不回答这个问题。)
答案 0 :(得分:8)
重要的是,函数是伪装的case
表达式,而不是let
表达式。 case
匹配和let
匹配具有不同的语义。这也是您无法匹配在let
表达式中进行类型细化的GADT构造函数的原因。
区别在于case
匹配在继续之前评估scrutinee,而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)