为什么Scala的元组语法如此不寻常?


72

在数学和计算机科学中,元组是元素的有序列表。在集合论中,(有序的)n元组是n个元素的序列(或有序列表),其中n是正整数。

因此,例如,在Python中,可以通过访问元组的第二项t[1]

在Scala中,只能通过奇怪的名称进行访问t._2

所以问题是,为什么不能按定义访问序列或列表中的元组数据?是否有某种主意或尚未检查?

Answers:


92

Scala知道元组的复杂性,因此能够提供诸如_1_2等的访问器,并且,例如,如果_3在一对上选择,则会产生编译时错误。此外,这些字段的类型正是用作参数的类型Tuple(例如,_3在aTuple3[Int, Double, Float]上将返回a Float)。

如果要访问第n个元素,则可以编写tuple.productElement(n),但是该元素的返回类型只能是Any,因此您会丢失类型信息。


1
我记得读到,既然案例类继承是非法的,则可以加强类型的继承,productIterator以便在某些情况下可以使用更具体的类型。这可能是针对2.10版本的,但是如果我错了,有人可以纠正我。
Kipton Barros 2011年

1
编译器可能会允许像这样的表达式t[1]并保留所有类型信息,并简单地让它t[exp](在编译时不知道exp的结果)的返回类型为Any,对吗?因此,似乎语法上的不同更多是为了使程序员明白元组不是列表。
benzado 2011年

@benzado您t[exp]正是t.productElement(exp)`所做的。请注意,在Scala中,方括号仅用于类型参数。
让-菲利普·佩莱

@Kipton Barros:-Xexperimental在2.10中继中已经可以使用这种方式,但仅适用于案例类,不适用于元组。不知道为什么。
SOC

5
那为什么不支持apply()方法呢?List,Array和所有其他类型都支持它。似乎奇怪的是Tuple *不支持apply方法(这会使编写tupl(0),tupl(3)等变得如此容易)。恕我直言,这将使其与其他语言类型更加统一。
2013年

37

我相信“ Scala编程:全面的循序渐进指南”中的以下摘录(Martin Odersky,Lex Spoon和Bill Venners)直接解决了您的两个问题:

访问元组的元素

您可能想知道为什么您不能访问元组的元素(例如列表对),例如使用“ pair(0)”。原因是列表的apply方法始终返回相同的类型,但是元组的每个元素可能是不同的类型:_1可以具有一个结果类型,_2可以具有另一个结果类型,依此类推。这些_N数字是从1开始的,而不是从0开始的数字,因为其他语言使用静态类型的元组(例如Haskell和ML)以1开头是一个传统。

就语言语法而言,Scala元组几乎没有任何优惠待遇,除了表达式'(' a1, ..., an ')'被编译器视为scala.Tuplen(a1,...,an)类实例化的别名。否则,元组的行为与任何其他Scala对象一样,实际上,它们是在Scala中编写的,其案例类的范围从Tuple2到Tuple22。Tuple2和Tuple3也分别以Pair和Triple的别名已知:

 val a = Pair   (1,"two")      // same as Tuple2 (1,"two") or (1,"two") 
 val b = Triple (1,"two",3.0)  // same as Tuple3 (1,"two",3.0) or (1,"two",3.0)

6
Pair和Triple现在已弃用。他们在Scala 2.11中被弃用
Dave

1
同时,Tuple1已添加。
David Moles

20

之间的一个很大的区别ListSeq任何收集和元组或者是在元组的每个元素都有它自己的类型,其中在列表中的所有元素都具有相同的类型。

因此,在Scala中,您会发现类似Tuple2[T1, T2]或的类Tuple3[T1, T2, T3],因此对于每个元素,您也都具有类型参数。集合仅接受一种类型参数:List[T]。语法("Test", 123, new Date)只是语法糖Tuple3[String, Int, Date]。和_1_2等等只是元组上返回相应元素的字段。


1
这个答案比这里的其他答案要好得多。关键是Lists强制同质类型,而Tuples则不强制,这使得类型检查更加困难。
bcherny 2015年

12

您可以轻松地achive与无形的

import shapeless.syntax.std.tuple._

val t = ("a", 2, true, 0.0)

val s = t(0) // String at compile time
val i = t(1) // Int at compile time
// etc

很多可用于标准的收集方法也可用于元组这样(headtailinitlast++:::进行连结,+::+添加元素takedropreversezipunziplengthtoListtoArrayto[Collection],...)


7
一个可能的缺点是编译时间可能会更长,因为这涉及“类型级”编程,即通过类型系统和隐式表示的计算,并由scalac完成。既有优点也有缺点的另一个功能是,在无法编译时调用这些方法(例如,在上面的示例中调用t.take(5)),但是通常会收到一个奇怪的错误消息: scalac而不是清楚的解释。一点点无形的工作可能会使最后一点更好
Alex Archambault 2014年

8

我认为这是用于类型检查。正如delnan所说,如果您有一个元组t和一个索引e(一个任意表达式),则t(e)不会向编译器提供有关正在访问哪个元素的信息(即使对于该大小的元组来说是一个有效的元素也是如此)。当您通过字段名称(_2是有效的标识符,不是特殊的语法)访问元素时,编译器会知道您正在访问哪个字段以及它具有什么类型。像Python这样的语言实际上并没有类型,因此它们不是必需的。


7

使用普通索引访问,可以使用任何表达式,并且如果保证索引表达式的结果在范围内,则将花费大量精力检查编译时。使其成为一个属性,并为(1, 2)._3“免费”提供一个编译时错误。诸如在元组的项访问中仅允许整数常量之类的事情将是一个非常特殊的情况(丑陋且不必要,有些人说这很荒谬),并且还有一些工作要在编译器中实现。

例如,Python可以避免这种情况,因为它不会(无法)在编译时检查索引是否在范围内。


0

除了让·菲利普·佩莱特(Jean-Philippe Pellet)已经提到的好处外,这种表示法在数学中也很常见(请参见http://en.wikipedia.org/wiki/Tuple)。如果他们想引用元组的元素,很多讲师会在元组变量后附加索引。编写“带有索引n ”(指元组的第n个元素)的常用(LaTeX)表示法是_n。所以我发现它实际上非常直观。

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.