如何解决如果不使用类型类约束,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
具有多态类型,其中 a
和 b
可以是任何类型(即范围超过 all 类型)。在这种情况下,forall
的范围是 whole 类型表达式,它通常被排除在外,但它总是隐含地存在。在您的示例中,它是明确编写的,可能是为了整齐地枚举类型变量。
但是,在某些情况下,即使没有类型类,您也需要 forall
:rank-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
中第一。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。