为什么这个在 where 子句中使用作用域类型变量的函数不进行类型检查?
Why does this function that uses a scoped type variable in a where clause not typecheck?
我有一个函数,其值在 where
子句中定义,我想给它一个显式类型注释。注释需要使用顶级函数中的类型变量,因此我的理解是我需要使用 ScopedTypeVariables
。这是问题的最小化:
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Monad.Trans.Except
import Data.Functor.Identity
f :: ExceptT String Identity a -> Maybe a
f m = Nothing
where x :: Identity (Either String a)
x = runExceptT m
此代码不类型检查。它失败并显示以下错误消息:
Couldn't match type ‘a’ with ‘a1’
‘a’ is a rigid type variable bound by
the type signature for f :: ExceptT String Identity a -> Maybe a
at src/Lib.hs:20:6
‘a1’ is a rigid type variable bound by
the type signature for x :: Identity (Either String a1)
at src/Lib.hs:22:14
Expected type: ExceptT String Identity a1
Actual type: ExceptT String Identity a
Relevant bindings include
x :: Identity (Either String a1) (bound at src/Lib.hs:23:9)
m :: ExceptT String Identity a (bound at src/Lib.hs:21:3)
f :: ExceptT String Identity a -> Maybe a
(bound at src/Lib.hs:21:1)
In the first argument of ‘runExceptT’, namely ‘m’
In the expression: runExceptT m
为什么会失败?我不明白为什么这会导致问题——这似乎是教科书对作用域类型变量的使用。作为参考,我使用的是 GHC 7.10.3.
您需要 explicit forall:
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Monad.Trans.Except
import Data.Functor.Identity
f :: forall a. ExceptT String Identity a -> Maybe a
f m = Nothing
where x :: Identity (Either String a)
x = runExceptT m
但是为什么
这是一个很好的问题。这似乎是ScopedTypeVariables
的规则。我们知道在 GHC Haskell 中所有顶级变量都是隐式 forall
的,如记录 here。有人会怀疑 GHC 开发人员添加此行为是为了向后兼容:如果没有此规则,未打开扩展名的文件可能会在扩展名打开后停止类型检查。我们可以很容易地想象这样一种场景,在 where
块中声明的辅助函数无意中重用了公共类型变量名称 a, b, c, t
,依此类推。如果有人能在 GHC 源代码中找到显式和隐式 forall
变量之间的区别出现的确切位置,那就太好了。
更新
我们开始吧(虽然这都是来自评论和 grepping 的猜测):
在对用户签名进行类型检查时,函数 tcUserTypeSig
调用 findScopedTyVars
。 TcBinds.hs:ef44606:L1786
findScopedTyVars
in TcRnTypes
通过调用 tcSplitForAllTys
过滤 forall
s。 TcRnTypes.hs:ef44606:L1221
它是 splitForAllTys
的包装器,它折叠类型的子类型以构建由 forall
引入的类型变量列表。 Types/Type.hs:ef44606:L1361
我有一个函数,其值在 where
子句中定义,我想给它一个显式类型注释。注释需要使用顶级函数中的类型变量,因此我的理解是我需要使用 ScopedTypeVariables
。这是问题的最小化:
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Monad.Trans.Except
import Data.Functor.Identity
f :: ExceptT String Identity a -> Maybe a
f m = Nothing
where x :: Identity (Either String a)
x = runExceptT m
此代码不类型检查。它失败并显示以下错误消息:
Couldn't match type ‘a’ with ‘a1’
‘a’ is a rigid type variable bound by
the type signature for f :: ExceptT String Identity a -> Maybe a
at src/Lib.hs:20:6
‘a1’ is a rigid type variable bound by
the type signature for x :: Identity (Either String a1)
at src/Lib.hs:22:14
Expected type: ExceptT String Identity a1
Actual type: ExceptT String Identity a
Relevant bindings include
x :: Identity (Either String a1) (bound at src/Lib.hs:23:9)
m :: ExceptT String Identity a (bound at src/Lib.hs:21:3)
f :: ExceptT String Identity a -> Maybe a
(bound at src/Lib.hs:21:1)
In the first argument of ‘runExceptT’, namely ‘m’
In the expression: runExceptT m
为什么会失败?我不明白为什么这会导致问题——这似乎是教科书对作用域类型变量的使用。作为参考,我使用的是 GHC 7.10.3.
您需要 explicit forall:
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Monad.Trans.Except
import Data.Functor.Identity
f :: forall a. ExceptT String Identity a -> Maybe a
f m = Nothing
where x :: Identity (Either String a)
x = runExceptT m
但是为什么
这是一个很好的问题。这似乎是ScopedTypeVariables
的规则。我们知道在 GHC Haskell 中所有顶级变量都是隐式 forall
的,如记录 here。有人会怀疑 GHC 开发人员添加此行为是为了向后兼容:如果没有此规则,未打开扩展名的文件可能会在扩展名打开后停止类型检查。我们可以很容易地想象这样一种场景,在 where
块中声明的辅助函数无意中重用了公共类型变量名称 a, b, c, t
,依此类推。如果有人能在 GHC 源代码中找到显式和隐式 forall
变量之间的区别出现的确切位置,那就太好了。
更新
我们开始吧(虽然这都是来自评论和 grepping 的猜测):
在对用户签名进行类型检查时,函数
tcUserTypeSig
调用findScopedTyVars
。 TcBinds.hs:ef44606:L1786findScopedTyVars
inTcRnTypes
通过调用tcSplitForAllTys
过滤forall
s。 TcRnTypes.hs:ef44606:L1221它是
splitForAllTys
的包装器,它折叠类型的子类型以构建由forall
引入的类型变量列表。 Types/Type.hs:ef44606:L1361