为什么这个函数在where子句中使用范围类型变量而不是类型检查?

时间:2016-05-02 18:39:33

标签: haskell ghc

我有一个函数,它在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。

1 个答案:

答案 0 :(得分:7)

您需要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' d,如文档here所示。有人会怀疑GHC开发人员为了向后兼容性而添加了这种行为:如果没有这个规则,没有启用扩展名的文件可能会在扩展程序打开后停止进行类型检查。我们可以很容易地想象一个场景,其中在where块中声明的辅助函数无意中重用了公共类型变量名a, b, c, t,依此类推。如果有人能够在GHC源代码中找到显式和隐式forall变量之间的区别,那就太棒了。

更新

我们走了(尽管这些都是评论和评论的猜测):

  • 在对用户签名进行类型检查时,函数tcUserTypeSig会调用findScopedTyVarsTcBinds.hs:ef44606:L1786

  • 通过调用findScopedTyVarsTcRnTypes过滤了foralltcSplitForAllTys中的{li>

    splitForAllTysTcRnTypes.hs:ef44606:L1221

  • 这是forall的包装器,它折叠了一个类型的子类型,以构建{{1}}引入的类型变量列表。 Types/Type.hs:ef44606:L1361