高阶参数多态性有用吗?


16

我很确定每个人都熟悉以下形式的通用方法:

T DoSomething<T>(T item)

此功能也称为参数多态(PP),特别是等级1 PP。

假设可以使用以下形式的函数对象来表示此方法:

<T> : T -> T

即,<T>意味着需要一种类型的参数,以及T -> T装置,它需要类型的一个参数T并返回相同的类型的值。

那么以下将是2级PP函数:

(<T> : T -> T) -> int 

该函数本身不接受任何类型参数,但接受一个具有类型参数的函数。您可以迭代地继续进行此操作,使嵌套越来越深,PP等级越高。

在编程语言中,此功能确实很少见。默认情况下,甚至Haskell也不允许。

它有用吗?它可以描述很难用其他方式描述的行为吗?

此外,强制性意味着什么?(在这种情况下)


1
有趣的是,TypeScript是一种主流语言,具有完整的n位PP支持。例如,以下是有效的TypeScript代码:let sdff = (g : (f : <T> (e : T) => void) => void) => {}
GregRos

Answers:


11

通常,当您希望被调用方能够选择类型参数的值而不是调用方时,可以使用更高级别的多态性。例如:

f :: (forall a. Show a => a -> Int) -> (Int, Int)
f g = (g "one", g 2)

任何功能g,我传递给这个f必须能够给我Int一些类型,其中的值事情g知道该类型是它的一个实例Show。所以这些是洁净的:

f (length . show)
f (const 42)

但是这些不是:

f length
f succ

一个特别有用的应用程序是使用类型范围来强制范围。假设我们有一个type对象Action<T>,它表示可以运行以产生type结果的动作T,例如future或callback。

T runAction<T>(Action<T>)

runAction :: forall a. Action a -> a

现在,假设我们还有一个Action可以分配Resource<T>对象的对象:

Action<Resource<T>> newResource<T>(T)

newResource :: forall a. a -> Action (Resource a)

我们要强制这些资源Action创建资源的内部使用,而不能在不同操作或同一操作的不同运行之间共享,因此操作是确定性和可重复的。

我们可以使用排名更高的类型来实现此目的,方法是SResourceAction类型上添加一个完全抽象的参数-它代表的“作用域” Action。现在我们的签名是:

T run<T>(<S> Action<S, T>)
Action<S, Resource<S, T>> newResource<T>(T)

runAction :: forall a. (forall s. Action s a) -> a
newResource :: forall s a. a -> Action s (Resource s a)

现在,当我们给出runActionan时Action<S, T>,我们可以放心,因为“ scope”参数S是完全多态的,因此它无法转义的主体,runAction因此使用S诸如此类的任何类型的值Resource<S, int>同样也无法转义!

(在Haskell中,这称为STmonad,在其中runAction被称为runSTResource被称为STRef,并且newResource被称为newSTRef。)


ST单子是一个很有趣的例子。您能否举一些更多的例子说明何时更高级别的多态性会有用?
GregRos

@GregRos:存在符号也很方便。在Haxl中,我们有一个likes存在类data Fetch d = forall a. Fetch (d a) (MVar a),它是对数据源的请求d和用于存储结果的插槽的一对。结果和插槽必须具有匹配的类型,但是该类型是隐藏的,因此您可以拥有对同一数据源的异类请求列表。现在,您可以使用更高级别的多态性来编写一个可提取所有请求的函数,给定一个可提取一个请求的函数:fetch :: (forall a. d a -> IO a) -> [Fetch d] -> IO ()
乔恩·普迪

8

较高等级的多态性非常有用。在系统F(您熟悉的类型化FP语言的核心语言)中,这对于接受“类型化的教堂编码”至关重要,而这实际上是系统F进行编程的方式。没有这些,系统F将完全无用。

在系统F中,我们将数字定义为

Nat = forall c. (c -> c) -> c -> c

加法类型

plus : Nat -> Nat -> Nat
plus l r = Λ t. λ (s : t -> t). λ (z : t). l s (r s z)

这是较高等级的类型(forall c.这些箭头内显示的)。

这也出现在其他地方。例如,如果您要表明计算是一种适当的延续传递样式(Google“ codensity haskell”),则可以将其正确地设置为

type CPSed A = forall c. (A -> c) -> c

即使谈论系统F中的无人居住类型也需要更高级别的多态性

type Void = forall a. a 

总而言之,如果我们要处理任何有趣的数据,则在纯类型系统(系统F,CoC)中编写函数需要更高等级的多态性。

特别是在系统F中,这些编码必须是“强制性的”。这意味着a 绝对可以forall a.量化所有类型。严格来说,这包括我们正在定义的类型。在forall a. aa实际上可以forall a. a再次代表!在像ML这样的语言中,情况并非如此,它们被称为“谓语”,因为类型变量仅在没有数量词(称为单型)的类型集合上进行数量化。我们的定义plus所需的非直谓性,以及因为我们的实例cl : NatNat

最后,我想提到的最后一个理由是,即使在具有任意递归类型的语言中(与System F一样),您也希望同时具有隐含性和较高等级的多态性。在Haskell中,有一个用于效果的monad,称为“状态线程monad”。这个想法是,状态线程monad可以让您改变事物,但需要对其进行转义,以使结果不依赖于任何可变的东西。这意味着ST计算是纯净的。为了执行此要求,我们使用更高等级的多态性

runST :: forall a. (forall s. ST s a) -> a

在这里,通过确保将a其限制在我们介绍的范围之外s,我们知道a代表了一种不依赖于格式良好的类型s。我们s习惯于对特定状态线程中的所有可变事物进行参数化处理,因此我们知道它a独立于可变事物,因此没有任何东西可以逃脱该ST计算的范围!使用类型排除格式错误的程序的一个很好的例子。

顺便说一句,如果您有兴趣学习类型理论,我建议您投资一本好书或两本。很难一点一点地学习这些东西。我会建议Pierce或Harper关于PL理论的一本书(以及类型理论的某些要素)。《类型和编程语言的高级主题》一书也涵盖了大量类型理论。最后,“对马丁·洛夫的类型理论进行编程”是对马丁·洛夫概述的内涵类型理论的很好的阐述。


感谢您的建议。我会查一下。这个主题真的很有趣,我希望更多的编程语言可以采用一些更高级的类型系统概念。它们赋予您更多的表达能力。
GregRos 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.