我正在讨论多态性,并且我正在尝试了解这种功能的实际用途。
我对Rank 2的基本理解是:
type MyType = ∀ a. a -> a
subFunction :: a -> a
subFunction el = el
mainFunction :: MyType -> Int
mainFunction func = func 3
我知道这允许用户在 mainFunction 内部使用多态函数(subFunction)并严格指定它的输出(Int)。这看起来与 GADT 非常相似:
data Example a where
ExampleInt :: Int -> Example Int
ExampleBool :: Bool -> Example Bool
1)鉴于上述情况,我对 Rank 2 多态性的理解是否正确?
2) 例如,与 GADT 不同,可以使用 Rank 2 多态性的一般情况是什么?
如果将多态函数作为参数传递给 Rank2 多态函数,则实际上传递的不仅仅是一个函数,而是整个函数family——对于满足约束的所有可能类型。
通常,这些 forall 量词带有类约束。例如,我可能希望同时使用两种不同类型进行数字算术(用于比较精度或其他):
data FloatCompare = FloatCompare {
singlePrecision :: Float
, doublePrecision :: Double
}
现在我可能想通过一些数学运算来修改这些数字。比如:
modifyFloat :: (Num -> Num) -> FloatCompare -> FloatCompare
但是
Num
不是类型,只是类型类。我当然可以传递一个可以修改任何特定数字类型的函数,但我不能用它来修改bothaFloat
和Double
值,至少不能没有一些丑陋的(并且可能有损的)来回转换。
解决方案:Rank-2 多态性!
modifyFloat :: (∀ n . Num n => n -> n) -> FloatCompare -> FloatCompare
mofidyFloat f (FloatCompare single double)
= FloatCompare (f single) (f double)
在实践中如何发挥作用的最佳示例可能是镜头。镜头是某些较大数据结构中字段的“智能访问器功能”。它允许您访问字段、更新字段、收集结果......同时以非常简单的方式进行组合。工作原理:Rank2-多态性;每个镜头都是多态的,不同的实例化分别对应于“getter”/“setter”方面。
Rank-2 类型应用的首选示例是 Benjamin Hodgson 在评论中提到的
runST
。这是一个相当好的示例,并且有多种使用相同技巧的示例。例如,branding维护跨多种类型的抽象数据类型不变性,避免ad中差异的混淆,ad是基于区域的ST版本。 但我实际上想谈谈 Haskell 程序员如何一直隐式使用 2 级类型。每个其方法具有通用量化类型的类型类都会脱糖为具有 2 级类型字段的字典。实际上,这实际上总是一个更高级的类型类
*,例如
Functor
或 Monad
。我将使用
Alternative
的简化版本作为示例。类声明是:
class Alternative f where
empty :: f a
(<|>) :: f a -> f a -> f a
代表这个类的字典是:
data AlternativeDict f = AlternativeDict {
empty :: forall a. f a,
(<|>) :: forall a. f a -> f a -> f a }
有时这样的编码很好,因为它允许人们对同一类型使用不同的“实例”,也许只是在本地。例如,
Maybe
有两个明显的Alternative
实例,具体取决于Just a <|> Just b
是Just a
还是Just b
。没有类型类的语言,例如 Scala,确实使用这种编码。 要连接到 leftaroundabout 对 lenses 的引用,您可以将那里的层次结构视为类型类的层次结构,并将镜头组合器视为用于显式构建相关类型类字典的简单工具。当然,它“实际上”不是类型类层次结构的原因是,我们通常会拥有同一类型的多个“实例”。例如。
_head
和 _head . _tail
都是 Traversal' s a
的“实例”。 *
较高种类的类型类不一定会导致这种情况,并且对于 *
类型的类型类可能会发生这种情况。例如:
-- Higher-kinded but doesn't require universal quantification.
class Sum c where
sum :: c Int -> Int
-- Not higher-kinded but does require universal quantification.
class Length l where
length :: [a] -> l
例如 Haskell 中下面的
Foo
模块...
module Foo(id) where
id :: forall a. a -> a
id x = x
import qualified Foo
main = do
putStrLn (Foo.id "hello")
return ()
...实际上可以认为是这样的记录:
type FooType = FooType {
id :: forall a. a -> a
}
Foo :: FooType
Foo = Foo {
id = \x -> x
}
P/S(与这个问题无关):从语言设计的角度来看,如果你要支持模块系统,那么你最好支持更高级别的类型(即允许在任何级别上任意量化类型变量)以减少重复的努力(即,类型检查模块应该与类型检查具有更高级别类型的记录几乎相同)。