Почему синтаксис 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