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

时间:2017-09-06 14:09:07

标签: haskell ghc

我注意到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。这两个选项都不是在我的真实代码中可用,并且使用它们并不回答这个问题。)

1 个答案:

答案 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)