为内部的辅助函数添加类型签名

Add type signature for a helper function inside

首先我定义如下数据类型

data Supply s a = S (Stream s -> (a, Stream s))
data Stream a = Cons a (Stream a)

然后我想实现一个映射到具有以下类型签名的 Supply 的函数:

mapSupply :: (a -> b) -> Supply s a -> Supply s b

这是我的实现:(编译没有问题)

mapSupply :: (a -> b) -> Supply s a -> Supply s b
mapSupply mapFunc (S supFuncA) = S supFuncB where
    supFuncB strm = let (la, strms) = supFuncA strm in 
        ((mapFunc la), strms)

然后当我试图写下我在 mapSupply 中定义的名为 supFuncB 的辅助函数的类型签名时遇到了问题。

supFuncB 的类型签名非常简单,应该是:

supFuncB :: Stream s -> (b, Stream s)

然而,当我尝试在代码中添加类型签名时,出现编译错误。代码看起来像这样

mapSupply :: (a -> b) -> Supply s a -> Supply s b
mapSupply mapFunc (S supFuncA) = S supFuncB where
    supFuncB :: Stream s -> (b, Stream s)
    supFuncB strm = let (la, strms) = supFuncA strm in 
        ((mapFunc la), strms)

然后编译器报错:

• Couldn't match type ‘s1’ with ‘s’
  ‘s1’ is a rigid type variable bound by
    the type signature for:
      supFuncB :: forall s1 b1. Stream s1 -> (b1, Stream s1)
    at Main.hs:58:5-41
  ‘s’ is a rigid type variable bound by
    the type signature for:
      mapSupply :: forall a b s. (a -> b) -> Supply s a -> Supply s b
    at Main.hs:56:1-49
  Expected type: Stream s1
    Actual type: Stream s
• In the expression: strms
  In the expression: ((mapFunc la), strms)
  In the expression:
    let (la, strms) = supFuncA strm in ((mapFunc la), strms)

我是 Haskell 的新手,我不明白为什么编译会失败?如果我要在代码中添加它,正确的类型签名应该是什么。

从头开始,解决方案是打开 ScopedTypeVariables 并在 mapSupply 签名中使用显式 forall,如下所示:

{-# LANGUAGE ScopedTypeVariables #-}  -- Put this at the top of your file.

mapSupply :: forall a b s. (a -> b) -> Supply s a -> Supply s b
mapSupply mapFunc (S supFuncA) = S supFuncB where
    supFuncB :: Stream s -> (b, Stream s)
    supFuncB strm = let (la, strms) = supFuncA strm in 
        ((mapFunc la), strms)

下面是对为什么需要这样做的解释。


当你这样写签名时:

mapSupply :: (a -> b) -> Supply s a -> Supply s b

GHC 实际上看到了这个:

mapSupply :: forall a b s. (a -> b) -> Supply s a -> Supply s b

forall 通常可以隐式表示 abs 可以是任何东西——mapSupply 是一个polymorphic 函数,所以使用它的人可以自由地为三个类型变量选择任何具体类型。明确编写 foralls,您的第二个定义将如下所示:

mapSupply :: forall a b s. (a -> b) -> Supply s a -> Supply s b
mapSupply mapFunc (S supFuncA) = S supFuncB where
    supFuncB :: forall s b. Stream s -> (b, Stream s)
    supFuncB strm = let (la, strms) = supFuncA strm in 
        ((mapFunc la), strms)

根据它,mapSupply中的abs可以是任何东西,s和[=20也是如此=] 在 supFuncB。不过,这是一个问题。例如,定义涉及 strms,其类型为 s... 除了 s,因为您使用的是 supFuncA,它显示为 不是 来自 supFuncB 签名的那个,而是来自 mapSupply 签名的那个。尽管 mapSupply 中的 s 原则上可以是任何东西,正如我之前提到的,一旦您使用 mapSupply 实际选择了 ss from supFuncB 必须匹配它。既然如此,supFuncB 签名中的 forall 就不合适了,因为它的类型变量实际上不能是任何东西。如果我们重命名来自 supFuncB 的类型变量,这样它们的名称就不会与来自 mapSupply 的变量名称冲突,就更容易看出(鉴于 forall,这应该是一个有效的举措那个):

mapSupply :: forall a b s. (a -> b) -> Supply s a -> Supply s b
mapSupply mapFunc (S supFuncA) = S supFuncB where
    supFuncB :: forall s1 b1. Stream s1 -> (b1, Stream s1)
    supFuncB strm = let (la, strms) = supFuncA strm in 
        ((mapFunc la), strms)

(GHC 在内部执行此操作,这解释了为什么您提到的错误消息是 s1 类型变量。)

这个问题只是因为添加到 supFuncB 的签名而发生的,它引入了一个隐含的 forall。没有签名,GHC 通过不概括 supFuncB 中的类型来做你想做的事——在这种情况下,它不是多态的,而是 monomorphic in the type variables abs,借自mapSupplyScopedTypeVariables 扩展可以在为 supFuncB 编写类型签名时恢复该行为。启用后,对签名中的类型变量使用显式 forall 将使相应定义中具有相同名称的任何类型变量引用相同的事物(只要它们不在 forall 在他们自己的签名中)。换句话说,通过这样做,可以在相应定义范围内的任何地方引用来自外部签名的变量,这证明了扩展名的合理性。