如果不使用 type class 约束,forall 的效用是什么?
What's the utility of forall if not using type class constraint?
我读完了 the Existential Types Wikibook,它比较了使用 forall
和使用小写字母来定义泛型类型。然后它说 forall
的真正用途是当您将它与类型 class 一起使用时。也就是说,forall
使您的函数适用于许多遵循某种类型 class.
的类型
示例:
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
但没有类型 class 约束:
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
。
像
这样的类型声明
f :: A a -> B b -> C
隐式普遍量化,即与
的意思相同
f :: forall a b . A a -> B b -> C
两者均表示:f
具有多态类型,其中 a
和 b
可以是任何类型(即范围超过 所有 类型) .在这种情况下,forall
的范围是 whole 类型表达式,它通常被排除在外,但它始终隐含地存在。在您的示例中,它是明确编写的,可能是为了巧妙地枚举类型变量。
但是,有些情况下即使没有类型类也需要 forall
:rank-N types,其中 forall
的范围是 而不是 整个类型表达式。
ST
monad 中使用了一个众所周知的示例,您可以在其中看到函数:
runST :: forall a. (forall s. ST s a) -> a
请注意,第一个 forall
可以省略,如上例所示,但第二个不能。另请注意,runST
.
类型中没有类型类约束
首先,忘掉存在主义吧。它们有点麻烦——我个人从不使用该扩展名,只在需要时使用更严格的通用 -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
不编译,因为全局和局部签名都被解释为隐式量化,即 as
foo :: ∀ a . a -> a
foo x = xAgain
where xAgain :: ∀ α . α
xAgain = x
但这不起作用,因为现在 xAgain
必须具有独立于您传入的 x
类型的多态类型。相比之下,在 foo'
中我们仅量化一次,并且全局定义中的 a
也是局部定义中使用的类型。
在 spock
的示例中,他们甚至没有使用作用域类型变量,但我怀疑他们在调试期间使用了,然后只是将 ∀
留在那里。
一个直接的实用程序是 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
我找不到在 link 中具体提到的,但上面的交互在 repl.it 中进行了测试。
嗯,实际上,link 中的一般描述确实适用:b
在 forall b a. a -> b -> b
中出现 第一个 ,当它被读取时从左到右。
我读完了 the Existential Types Wikibook,它比较了使用 forall
和使用小写字母来定义泛型类型。然后它说 forall
的真正用途是当您将它与类型 class 一起使用时。也就是说,forall
使您的函数适用于许多遵循某种类型 class.
示例:
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
但没有类型 class 约束:
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
。
像
这样的类型声明f :: A a -> B b -> C
隐式普遍量化,即与
的意思相同f :: forall a b . A a -> B b -> C
两者均表示:f
具有多态类型,其中 a
和 b
可以是任何类型(即范围超过 所有 类型) .在这种情况下,forall
的范围是 whole 类型表达式,它通常被排除在外,但它始终隐含地存在。在您的示例中,它是明确编写的,可能是为了巧妙地枚举类型变量。
但是,有些情况下即使没有类型类也需要 forall
:rank-N types,其中 forall
的范围是 而不是 整个类型表达式。
ST
monad 中使用了一个众所周知的示例,您可以在其中看到函数:
runST :: forall a. (forall s. ST s a) -> a
请注意,第一个 forall
可以省略,如上例所示,但第二个不能。另请注意,runST
.
首先,忘掉存在主义吧。它们有点麻烦——我个人从不使用该扩展名,只在需要时使用更严格的通用 -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
不编译,因为全局和局部签名都被解释为隐式量化,即 as
foo :: ∀ a . a -> a
foo x = xAgain
where xAgain :: ∀ α . α
xAgain = x
但这不起作用,因为现在 xAgain
必须具有独立于您传入的 x
类型的多态类型。相比之下,在 foo'
中我们仅量化一次,并且全局定义中的 a
也是局部定义中使用的类型。
在 spock
的示例中,他们甚至没有使用作用域类型变量,但我怀疑他们在调试期间使用了,然后只是将 ∀
留在那里。
一个直接的实用程序是 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
我找不到在 link 中具体提到的,但上面的交互在 repl.it 中进行了测试。
嗯,实际上,link 中的一般描述确实适用:b
在 forall b a. a -> b -> b
中出现 第一个 ,当它被读取时从左到右。