Rank 2 多态性的实际应用?

问题描述 投票:0回答:3

我正在讨论多态性,并且我正在尝试了解这种功能的实际用途。

我对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 多态性的一般情况是什么?

haskell polymorphism
3个回答
11
投票

如果将多态函数作为参数传递给 Rank2 多态函数,则实际上传递的不仅仅是一个函数,而是整个函数family——对于满足约束的所有可能类型。

通常,这些 forall 量词带有类约束。例如,我可能希望同时使用两种不同类型进行数字算术(用于比较精度或其他):

data FloatCompare = FloatCompare {
     singlePrecision :: Float
   , doublePrecision :: Double
   }

现在我可能想通过一些数学运算来修改这些数字。比如:

modifyFloat :: (Num -> Num) -> FloatCompare -> FloatCompare

但是

Num
不是类型,只是类型类。我当然可以传递一个可以修改任何特定数字类型的函数,但我不能用它来修改botha
Float
Double
值,至少不能没有一些丑陋的(并且可能有损的)来回转换。

解决方案:Rank-2 多态性!

modifyFloat :: (∀ n . Num n => n -> n) -> FloatCompare -> FloatCompare
mofidyFloat f (FloatCompare single double)
    = FloatCompare (f single) (f double)

在实践中如何发挥作用的最佳示例可能是镜头。镜头是某些较大数据结构中字段的“智能访问器功能”。它允许您访问字段、更新字段、收集结果......同时以非常简单的方式进行组合。工作原理:Rank2-多态性;每个镜头都是多态的,不同的实例化分别对应于“getter”/“setter”方面。


5
投票

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



1
投票

例如 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(与这个问题无关):从语言设计的角度来看,如果你要支持模块系统,那么你最好支持更高级别的类型(即允许在任何级别上任意量化类型变量)以减少重复的努力(即,类型检查模块应该与类型检查具有更高级别类型的记录几乎相同)。

© www.soinside.com 2019 - 2024. All rights reserved.