我覆盖了多态性,我试图看到这种功能的实际用途。
我对等级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多态性的一般情况是什么?
答案 0 :(得分:6)
如果您将多态函数as和参数传递给Rank2多态函数,那么您实际上不仅要传递一个函数,而且传递函数的整个族 - 用于满足约束的所有可能类型
通常,这些forall量词具有类约束。例如,我可能希望同时使用两种不同类型进行数字运算(用于比较精度或其他类型)。
data FloatCompare = FloatCompare {
singlePrecision :: Float
, doublePrecision :: Double
}
现在我可能想通过一些数学运算来修改这些数字。像
这样的东西modifyFloat :: (Num -> Num) -> FloatCompare -> FloatCompare
但Num
不是类型,只是类型类。我当然可以传递一个修改任何特定数字类型的函数,但是我不能用它来修改两个一个Float
和一个{{1价值,至少不是没有一些丑陋(可能有损)来回转换。
解决方案:Rank-2多态性!
Double
这在实践中如何有用的最佳单一示例可能是lenses。镜头是某些较大数据结构中的字段的“智能访问器功能”。它允许您访问字段,更新它们,收集结果......同时以非常简单的方式编写。工作原理:Rank2-polymorphism;每个镜头都是多态的,不同的实例分别对应于“getter”/“setter”方面。
答案 1 :(得分:1)
正如Benjamin Hodgson在评论中提到的那样,等级2类型的应用的首选示例是runST
。这是一个相当不错的示例,并且有各种使用相同技巧的示例。例如,branding可以跨多种类型维护抽象数据类型不变量,从而避免混淆ad,region-based version of ST中的差异。
但我真的想谈谈Haskell程序员如何隐式使用rank-2类型。每个类型的方法具有普遍量化的类型,这些类型都是desugars到具有rank-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)确实use this encoding。
要连接到leftaroundabout对lenses的引用,您可以将层次结构视为类型类的层次结构,并将镜头组合器视为显式构建相关类型类词典的工具。当然,它不是实际上类型类的层次结构的原因是我们通常会为同一类型提供多个“实例”。例如。 _head
和_head . _tail
是Traversal' s a
的{{3}}个“实例”。
* 高级类型类不一定会导致这种情况,并且类型类*
可能会发生这种情况。例如:
-- 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
答案 2 :(得分:0)
如果您在 Haskell 中使用模块,那么您已经在使用 Rank-2 类型。从理论上讲,模块是具有 rank-2 类型属性的记录。
例如,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(与此问题无关):从语言设计的角度来看,如果您要支持模块系统,那么您不妨支持更高等级的类型(即允许在任何级别上任意量化类型变量)以减少重复工作(即类型检查模块应该几乎与类型检查具有较高等级类型的记录相同)。