Кортежи
Кортеж — это упорядоченная коллекция элементов фиксированного размера. Размер кортежа и типы его элементов известны на стадии компиляции. Элементами кортежа могут быть другие типы, включая сами кортежи, и кортежи могут быть вложены в другие типы:
type quaternion = (float, float, float, float) // или (float*4)
type color_t = (uint8*3) // сокращение для (uint8, uint8, uint8)
type label_t = (string, color_t)
type graph_t = (int, int list) list
Кортежи формируются путём помещения двух или более элементов, разделённых запятыми, в круглые скобки:
val q: quaternion = (1.f, 0.f, 0.f, 0.f)
// явно указали тип, но это не обязательно
val magenta : color_t = (255u8, 0, 255u8)
// другое указание типа кортежа
val yellow : (uint8*3) = (255u8, 255u8, 128u8)
val label = ("car", magenta)
Как уже отмечалось, кортежи могут использоваться анонимно, без явного указания типа. Однако можно задать удобные синонимы, как показано выше.
Доступ к элементам кортежа осуществляется с помощью нотации tuple_expr.целочисленный_литерал. Можно также распаковывать кортежи:
val (q_re, qi, qj, qk) = q
val (r, g, b) = magenta
val channel_idx = 1
// правильный способ извлечь элемент кортежа
fun channel(c: color, idx: int) =
if idx == 0 {c.0}
else if idx == 1 {c.1}
else if idx == 2 {c.2}
else {throw OutOfRangeError}
val label_name = label.0
val label_color = label.1
val i = i1.0
Кортежи как короткие числовые векторы
В Фикус отсутствуют отдельные типы для коротких числовых векторов, точек, комплексных чисел, кватернионов, пикселей RGB и прочего. Вместо этого предлагается использовать кортежи. Например, кортеж типа (uint8, uint8, uint8) занимает 3 байта и столь же эффективен, как встроенный тип пикселя RGB, будь он доступен.
Для упрощения работы с такими типами в стандартной библиотеке Фикус определены базовые операции над подобными кортежами, в частности:
- арифметические операции:
- поэлементные:
.+,.-,.*,./ *над 2-компонентными кортежами дает произведение комплексных чисел,/- деление комплексных чисел*над 4-компонентными кортежами дает произведение кватернионов
- поэлементные:
- операции сравнения: кортежи сравниваются лексикографически
norm(): вычисляет корень суммы квадратов элементов кортежаdot(): скалярное произведениеcross(): векторное произведение для 3-компонентных кортежей: cross()- печать и преобразование в строку:
print(),string() - другие операции, полный список см. Builtins.fx.
Вложенные кортежи
Кортежи могут содержать произвольные элементы, включая другие кортежи. Доступ к элементам вложенных кортежей осуществляется аналогичным способом:
fun transform(Rt: ((double*3)*2), pt: (double*2)) =
(Rt.0.0*pt.0 + Rt.0.1*pt.1 + Rt.0.2,
Rt.1.0*pt.0 + Rt.1.1*pt.1 + Rt.1.2)
val a = 30*M_PI/180
val Rt = ((cos(a), -sin(a), 10.), (sin(a), cos(a), 0.))
val pt1 = (1., 0.)
println(transform(Rt, pt1))
Несмотря на возможное замешательство, вызванное наличием дробных чисел после кортежей, парсер решает проблему корректно. Пример иллюстрирует, что кортежи могут представлять небольшие матрицы, а не только векторы.
Изменение кортежей
До сих пор мы рассматривали способы построения и чтения кортежей. Теперь разберёмся, как их изменять. Кортежи — неизменяемые структуры данных, поэтому непосредственно заменить их элементы нельзя. Однако, если у вас есть переменная типа кортежа (var), вы можете присвоить ей новый кортеж:
var vec = (1.f, 0.f, 0.f)
// "Изменим" второй элемент, создав новый кортеж
vec = (vec.0, vec.1 + 0.1f, vec.2)
Поскольку это не совсем эффективно, компилятор допускает изменение отдельных элементов, имитируя полную замену кортежа новым:
var vec = (1.f, 0.f, 0.f)
// Меняем только второй элемент
vec.1 += 0.1f
Это не нарушает правила “неизменяемости кортежей”, а скорее служит оптимизацией с добавлением удобной синтаксической оболочки.
Эта оптимизация и удобный синтаксис применимы ко всем ситуациям, когда кортеж хранится в изменяемом расположении:
- в переменной
- в элементе массива
- на кортеж указывает ref-ссылка (см. раздел ref-ссылки)