如果不使用类型类约束,forall 有什么用?

问题描述

我读完了 the Existential Types Wikibook,它比较了使用 forall 和使用小写字母来定义泛型类型。然后它说 forall 的真正用处是当您将它与类型类一起使用时。也就是说,forall 使您的函数可以处理许多符合某种类型类的类型。

示例:

 data ShowBox = forall s. Show s => SB s

嗯,我发现了一个真正的世界用法

spock :: forall conn sess st. Spockcfg conn sess st -> 
                               SpockM conn sess st () -> IO Middleware
<Source>

您可以在这里看到,在 source 中,它使用 forall 但没有类型类约束:

spock :: forall conn sess st. Spockcfg conn sess st -> 
                               SpockM conn sess st () -> IO Wai.Middleware
spock spockcfg spockAppl =
    do connectionPool <-
           case poolOrConn of
             PCNoDatabase ->
             {- ... -}

我对 Haskell 很陌生,正在尝试理解 forall

解决方法

首先,忘记存在主义。它们有点麻烦——我个人从不使用该扩展名,只在需要时使用更通用的 -XGADTs
另外,请允许我使用 符号进行通用量化,我发现它更具可读性。 (请注意,它看起来有点像 \ lambda,它是 的值级类似物。)这需要 -XUnicodeSyntax

所以,签名

spock :: ∀ conn sess st. SpockCfg conn sess st -> SpockM conn sess st () -> IO Middleware

对于所有外部目的,与

完全相同
spock :: SpockCfg conn sess st -> SpockM conn sess st () -> IO Middleware

spock :: SpockCfg c s t -> SpockM c s t () -> IO Middleware

当您看到此类带有显式 的签名时,原因通常与 -XExistentialQuantification-XRankNTypes 无关。相反,他们要么只是发现明确说明什么是类型变量会更清楚,要么定义可能会使用 -XScopedTypeVariables。比如这两个定义其实是不同的:

{-# LANGUAGE ScopedTypeVariables,UnicodeSyntax #-}

foo :: a -> a
foo x = xAgain
 where xAgain :: a
       xAgain = x

foo' :: ∀ a . a -> a
foo' x = xAgain
 where xAgain :: a
       xAgain = x

foo 不编译,因为全局和局部签名都被解释为隐式量化,即作为

foo :: ∀ a . a -> a
foo x = xAgain
 where xAgain :: ∀ α . α
       xAgain = x

但这行不通,因为现在 xAgain 必须具有独立于您传入的 x 类型的多态类型。相比之下,在 foo' 中我们只量化一次,并且比全局定义中的 a 也是本地定义中使用的类型。

spock 的示例中,他们甚至不使用作用域类型变量,但我怀疑他们在调试期间使用了,然后只是将 留在那里。

,

类似的类型声明

f :: A a -> B b -> C

被隐含地普遍量化,即意味着与

相同
f :: forall a b . A a -> B b -> C

两者都表示:f 具有多态类型,其中 ab 可以是任何类型(即范围超过 all 类型)。在这种情况下,forall 的范围是 whole 类型表达式,它通常被排除在外,但它总是隐含地存在。在您的示例中,它是明确编写的,可能是为了整齐地枚举类型变量。

但是,在某些情况下,即使没有类型类,您也需要 forallrank-N types,其中 forall 的范围不是整个类型表达式.

在您看到函数的 ST monad 中使用了一个众所周知的示例:

runST :: forall a. (forall s. ST s a) -> a

请注意,第一个 forall 可以省略,如上例所示,但不能省略第二个。另请注意,runST 的类型中没有类型类约束。

,

一个直接实用程序是带有 TypeApplications 扩展名,它允许我们明确地选择类型变量的顺序:

 > foo :: forall a b. a -> b -> b ; foo x y = y
 > :t foo @ Int 1 2
foo @ Int 1 2 :: Num b => b    -- Int goes to the first argument

 > foo :: forall b a. a -> b -> b ; foo x y = y
 > :t foo @ Int 1 2
foo @ Int 1 2 :: Int           -- Int goes to the second argument

我在链接中找不到具体提到的,但上面的交互在 repl.it 中进行了测试。

嗯,实际上,链接中的一般描述确实适用:当从左到右阅读时,b 出现在forall b a. a -> b -> b第一