https://www.seas.harvard.edu/courses/cs152/2019sp/lectures/lec18-monads.pdf 上面写着

一种类型 $\头$ 列表是具有类型元素的列表的类型 $\头$

为什么列表必须包含相同类型的元素?为什么不能包含不同类型的元素?有没有一种方法可以在类型化lambda演算中多态地定义列表,以便它采用任何类型的元素?

那么我们可以在多态定义的列表上使用列表monad吗?

有帮助吗?

解决方案

短答案是 $ \ tau \ \ text {list} $ 定义作为类型构造函数,以及规则形成和消除,因此我们可以类似地定义一个允许不同类型的术语构造函数来形成单个“可变类型的列表”。但是,列表不能在定义中采取不同的类型,仅仅因为它们是关于单个类型定义的。在任何一种情况下,添加列表或可变类型的列表都涉及扩展简单类型的 $ \ lambda $ -calculus,作为任何的列表>种类在通常的演示中不存在。

如果我们有一个稍微更丰富的类型系统,而不是简单类型的 $ \ lampda $ -calculus,我们可以使用标准 $ \ tau \ \ text {list} $ s。

  • 如果我们有一种亚型,我们可以存储不同类型的条款,如只要他们共享超级类型。但是,当我们从列表中项目元素出来时,我们不能特别告诉他们开始的类型(这可能熟悉来自面向对象的编程),因此这有点有限。
  • 如果我们有依赖总和类型(也称为 $ \ sigma $ -types)和一个宇宙类型 $ \ mathcalu $ (即“类型”),我们可以形成 $( \ sigma_ {a:\ mathcal u} a)\ \ text {list} $ ,其元素是由类型 $ a $ 和a的对成对组成这个类型的术语。
最后,如果我们想要异构名单,我将注意到,如果我们想要异构列表,多态性并没有帮助我们:它只允许我们操纵不同 $ \ tau $ 更有效。多态类型必须是 somansed 在某种意义上是我们在此处需要依赖的原因。< / p>
要回答后续问题:如果我们使用依赖类型方法有两个可变排序的列表,我们可以像普通列表一样连接和平坦列表。

  • $ \ mathrm {list} $ monad有一个操作 $ \ mathrm {join} $ (以haskell的语言),给出了可变类型的列表列表, $$ l= [[(a,a),(b,b)],[(c ,c),(d,d)]] :( \ sigma_ {x:\ mathcal u} x)\ \ text {list list} $$ 我们可以执行 $ \ mathrm {join} $ 获取新列表: $$ \ mathrm {join}(l)= [(a,a),(b,b) (c,c),(d,d)] :( \ sigma_ {x:\ mathcal u} x)\ \ text {list} $$
  • 类似地, $ \ tau \ \ text {list} $ 可以配备连接操作 $ + \! + $ ,所以给出了前面示例中的两个列表,我们可以将它们连接到类似的结果: $$ [(a,a),(b,b)] \ {+ \!+} \ [(c,c),(d,d)]= [(a ,a),(b,b),(c,c),(d,d)] :( \ sigma_ {x:\ mathcal u} x)\ \ text {list} $$

其他提示

no,这是不可能的,至少不是有用的方式。想想世代古典etagcode的类型。当每个元素具有相同类型时,head具有类型 $ \ tau \;\ mathsf {list} \ to \ tau $ 。如果没有该保证,则无法为世代odicetagcode编写一个相干类型。对于列表类型是有用的,我们希望能够绘制有关head的输出类型的有用的结论;这需要列表的所有元素都具有相同类型。

我想你可以定义一些“列表”一些方式,但它无论是没有用吗(你不能有理于你用head离开它的值类型)或者与计算机科学家称之为“列表”的东西不相反。

你不能有用地定义一个类型 $\mathsf{列表}$ 这并不表示其元素的类型。这并不意味着你不能有包含不同类型的东西的列表:它仍然是一个 $ au\,\mathsf{list}$, ,但你可以把"包含不同类型的东西"部分放在 $\头$.

(这些基本思想已经在 D.W.瓦尔科""回答。重要的是要认识到这些答案并不矛盾!他们着眼于大局的不同方面。)

如果类型系统允许您定义类型 $\mathsf{列表}$ 它可以包含任何类型的元素,然后考虑析构函数的返回类型,如 $\mathsf{head}$$\mathsf{nth}$, ,或函数参数的类型 math\mathsf{折叠}$.您没有关于元素类型的信息,因此它们必须允许任何类型。这意味着例如 $\lambda x。\mathsf{head}(\mathsf{cons}(x,\mathsf{nil}))$ 不会给你一个相同类型的值 $x$ (或 $x\,\mathsf{选项}$, ,使 $\mathsf{head}$ 可以返回 math\mathsf{None}$ 在空列表上)。但那你又能从中得到什么呢? $\mathsf{head}$?

  • 如果 $\mathsf{head}$ 允许调用者指定任何返回类型,那么类型系统几乎是无用的,因为它允许通过 $\lambda x。\mathsf{head}(\mathsf{cons}(x,\mathsf{nil}))$.这是无用的逻辑,因为 库里-霍华德通信 将类型之间的任意强制映射为每个命题都暗示每个其他命题,因此您有一个不一致的逻辑。
  • 如果不是,那么您无法通过以下方式取回原始类型的值 $\lambda x。\mathsf{head}(\mathsf{cons}(x,\mathsf{nil}))$.所以你可能能够构建列表,但你不能从中获取元素。

一个现实生活中的例子,实际上证明了上述两种行为是 早期版本Java语言, ,在它之前 泛型.Java既有静态类型系统,也有动态类型系统。在静态类型系统中,any1值可以透明地强制转换为 Object, ,因为 Object 被认为是一切的超类型。所以你可以把任何值放在一个 List.但是你从中得到的是最初的价值 Object, ,而不是原始值本身。在动态类型系统中,您可以将任何类型强制转换为任何其他类型,因此在实践中,要从列表中获取值,请将其强制转换为所需的类型。但强迫违背了类型系统的目的。这个问题是Java获取泛型的主要原因:他们允许语言有 $ au\,\mathsf{list}$ 而不是 $\mathsf{列表}$ (或者用Java表示法, List<T> 而不是 List).

仅仅因为一个列表有一个类型的元素 — $ au\,\mathsf{list}$ 是类型元素的列表 $\头$ -并不意味着你不能安排把不同类型的值放在同一个列表中。几乎任何允许定义列表类型的语言都是通过允许 代数数据类型 定义,类似这样的东西:$ $ au\,\mathsf{list}::=\mathsf{nil}\mid\mathsf{cons} \:\头 \:( au\,\mathsf{列表})$ $ 假设你想把整数和字符串放在同一个列表中。定义类型 $ $U::=\mathsf{i} \:[美][美][美][美][美][美][美][美][美][美] \:\mathsf{字符串}$ $ 现在 $U\,\mathsf{list}$ 列表的类型可以包含整数和字符串的混合,例如 $[\mathsf{I}(3),\mathsf{S}( exttt{"foo"}),\mathsf{I}(4)]$.

您可以在类型系统允许异构类型的程度上以这种方式制作异构列表。请注意,"异构列表"并不完全正确:列表本身是同质的:它是类型元素的列表 $U$.的异质性在型 $U$.要将元素放入列表中,您需要应用 $U$ 首先。从列表中获取元素后,应用 $U$ 以获取具有其原始类型的原始值。

您可以使用该语言支持的任何类型执行此操作。如果你想要一个完全异构的列表,你需要一种支持"任何"类型的语言。那是 Object 在Java中,例如。如果强类型在运行时携带必要的类型信息,则可以具有"any"类型。Java一直这样做。静态类型的语言(如OCaml和其他ML方言,Haskell,Clean,Swift或Rust)可以使用 $\mathsf{dyn}$ 其运行时表示形式包含值的类型的类型。用这样的类型, $\mathsf{dyn}\,\mathsf{list}$ 是可以包含任何类型的值的列表类型。此类型与其他列表类型共存,例如 $\mathsf{int}\,\mathsf{list}$ (其中列表元素不携带运行时类型信息)。

构建异构数据结构的一种相关方法是 存在类型.存在类型允许您使用该类型的值打包类型: $(\存在 au :P( au)。a)$ 哪里 $a$ 是某种类型的表达式 $T$ 这样, <P(T)> 是真的。例如, $\mathsf{dyn}$ 可以建模为一个特殊情况,其中 $P$ 所有类型都是如此(无界存在)。存在类型的一个常见用途是说 $\头$ 是具有某些特定元素或方法的记录,模块或类,而没有给出所有细节:存在类型是一种对抽象类型建模的方法。使用有界存在,即使没有运行时类型信息,您仍然可以使用该值做一些有用的事情(例如您可以调用以下方法 $P$ 描述),但不能获得原始类型。元素具有存在类型的列表 $T_E=(\exists au\ldots)$ 可以被看作是一个异构列表(因为它的元素具有不同的"真实"类型),但它仍然是同质的,因为如果你从列表中检索一个值,你所知道的只是它的包类型 $T_E$.

如果语言有 依赖类型, ,您可以以允许恢复原始值的方式将值与其类型打包: $\mathsf{package}::=\sum_{ au:\mathsf{TYPE}} au$ 哪里 $\mathsf{类型}$ 是类型的类型。这是一个 从属和类型 其中第一个组件恰好是一个类型。该 $\mathsf{包}$ 类型是一种在依赖类型语言中实现无界存在的方法。您可以通过在以下位置添加约束来构造有界存在 $\头$.再次,您可以在以下意义上构建异构列表: $\mathsf{package}\,\mathsf{list}$ 包含"真实"类型不同的元素,但列表本身是同质的,因为每个列表元素都具有该类型 $\mathsf{包}$.与存在类型一样,您无法从列表中提取值并直接恢复其"真实"类型。可以析构类型的值 $\mathsf{包}$ 通过应用第二元素投影,但所有你知道的结果是它的类型是第一元素投影: $p :\mathsf{包}\vdash\pi_2(p) :\pi_1(p)$.

到目前为止,我们已经看到,在非退化类型系统中,列表是同质的。可以构建异构列表,但列表类型构造函数本身是同质的:异质性来自元素类型。在具有代数数据类型和依赖于整数(或与自然同构的东西)的类型的语言中,可以定义真正的异质列表类型。给定一个类型族 $(T_n)_{n\in\mathbb{N}}$, ,您可以定义其列表的类型 $n$th元素具有类型 $T_n$.这是一个这样的定义。 归纳构造的微积分, ,特别是在Coq语法中。首先,我定义一个由整数索引的类型族的示例: tuple A n 是类型的 n-元素元组,其组件都具有类型 A.为了保持定义简单,所有元组都有一个附加值 U 在单元类型的开始。然后我定义归纳类型 hlist_ 它由两个类型族参数化 T 和一个整数 n, ,这是一个异构列表,其 kth元素具有类型 n + k.参数 n 是必要的,以保持定义建设性。最后我展示了一些类型的示例术语 hlist (tuple bool), ,即列出其 nth元素是一个 nth-元素元组 bool 值(与 U 预置)。

Inductive unit : Type := U : unit.
Fixpoint tuple (A : Type) (n : nat) : Type :=
  match n with
    | 0 => unit
    | S m => (tuple A m) * A
  end.

Inductive hlist_ (T : nat -> Type) n :=
  | Hnil : hlist_ T n
  | Hcons : (T n) -> hlist_ T (S n) -> hlist_ T n.
Definition hlist T := hlist_ T 0.

Check (Hcons (tuple bool) 0 U (Hnil _ _) : hlist (tuple bool)).
Check (Hcons (tuple bool) 0 U (Hcons _ 1 (U, true) (Hnil _ _)) : hlist (tuple bool)).
Check (Hcons (tuple bool) 0 U (Hcons _ 1 (U, true) (Hcons _ 2 (U, true, true) (Hnil _ _))) : hlist (tuple bool)).

¹ 实际上,除了一些原始数据类型,但这在这里并不重要。当我在这个答案中对Java说"任何"时,我的意思是仅对象,而不是原始数据类型。

许可以下: CC-BY-SA归因
不隶属于 cs.stackexchange
scroll top