Почему синтаксис Scala для кортежей такой необычный?

В математике и информатике кортеж - это упорядоченный список элементов. В теории множеств (упорядоченный) n-кортеж - это последовательность (или упорядоченный список) из n элементов, где n - положительное целое число.

Так, например, в Python 2-й элемент кортежа будет доступен через t[1],

В Scala доступ возможен только через странные имена t._2,

Итак, вопрос в том, почему я не могу получить доступ к данным в кортежах как Sequence или List, если это по определению? Есть какая-то идея или просто еще не проверена?

8 ответов

Решение

Scala знает артикль кортежей и, таким образом, может предоставить такие средства доступа, как _1, _2и т. д., и выдает ошибку времени компиляции, если вы выберете _3 на пару, например. Более того, тип этих полей в точности соответствует типу, используемому в качестве параметра для Tuple (например _3 на Tuple3[Int, Double, Float] вернет Float).

Если вы хотите получить доступ к n-му элементу, вы можете написать tuple.productElement(n), но тип возвращаемого значения может быть только Any, так что вы теряете информацию о типе.

Я полагаю, что следующий отрывок из "Программирование в Scala: всеобъемлющее пошаговое руководство" (Мартин Одерский, Лекс Спун и Билл Веннерс) непосредственно отвечает на оба ваших вопроса:

Доступ к элементам кортежа

Вы можете задаться вопросом, почему вы не можете получить доступ к элементам кортежа, таким как элементы списка, например, с "pair(0)". Причина заключается в том, что метод apply списка всегда возвращает один и тот же тип, но каждый элемент кортежа может иметь другой тип: _1 может иметь один тип результата, _2 другой и т. Д. Эти числа _N основаны на единицах, а не на нулях, потому что начинание с 1 является традицией, установленной другими языками со статически типизированными кортежами, такими как Haskell и ML.

Для кортежей Scala предпочтение отдается синтаксису очень мало, кроме выражений '(' a1, ..., an ')' рассматривается компилятором как псевдоним для экземпляра класса scala.Tuplen (a1,..., an). В противном случае кортежи ведут себя как любые другие объекты Scala, фактически они записываются в Scala как классы case, которые варьируются от 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)

Одна большая разница между List, Seq или любая коллекция и кортеж состоит в том, что в кортеже каждый элемент имеет свой собственный тип, где в списке все элементы имеют одинаковый тип.

И, как следствие, в Scala вы найдете такие классы, как Tuple2[T1, T2] или же Tuple3[T1, T2, T3], поэтому для каждого элемента у вас также есть параметр типа. Коллекции принимают только 1 тип параметра: List[T], Синтаксис как ("Test", 123, new Date) это просто синтаксический сахар для Tuple3[String, Int, Date], А также _1, _2и т. д. - это просто поля в кортеже, которые возвращают соответствующий элемент.

Вы можете легко достичь этого с бесформенным:

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

Таким образом, для кортежей доступно множество методов для стандартной коллекции (head, tail, init, last, ++ а также ::: для объединения, +: а также :+ для добавления элементов, take, drop, reverse, zip, unzip, length, toList, toArray, to[Collection]...)

Я думаю, что это для проверки типов. Как говорит Делнан, если у вас есть кортеж t и индекс e (произвольное выражение), t(e) не даст компилятору никакой информации о том, к какому элементу осуществляется доступ (или даже если это допустимый элемент для кортежа такого размера). При доступе к элементам по имени поля (_2 является допустимым идентификатором, это не специальный синтаксис), компилятор знает, к какому полю вы обращаетесь и какой у него тип. Такие языки, как Python, на самом деле не имеют типов, поэтому им это не нужно.

При обычном доступе к индексу можно использовать любое выражение, и во время компиляции потребовались бы серьезные усилия, чтобы убедиться, что результат выражения индекса находится в диапазоне. Сделайте это атрибутом, и ошибка времени компиляции для (1, 2)._3 следует "бесплатно". Такие вещи, как разрешение только целочисленных констант внутри доступа к элементам в кортежах, были бы очень особым случаем (некрасивым и ненужным, некоторые сказали бы смешным), и опять-таки некоторые работы для реализации в компиляторе.

Например, Python может обойтись без этого, потому что он не будет (не может) проверять (то есть во время компиляции), находится ли индекс в любом случае в диапазоне.

Помимо преимуществ, которые уже упоминал Жан-Филипп Пелле, эта запись также очень распространена в математике (см. http://en.wikipedia.org/wiki/Tuple). Многие лекторы добавляют индексы к переменным кортежа, если они хотят ссылаться на элементы кортежа. И общая (LaTeX) нотация для записи "с индексом n" (ссылаясь на n-й элемент кортежа) имеет вид _n, Поэтому я нахожу это на самом деле очень интуитивным.

Начальные кортежи Scala 3 могут быть проиндексированы безопасным для типов способом с использованием метода 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
Другие вопросы по тегам