在hlint
的帮助下编写一些Haskell代码时,我按照提示向函数添加了一个类型签名。它注入了类似的东西:
{-# LANGUAGE RankNTypes #-}
multipleOf :: forall a. Integral a => a -> a -> Bool
multipleOf divisor n = n `mod` divisor == 0
这种语言扩展对我来说是新的,但据我所知,这与更简单的相同:
multipleOf :: Integral a => a -> a -> Bool
multipleOf divisor n = n `mod` divisor == 0
我读过的所有排名N类的例子似乎都没有对已经可用的多态性添加任何新内容,而且这种forall
语法似乎并不像添加任何值。
我错过了什么?是否有一个很好的例子说明forall
语法在没有扩展名的情况下可以做什么?
答案 0 :(得分:11)
您错过了可以限制量化类型变量的范围:
modifyPair :: (Num b, Num c) => (forall a. Num a => a -> a) -> (b, c) -> (b, c)
modifyPair f (b, c) = (f b, f c)
尝试在没有RankNTypes
扩展名的情况下编写该函数。特别是,允许该对的元素彼此不同类型。
这个特定的示例并不太有用,但总体思路仍然存在。您可以指定函数的参数必须是多态的。
您可以使用此工具执行其他技巧。规范示例来自ST
。 ST
库的目标是允许约束使用可变数据。也就是说,您可以实现一个涉及真实变异的算法,并提供一个纯粹的外部接口。 ST
本身通过聪明的类型系统技巧来证明使用是安全的:
newtype ST s a = ... -- details aren't important
newtype STRef s a = ... -- details still aren't important
-- a standard-ish interface to mutable data
newSTRef :: ST s (STRef s a)
readSTRef :: STRef s a -> ST s a
writeSTRef :: STRef s a -> a -> ST s ()
-- the magic using RankNTypes
runST :: (forall s. ST s a) -> a
额外的类型变量s
值得关注。它出现在ST
和STRef
中。每个操纵STRef
的函数都会确保s
和ST
中的STRef
是相同的类型变量。因此,当您到达runST
时,您会发现类型s
必须与类型a
无关。 s
的范围比a
的范围更受限制。最终结果是你不能写runST newSTRef
之类的东西。类型检查器将拒绝它,因为类型变量s
必须逃避它量化的上下文。
因此,当您指定函数的参数必须是多态的时,您可以提取一些方便的技巧。