【问题标题】:Why is Scala's syntax for tuples so unusual?为什么 Scala 的元组语法如此不同寻常?
【发布时间】:2011-10-16 14:12:08
【问题描述】:

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

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

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

所以问题是,如果按照定义,为什么我不能以序列或列表的形式访问元组中的数据?是否有某种想法或尚未检查?

【问题讨论】:

    标签: scala tuples iterable-unpacking


    【解决方案1】:

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

    如果要访问第n个元素,可以写tuple.productElement(n),但是this的返回类型只能是Any,所以丢失了类型信息。

    【讨论】:

    • 我记得读过,现在案例类继承是非法的,可以加强productIterator 的类型,以便在某些情况下使用更具体的类型。这可能会在 2.10 中推出,但如果我错了,请有人纠正我。
    • 编译器可以允许t[1]这样的表达式并保留所有类型信息,并简单地让t[exp](其中exp的结果在编译时未知)有一个返回Any 的类型,对吧?所以看起来语法不同是为了让程序员明白元组不是列表。
    • @benzado You t[exp] 正是 t.productElement(exp)` 所做的。请注意,方括号在 Scala 中仅用于类型参数。
    • @Kipton Barros:它已经在 2.10 主干中与 -Xexperimental 一起工作,但仅适用于案例类,不适用于元组。不知道为什么。
    • 那为什么不支持 apply() 方法呢? List、Array 和所有其他类型都支持它。 Tuple* 不支持 apply 方法似乎很奇怪(这会使编写 tupl(0)、tupl(3) 等变得更容易......)。恕我直言,这会使它与语言中的其他类型更加统一。
    【解决方案2】:

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

    访问元组的元素

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

    就语言语法而言,Scala 元组很少受到优先处理,除了表达式 '(' a1, ..., an ')' 被编译器视为 scala.Tuplen(a1, ..., an em>) 类实例化。否则,元组的行为与任何其他 Scala 对象一样,实际上它们在 Scala 中编写为case classes that range from Tuple2 to 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)
    

    【讨论】:

    • Pair 和 Triple 现已弃用。它们在 Scala 2.11 中已被弃用
    • 同时,Tuple1 已添加。
    【解决方案3】:

    ListSeq 或任何集合和元组之间的一大区别是,在元组中,每个元素都有自己的类型,而在 List 中,所有元素都具有相同的类型。

    因此,在 Scala 中,您会找到 Tuple2[T1, T2]Tuple3[T1, T2, T3] 之类的类,因此对于每个元素,您还具有类型参数。集合仅接受 1 个类型参数:List[T]。像("Test", 123, new Date) 这样的语法只是Tuple3[String, Int, Date] 的语法糖。而_1_2 等只是返回对应元素的元组上的字段。

    【讨论】:

    • 这是一个比这里的其他答案更好的答案。关键是 Lists 强制执行同质类型,而 Tuples 没有,这使得类型检查更加困难。
    【解决方案4】:

    您可以使用shapeless 轻松实现:

    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++::: 用于连接,+: 和 @ 987654330@ 用于添加元素,takedropreversezipunziplengthtoListtoListtoArrayto[Collection],...)

    【讨论】:

    • 一个可能的缺点是编译时间可能更长,因为这涉及“类型级”编程,即通过类型系统和隐式表示并由 scalac 完成的计算。一个既是优点也是缺点的附加功能是,当您无法编译时调用这些方法(例如,在上面的示例中调用 t.take(5)),但您通常会收到一条奇怪的错误消息scalac 而不是一个清晰的解释。不过,在 shapeless 中做一点工作可能会使最后一点变得更好
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      通过正常的索引访问,可以使用任何表达式,并且在编译时检查索引表达式的结果是否保证在范围内需要花费一些精力。使其成为一个属性,(1, 2)._3 的编译时错误遵循“免费”。像只允许在元组的项目访问中使用整数常量之类的事情将是一种非常特殊的情况(丑陋且不需要,有些人会说荒谬),并且需要在编译器中实现一些工作。

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

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

          从 Scala 3 开始,tuples 可以使用 apply 方法以类型安全的方式进行索引,该方法通过匹配类型、基于文字的单例类型和依赖函数类型等设施实现

          /** Get the i-th element of this tuple.
           *  Equivalent to productElement but with a precise return type.
           */
          inline def apply[This >: this.type <: NonEmptyTuple](n: Int): Elem[This, n.type] =
            runtime.Tuples.apply(this, n).asInstanceOf[Elem[This, n.type]]
          

          给予

          val t = (42, 3.14, "woohoo")
          // t: Tuple3[Int, Double, String] = (42,3.14,woohoo)
          t(0)
          // res0: Int = 42
          t(1)
          // res1: Double = 3.14
          t(2)
          // res2: String = woohoo
          

          注意根据特定索引处的元素类型而变化的精确类型。同样越界索引t(3) 会导致编译时错误

          t(3)
            ^
            index out of bounds: 3
          

          【讨论】:

          • 可能值得注意的是,索引Int 必须是编译时值。这不起作用:t("1".toInt)
          猜你喜欢
          • 1970-01-01
          • 2014-05-09
          • 2021-12-30
          • 2019-04-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-04-29
          相关资源
          最近更新 更多