【发布时间】:2016-01-31 06:59:31
【问题描述】:
Ceylon 对可能都被视为某种数组的事物有几个不同的概念:List、Tuple、Sequence、Sequential、Iterable、Array、Collection、@987654328 @等。这些类型有什么不同,我应该什么时候使用它们?
【问题讨论】:
标签: collections ceylon
Ceylon 对可能都被视为某种数组的事物有几个不同的概念:List、Tuple、Sequence、Sequential、Iterable、Array、Collection、@987654328 @等。这些类型有什么不同,我应该什么时候使用它们?
【问题讨论】:
标签: collections ceylon
开始了解这些基本知识的最佳地点是Ceylon tour。深入了解这些内容的地方是module API。查看source files for these 也会有所帮助。
像所有优秀的现代编程语言一样,前几个接口是超级抽象的。它们围绕一个正式成员构建,并通过一堆默认成员和实际成员提供其功能。 (在 Java 8 之前创建的编程语言中,您可能听说过这些称为“特征”,以将它们与只有正式成员且没有功能的传统接口区分开来。)
让我们从接口Category开始。它表示您可以询问“此集合是否包含此对象”的类型,但您不一定能够从集合中获取任何成员。它的正式成员是:
shared formal Boolean contains(Element element)
一个例子可能是一个大数的所有因子的集合——你可以有效地测试任何整数是否是因子,但不能有效地得到所有因子。
Category 的子类型是接口Iterable。它表示您可以一次获取每个元素的类型,但不一定索引元素。元素可能没有明确定义的顺序。这些元素甚至可能还不存在,而是动态生成的。收藏甚至可能无限长!它的正式成员是:
shared formal Iterator<Element> iterator()
例如标准输出这样的字符流。另一个例子是提供给 for 循环的整数范围,一次生成一个数字会更节省内存。
这是 Ceylon 中的一种特殊类型,可以分别缩写为 {Element*} 或 {Element+},具体取决于可迭代对象可能为空还是绝对不为空。
Iterable 的子类型之一是接口Collection。它有一个正式成员:
shared formal Collection<Element> clone()
但这并不重要。定义Collection 的重要内容是文档中的这一行:
所有
Collections 都需要支持明确定义的价值概念 平等,但平等的定义取决于 收藏。
基本上,Collection 是一个集合,其结构定义良好,足以相互等同且可克隆。对定义良好的结构的要求意味着这是最后一个超级抽象接口,其余的将看起来像更熟悉的集合。
Collection 的子类型之一是接口List。它表示一个集合,我们可以通过索引 (myList[42]) 获取其元素。当你的函数需要一个数组来取出东西时使用这种类型,但不关心它是可变的还是不可变的。它有一些正式的方法,但重要的一个来自它的另一个超类型Correspondence:
shared formal Item? get(Integer key)
List 的子类型中最重要的是接口Sequential。它代表一个不可变的List。 Ceylon 喜欢这种类型,并围绕它构建了很多语法。它被称为[Element*] 和Element[]。它有两个子类型:
Empty(又名[]),代表空集合Sequence(又名[Element+]),代表非空集合。因为集合是不可变的,所以你可以用它们做很多事情,而你不能用可变集合做。一方面,在空列表上使用null,许多操作可能会失败,例如reduce 和first,但是如果您首先使用test that the type is Sequence,那么您可以保证这些操作将始终成功,因为集合以后不会变为空(毕竟它们是不可变的)。
Sequence 的一个非常特殊的子类型是Tuple,这是这里列出的第一个真正的类。与Sequence 不同,其中所有元素都被限制为一种类型Element,Tuple 对每个元素都有一个类型。它在锡兰有特殊的语法,其中[String, Integer, String] 是一个不可变的列表,恰好包含三个元素,这些元素的类型完全按照该顺序排列。
List 的另一个子类型是Array,也是一个真正的类。这是熟悉的 Java 数组,一个可变的固定大小的元素列表。
【讨论】:
drhagen 已经很好地回答了你的问题的第一部分,所以我将在第二部分说一点:你什么时候使用哪种类型?
一般:在编写函数时,使其接受支持所需操作的最通用类型。到目前为止,很明显。
Category 非常抽象,很少有用。
Iterable 如果您期望一些您将要迭代的元素流(或使用流操作,如filter、map 等),则应使用。
关于Iterable 要考虑的另一件事是,它在命名参数中有一些额外的语法糖:
void printAll({Anything*} things, String prefix = "") {
for (thing in things) {
print(prefix + (thing?.string else "<null>"));
}
}
printAll { "a", "b", "c" };
printAll { prefix = "X"; "a", "b", "c" };
Iterable 类型的任何参数都可以在命名参数列表末尾以逗号分隔参数列表的形式提供。也就是说,
printAll { "a", "b", "c" };
等价于
printAll { things = { "a", "b", "c" }; };
这允许您制作 DSL 样式的表达式; tour 有一些很好的例子。
Collection 和Correspondence 一样,相当抽象,根据我的经验,很少直接使用。
List 听起来应该是常用的类型,但实际上我不记得经常使用它。我不确定为什么。我似乎跳过它并将我的参数声明为Iterable 或Sequential。
Sequential 和 Sequence 适用于您想要一个不可变的、固定长度的列表。它还有一些语法糖:像void foo(String* bar) 这样的可变参数方法是Sequential 或Sequence 参数的快捷方式。 Sequential 还允许您使用 nonempty 运算符,它通常与 first 和 rest 结合使用效果很好:
String commaSeparated(String[] strings) {
if (nonempty strings) {
value sb = StringBuilder();
sb.append(strings.first); // known to exist
for (string in strings.rest) { // skip the first one
sb.append(", ").append(string);
// we don’t need a separate boolean to track if we need the comma or not :)
}
return sb.string;
} else {
return "";
}
}
当我要对流进行多次迭代时,我通常使用Sequential 和Sequence(这对于通用Iterable 来说可能很昂贵),尽管List 可能是更好的接口。
Tuple 永远不应用作 Tuple(除非在极少数情况下您对它们进行抽象),但使用 [X, Y, Z] 语法糖通常很有用。您通常可以在子类中将Sequential 成员细化为Tuple,例如。 G。超类有一个<String|Integer>[] elements,它在一个子类中被称为[String, Integer] elements。
Array我从来没有用作参数类型,只是很少用作类来实例化和使用。
【讨论】:
printAll 示例如何工作。您写道“任何类型的 Iterable 参数都可以在命名参数列表的末尾以逗号分隔的参数列表的形式提供”。但是在调用printAll时怎么可能呢,看到命名参数prefix 出现在函数定义中命名参数strings之后。
prefix 是可选的,所以你可以省略它。命名参数列表中的逗号分隔参数列表始终适用于第一个未分配的Iterable 参数,即things。在这种情况下,things 和 prefix 的顺序直接无关紧要(尽管由于不同的原因仍然无法交换参数 - 您不能在可选参数之后具有必需参数(things) (prefix))。
printAll { "a", "b", "c" };
void printAll({Anything*} things, String prefix = "") { for (thing in things) { print(prefix + (thing?.string else "<null>")); } }
f { argument; }),位置参数列表是用括号括起来的 (f(argument))。您可以使用任何一种语法调用任何¹ 函数,这取决于您。 (¹至少在纯锡兰;与其他语言的互操作可能会变得很挑剔)