Потоки
Люди
Инвентарь
Стиль
Гараж
Кухня
Спорт
Игры
Кино
Музыка
Дело
Отдых
Остальное
donats
Поиск
Перейти к содержимому
Храм
Участники Активность События Галерея Форум Спецпроекты Касса
Активность Участники Группы Заведения Рейтинги
Об этом блоге
Блог пользователя
Daviiiiiiid
  • 4
    записи
  • 2
    комментария
  • 315
    просмотров

В предыдущей части я рассмотрел реализации кортежей в различных языках программирования (причем я рассматривал компилируемые не скриптовые языки со статической типизацией и классическим си-подобным синтаксисом, что вызвало удивление у некоторых читателей). В этой части я предлагаю выйти за рамки существующего и заняться по сути дизайном языка программирования. Исходные данные — такие же: компилируемый не скриптовый язык со статической типизацией и си-подобным синтаксисом, включающий императивную парадигму (хотя и не ограничивающийся ею разумеется).
Итак, попробуем помечтать и поэкспериментировать — а что вообще можно сделать с кортежами? Как выжать из них максимум возможностей? Как с их помощью сделать язык программирования мощнее и выразительнее, как вызвать восхищение у истинных Хакеров Кода и при этом не слишком запутать обычных программистов? Какие неожиданные возможности появляются в языке, если правильно и грамотно экстраполировать семангтику кортежей в разных направлениях, и какие затруднения при этом возникают?
Итак, если вам нравится размышения и холивары на тему дизайна языков программирования, то прошу под кат.

Записи в этом блоге

astafevm

Чем больше строк кода написано, тем реже хочется дублировать код, а чем больше проектов реализовано, тем чаще обходишь старые, хоть и зачастую любимые, грабли, и начинаешь все больше интересоваться архитектурными решениями.

Думаю, достаточно не привычно рядом с Android встретить горячо любимый iOS-разработчиками архитектурный шаблон VIPER, мы тоже первое время пропускали мимо ушей разговоры из соседнего отдела по этому поводу, пока вдруг не обнаружили, что стали невольно использовать такой шаблон в своих Android приложениях.

Как такое могло произойти? Да очень просто. Поиски изящных архитектурных решений начались еще за долго до Android приложений, и одним из моих любимых и незаменимых правил всегда было — разделение проекта на три слабосвязанных слоя: Data, Domain, Presentation. И вот, в очередной раз изучая просторы Интернета на предмет новых веяний в архитектурных шаблонах для Android приложений, я наткнулась на великолепное решение: Android Clean Architecture, здесь, по моему скромному мнению, было все прекрасно: разбиение на любимые слои, Dependency Injection, реализация presentation layer как MVP, знакомый и часто используемый для data layer шаблон Repository.

Но помимо давно любимых и знакомых приемов в проектировании было место и открытиям. Именно этот проект познакомил меня с понятием Interactor (объект содержащий бизнес логику для работы с одной или несколькими сущностями), а так же именно здесь мне открылась мощь реактивного программирования.

Реактивное программирование и в частности rxJava достаточно популярная тема докладов и статей за прошедший год, поэтому вы без труда сможете ознакомится с этой технологией (если конечно, вы еще с ней не знакомы), а мы продолжим историю о VIPER.

Знакомство с Android Clean Architecture привело к тому, что любой новый проект, а так же рефакторинг уже существующих сводился к трехслойности, rxJava и MVP, а в качестве domain layer стали использоваться Interactors. Оставался открытым вопрос о правильной реализации переходов между экранами и здесь все чаще стало звучать понятие Router. Сначала Router был одинок и жил в главной Activity, но потом в приложении появились новые экраны и Router стал очень громоздким, а потом появилась еще одна Activity со своими Fragments и тут пришлось подумать о навигации всерьез. Вся основная логика, в том числе переключение между экранами, содержится в Presenter, соответственно Presenter-у необходимо знать о Router, который в свою очередь должен иметь доступ к Activity для переключения между экранами, таким образом Router должен быть для каждой Activity свой и передаваться в Presenter при создании.

И вот как-то в очередной раз глядя на проект пришло понимание, что у нас получился V.I.P.E.R — View, Interactor, Presenter, Entity and Router.

astafevm

AMP — наверняка, вы уже слышали об этой технологии, продвигаемой Google. Казалось бы, еще одна модная технология для хипстеров, о которой скоро все забудут. Однако, в реальности эта фича уже работает в продакшне значительного числа новостных сайтов, среди которых такие гиганты, как the Guardian, Times, Washington Post, и прочая, прочая, прочая. Краткий рассказ о плюшках AMP уже был на страницах “Хабра”, а я хотел бы чуть более подробно сфокусироваться на том, как внедрять это в проект, и какой профит в действительности можно получить.

 

Очень кратко об том, как это работает


AMP — это технология, которая позволяет ускорить загрузку мобильных страниц. По своей сути это тот же самый HTML, но не просто HTML, а хитро оформленный по специальным правилам. Отрисовка страницы осуществляется при помощи специального скрипта (AMP runtime), который берёт на себя оптимизацию рендеринга.

Выполнение прочих скриптов на странице запрещено, чтобы своими изменениями DOM’а они не мешали нам оптимизировать. Это не значит, что на ваших страницах не будет интерактивности. Её можно сделать, но опять-таки по специальным правилам (наприме, в специальном компоненте). 
Кроме того, гугл бот при парсинге обнаруживает AMP страницы, кеширует их содержимое и в дальнейшем будет отдавать их быстрее. 

В выдаче Google AMP-ресурсы видны в специальной карусели в верхней части страницы. 

dbf3b70666544bceb5fab3dd63ee3f93.png

Если же говорить об автоматическом редиректе на amp-версию при прямом заходе по ссылке, то поддержка браузерами amp-страниц и автоматический переход на них пока еще не реализована, а только ожидается. AMP хорош именно для контентных страниц, где нет значительного количества поведенческих скриптов (впрочем, различную интерактивную инфографику, мини-игры и т.п. мы можем реализовать внутри iframe).

Всё вышеперечисленное привело нас к намерению сделать AMP у себя. И вот как это было:
 

Шаг 1 — создаем новые шаблоны


На бэкенде мы используем Rails, в качестве шаблонизатора — Slim. Первым делом мы создаем новые пути в routes.rb для интересующих нас сущностей (в данном случае это будут новость и статья — основной контент нашего сайта). Принципиально не важно, какой именно адрес будет у amp — версии страниц — это может быть как поддомен amp.youpage.com/article, так и, например, youpage.com/article/amp, лишь бы схема была единообразной.

Затем мы создаем новые шаблоны для рендеринга AMP- версии наших страниц, и смело удаляем из них все лишнее. 

Под лишним подразумеваются следующие вещи:
— Javascript-код
— Inline-стили
— Запрещенные в amp-html теги, как-то: object, frame, embed, select, form, input, option, textarea (впрочем, насчёт input спецификация еще может поменяться — следите за развитием проекта.)

В общем, надо выпилить всё, что усложняет рендеринг страницы или же может его блокировать.
 

def amp_strip_tags(text = '')
    text
      .gsub(%r{<img(.+?)\/>}, '')
      .gsub(%r{<iframe.+?\/iframe>}m, '')
      .gsub(%r{<script.+?<\/script>}m, '')
end



Особое внимание хотелось бы уделить head наших страничек. Он должен иметь достаточно явно определенную структуру, вот такую:
 

Простыня html


Обратите внимание, что все скрипты подключаются с атрибутом async. 
 

Шаг 2 — Перелопачиваем стили


В зависимости от реализации, мы можем сделать AMP-версию максимально похожей на обычную мобильную, или же полностью редизайнить её. Мы выбрали первый путь. Итак, для процессинга CSS мы используем Stylus, и БЭМ-style нэйминг. На мобильной версии мы имеем примерно такой набор файлов:
 

Длинный список


Очевидно, что часть из них нам не потребуется. Поэтому в таблицу стилей для AMP войдут не все, а кроме того, вероятно, нам потребуются кое-какие исправления.
В частности, внутри стилей нельзя применять:
— Универсальный селектор * и :not()
— overflow: scroll, overflow: auto (нельзя делать скроллящиеся блоки)
— filter
— !important

Такой у нас получается корневой amp.styl:

@import 'reset'
@import 'variables'
@import 'mixins'
@import 'fonts'
@import 'general'
@import 'amp/icons'
@import 'amp/blocks/**/*.styl'
@import 'elements/button'
_apply_media_cache()


Избавившись от вышеперечисленного, и подправив стилевое оформление нужным образом, получившиеся стили надо вывести внутри получившейся страницы. Сборщик у нас webpack, и получившийся css — файл мы вставляем в шаблоне через такой хелпер: 
 

def amp_stylesheet_content
    css_file_path = Rails.root.join('public').to_s + URI(stylesheet_path('amp.css')).path
    File.read(css_file_path)
end



Внутри head:
 

<style amp-custom>
  =amp_stylesheet_content.html_safe
</style>



(В данном случае мы используем raw html, так как slim не умеет при стандартных настройках создавать атрибуты без значения)

При этом важно, чтобы размер стилевого файла не превысил 50 кБ. В общем-то этого достаточно, к тому же не забываем о минификации. У нас стили заняли 35 кБ.
 

Шаг 3 — Медиа-контент должен быть внутри AMP-компонентов


В основном медиаконтент новостного сайта можно разделить на 4 категории:
— Изображения (возможно, серии изображений — галереи)
— Видео
— Виджеты социальных сетей
— Специальные интерактивы (например, мини-игры, инфографика и тому подобное)

Для того, чтобы выводить всё это, в AMP предусмотрены различные компоненты.

Итак: для изображений у нас есть очень похожий на HTML5 picture amp-img:
 

<amp-img alt="Екатерина Проничева" width="420px" height="280px" src="http://icdn.lenta.ru/images/2016/02/11/12/20160211121428311/pic_8dc2c0121a05e635e509d1981d4fda63.jpg" layout="responsive"></amp-img>



А также amp-carousel (годится не только для изображений, но и для прочего контента) 
 

<amp-carousel width=300 height=400>
  <amp-img src="my-img1.png" width=300 height=400></amp-img>
  <amp-img src="my-img2.png" width=300 height=400></amp-img>
  <amp-img src="my-img3.png" width=300 height=400></amp-img>
</amp-carousel>



и amp-lightbox (обычный лайтбокс)
 

<button on="tap:my-lightbox">Хочу увидеть всё!</button>

<amp-lightbox id="my-lightbox" layout="nodisplay">
  <div class="lightbox">
    <amp-img src="my-full-image.jpg" width=300 height=800 on="tap:my-lightbox.close">
  </div>
</amp-lightbox>



Для видео — amp-video
 

<amp-video width=400 height=300 src="https://supervideo.com/videos/myvideo.mp4"
    poster="fallback-poster.jpg">
  <div fallback>
    <p>Ooops! Sorry, your browser doesn’t support HTML5 video!</p>
  </div>
  <source type="video/mp4" src="foo.mp4">
  <source type="video/webm" src="foo.webm">
</amp-video>



И разнокалиберный набор виджетов для популярных социальных сетей ( к сожалению, среди них пока отсутствует VK.)

Для замены удобно использовать свой хелпер:
 

def amp_img(cloud_unit, version, params={})
    if cloud_unit.versions.present? && cloud_unit.versions[version.to_s].present?
      version = cloud_unit.versions[version.to_s]

      if cloud_unit.caption.present?
        alt = cloud_unit.caption
      elsif cloud_unit.alt.present?
        alt = cloud_unit.alt
      else
        alt = ''
      end

      attrs = {
        alt:  strip_tags(alt),
        width: version.width.to_s + 'px',
        height: version.height.to_s + 'px',
        src: cloud_url(version.rel_url),
        class: params[:addclass],
        layout: params[:layout]
        #sizes: "(min-width: 650px) 50vw, 90vw"
      }
      content_tag('amp-img', nil, attrs)
    end
  end



Для того, чтобы редактору было удобно работать с контентом, в админке имеются т.н. “виджеты” — боксы, куда можно вставлять по выбору различный контент. Например, для вставки -instagram или -facebook поста редактору достаточно вставить ссылку на него, из которой затем формируется полноценный код виджета. Нам необходимы новые view для виджетов amp-версии, что достаточно просто — вытащенные регуляркой значения (например, код youtube-ролика) подставляются в шаблон соответствующего amp-компонента, который затем выводится на странице.

Кроме того, необходимо подключить библиотеки требуемых компонентов внутри head.

Важный момент, на котором стоит заострить внимание — для ускорения рендеринга размеры медийных элементов должны быть определены заранее, поэтому мы устанавливаем явно ширину и высоту (реальную) для изображений и виджетов.

Если мы хотим внедрить в страницу какой-то совсем специальный контент, то мы можем воспользоваться amp-iframe и реализовать в нём любое, самое причудливое поведение, не будучи ограниченными в использовании js.

Есть ряд ограничений на его использование — например, контент внутри amp-iframe может сервиться только через https, кроме того, amp-iframe не должен располагаться, в первом экране. Ну, и конечно, содержимое не может обращаться к DOM страницы, и тем более как-то его изменять. Все, что происходит в песочнице — останется в песочнице.
Загрузка элементов происходит асинхронно, так что, скорее всего, при плохом коннекте вы сначала не увидите их содержимого, а только placeholder (который, кстати можно кастомизировать или же вообще отключить). Зато после того, как контент загрузится, он станет виден безо всякого FOUC! 
Вместо виджетов социальных кнопок приходится применить обычный noscript-вариант. Вот, например, для VK:
 

.b-socials__button
        a.b-socials__image.b-socials__image-vk href="http://vk.com/share.php?url=#{extlink}" target='_blank' title=("Поделиться ссылкой во Вконтакте")


 

Шаг 4 — Микроразметка


Для AMP-страниц Google настоятельно требует введение микроразметки. На самом деле, значительная часть сайтов уже использует такую разметку, если же у вас ее пока нет — то самое время начать!
В качестве словаря используется schema.org, в данном случае — под наши цели отлично подходит сущность NewsArticle. Вставить разметку в страницу можно при помощи JSON-LD (на мой взгляд, наиболее удобный способ) или используя microdata — специальные атрибуты, устанавливающиеся для тегов с определённым контентом. Все необходимые данные для этого, скорее всего, у вас уже есть, и остается только подставить их в шаблон.
 

Шаг 5 — Аналитика и реклама


Как вы, вероятно, замечали, любой крупный проект начинает обрастать различными счетчиками и метриками, как корабль — морскими ракушками, с аналогичным действием — замедляет его. Для того, чтобы как-то бороться с этим нежелательным явлением, мы можем применить компонент amp-pixel, который, как ясно из названия, предоставляет функционал обычного счетчика — отправляет запрос на указанный адрес при показе страницы.
 

<amp-pixel src="https://foo.com/pixel?RANDOM"></amp-pixel>



Очень удобно — вместо RANDOM будет подставляться рандомное число.
Также есть нативный компонент Google Analytics — amp-analytics 
 

<amp-analytics id="analytics1" type="googleanalytics"><script type="application/json">{
  "vars": {
    "account": "UA-*******"
  },
  "triggers": {
    "trackPageview": {
      "on": "visible",
      "request": "pageview"
    }
  }
}</script></amp-analytics>



В принципе, мы решили, что стоит заменить гору счетчиков одним Google Analytics, однако если для вас этот вариант непозволителен, то возможности есть.

Что касается рекламы, то в текущий момент поддерживаются в основном системы доставки рекламного контента, распространенные в США. Чтобы подружиться, например, с adFox, можно сделать вот что: получить ссылку на содержимое баннера и разместить ее внутри amp-iframe заданного размера. Таким образом, реклама придет асинхронно и никогда не сможет помешать загрузке странички. 

В рамках нашего проекта рекламу в AMP мы пока не внедряли. Наслаждайтесь свободой :)
 

Шаг 6 — Проверяем, все ли у нас хорошо


После того, как вы успешно собрали ваши AMP-версии страниц, необходимо проверить, все ли соответствует спецификации, чтобы поисковый бот Google обрабатывал их и включал в поисковую выдачу.

Во-первых, прямо на локальной машине или dev-сервере можно открыть проверяемую страничку с хэш-параметром #development=1, а затем заглянуть в developer console.

Если все OK, вы увидите примерно такое сообщение:

84aa22ca61834c05b50ea8a816d5cd17.png

Если же нет, встроенный валидатор покажет, где обнаружены ошибки, и даже приведёт ссылки на документацию:

fec9d50b326546c29cc490231aa66869.png

После того, как вы выкатились в production, загляните сюда, и проверьте, что microdata парсится нормально и имеет необходимые поля.

Кроме того, в инструментах вебмастера Google вы можете найти вкладку “Страницы для мобильных устройств”, где собирается статистика по проиндексированным страницам, там вы сможете найти в удобном виде статистику и информацию об ошибках по всему сайту, если таковые появятся, но будьте внимательны — индексирование происходит довольно нечасто (раз в несколько дней), так что после внесения изменений придется подождать.
 

Замеры скорости


Скорость загрузки amp-версий страниц значительно выше, притом, чем хуже качество соединения, тем более заметна разница. В целом, можно говорить о как минимум 2х-кратном росте скорости загрузки. Сравним при помощи tools.pingdom.com показатели обычной мобильной странички:

07f758cbdc674a77b9edaec041b2539b.png

и amp-версии:

b7e3f8925d5e4f7688a19d49401aaf6b.png

Разница, как говорится, налицо. Особенно интересно рассмотреть waterfall загрузки ресурсов и сравнить (эту задачу оставлю для интересующихся читателей).

Для подробностей в нюансах рекомендую посмотреть официальный github проекта и документацию.

astafevm

Сейчас во многих языках программирования существует такая конструкция, как кортежи (tuples). Где-то кортежи в той или иной мере встроены в язык, иногда — опять же в той или иной мере — реализуются средствами библиотек. C++, C#, D, Python, Ruby, Go, Rust, Swift (а также Erlang, F#, Groovy, Haskell, Lisp, OCaml и многие другие)…
Что же такое кортеж? В Википедии дается достаточно точное определение: кортеж — упорядоченный набор фиксированной длины. Определение хоть и точное, но для нас пока бесполезное, и вот почему: задумывается ли большинство программистов, зачем понадобилась эта сущность? В программировании существует множество структур данных, как фиксированной, так и переменной длины; они позволяют хранить различные значения — как однитипные, так и разных типов. Всевозможные массивы, ассоциативные массивы, списки, структуры… зачем еще и кортежи? А в языках со слабой типизацией — и тем более, разница между кортежами и списками/векторами совсем размытая… ну нельзя добавлять в кортеж элементы, ну и что с того? Это может ввести в некоторое заблуждение. Поэтому стоит копнуть глубже и разобраться, зачем же на самом деле нужны кортежи, чем они отличаются от других языковых конструкций, и как сформировать идеальный синтаксис и семантику кортежей в идеальном (или близком к идеальному) языке программирования.

В первой части мы рассмотрим кортежи и кортежеподобные конструкции в распространенных и не очень языках программирования. Во второй части я попытаюсь обобщить и расширить и предложить наиболее универсальный синтаксис и семантику кортежей.

Первая важная вещь, о которой на Википедии не упомянули: кортеж — это структура времени компиляции. Иными словами, это некая сущность, объединяющая некоторые объекты на этапе компиляции. И это очень важно. Кортежи неявно используются во всех языках программирования, даже в Си и Ассемблере. Давайте поищем их в том же Си, С++, в любом компилируемом языке.
Так, список аргументов функции — это кортеж; 
Список инициализации структуры или массива — это тоже кортеж;
Список аргументов шаблона или макроса — тоже кортеж
Описание структуры, и даже обычный блок кода — это тоже кортеж; только элементами его являются не объекты, а синтаксические конструкции.

Кортежей в программе гораздо больше чем кажется на первый взгляд. Но они все неявные; так или иначе они являются жестко прикрученными к каким-то синтаксическим конструкциям. Явное использование кортежей в старых языках было не предусмотрено. В более современных языках некоторые возможности явного использования стали появляться — но далеко не все. Здесь мы будем рассматривать в основном кортежи значений — или переменных, или констант. Возможно, в следующих частях я рассмотрю и кортежи произвольных синтаксических элементов.

Начнем с самого очевидного — возврата нескольких значений из функции. Еще со школьных времен меня удивляла такая несправедливость: почему функция может принимать сколько угодно значений, а возвращать только одно? В самом деле, почему y=x*x — нормальная парабола, а y = sqrt(x) — какая-то обрезанная наполовину фигня? Разве это не нарушение математической гармонии? В программировании конечно можно возвратить структурный объект, но суть остается та же: возвращается один объект, а не несколько.

Непосредственная реализация множественного возврата есть в Go. Функция может явно вернуть несколько значений. Синтаксис позволяет присвоить эти несколько значений нескольким переменным, а также выполнять групповые присваивания и даже перестановки аргументов за одну операцию. Однако, никаких других групповых действий кроме присваивания не предусмотрено.

func foo() (r1 int, r2 int) {
     return 7, 4
}
x, y := foo()
x, y = 1, 2
x, y = y, x


Интересная возможность, на которую следует обратить внимание — «пакетная» передача нескольких возвращаемых значений одной функции в другую функцию.

func bar(x int, y int) {
}
bar(foo())



Такая пакетная передача сама по себе крайне интересна. С одной стороны, она кажется весьма элегантной; но с другой — она слишком «неявная», неуниверсальная. Например, если попытаться добавить в bar третий аргумент, и попытаться совместить «пакетную» передачу и обычную

bar(foo(), 100)


то ничего не получится — ошибка компиляции. 

Еще один интересный аспект — неиспользование возвращаемых значений. Вспомним С/С++. В них (а также в подавляющем большинстве других языков — Java, C#, ObjC, D...) можно было спокойно игнорировать возвращаемые значения при вызове функции. В Go такое тоже возможно, причем можно игнорировать как единственное возвращаемое значение, так и группу. Однако, попытка использовать первое возвращаемое значение и неявно проигнорировать второе приводит к ошибке компиляции. Игнорировать возможно, но явно — с использованием специального символа "_":

x, _ := foo()


Т.е. работает принцип «все или ничего»: можно или игнорировать все возвращаемые значения, или использовать — но также все. 

В Rust имеются схожие возможности. Точно также функции могут возвращать несколько значений; также можно инициализировать ими новые значения. При этом множественное присваивание как таковое отсутствует, возможна только инициализация. Аналогично можно использовать символ "_" для неиспользуемых значений. Аналогично можно игнорировать возвращаемые значения полностью, или получать их также все полностью. Также кортежи можно сравнивать:

let x = (1i, 2i, 3i);
let y = (2i, 3i, 4i);
if x == y {
    println!("yes");
} else {
    println!("no");
}


Отметим этот факт: мы встретили первую операцию над кортежами, отличную от присваивания. Также здесь наблюдается еще одна интересная возможность — создание именованных кортежей и их последующее использование «как единого целого».

В языке Swift возможности в целом аналогичны. Из интересного — обращение к элементам кортежа по константному индексу через точку; возможность назначать элементам кортежа имена и обращаться к элементам через них.
 

let httpStatus = (statusCode: 200, description: "OK")
print("The status code is \(httpStatus.0)")
print("The status code is \(httpStatus.statusCode)") 



Такие кортежи уже близки к структурам, но все-же структурами не являются. И здесь я бы хотел отойти от примеров и перейти к своим собственным мыслям. Разница между кортежами и структурами в том, что кортеж — это не тип данных, это нечто более низкоуровневое; можно сказать — что кортеж — это просто (возможно именованная) группа (возможно именованных) объектов времени компиляции. В этом месте вспомним языки C/С++. Простейшие конструкции инициализации массива и структуры выглядят так:

int arr[] = {1, 2, 3};
Point3D pt = {1, 2, 3};


Обратите внимание, списки инициализации в данном случае вообще идентичны. И тем ни менее, они инициализируют совершенно разные объекты данных. Такое поведение в общем нетипично для типа данных. Но зато это близко к другой интересной фиче, которая иногда (но редко) встречается в языках программирования — структурной типизации. Конструкция в фигурных скобках — типичный кортеж. Кстати, в Си есть именованная инициализация полей структуры (идея кстати весьма похожа на Swift), которую пока так и не протащили в C++17:

Point3D pt = {.x=1, .y=2, .z=3};



В С++ пошли немного в другом направлении: ввели понятие " унифицированный синтаксис инициализации и списки инициализации". Синтаксически это те же кортежи, которые можно использовать для инициализации объектов; в дополнение к старым возможностям, унифицированный синтаксис инициализации позволяет передавать объекты в функции и возвращать из функций в виде кортежей.

    Point3D  pt{10,20,30}; // новый синтаксис инициализации
Point3D foo(Point3D a)
{
    return {1, 2, 3}; // возвращаем "кортеж"
}
foo( {3,2,1} ); // передаем "кортеж" 



Другая интересная возможность — списки инициализации.Они используются для начальной инициализации динамических структур данных — таких как вектора и списки. Списки инициализации в С++ должны быть однородными, то есть все элементы списка должны быть одного типа. Технически такие списки формируют константные массивы в памяти, для доступа к которым применяются итераторы std::initializer_list. Можно сказать, что шаблонный тип std::initializer_list — специальный определенный на уровне компилятора интерфейс к однородным кортежам (а фактически к константным массивам). Разумеется, списки инициализации можно использовать не только в конструкторах, но и как аргументы любых функций и методов. Думаю, если бы в С++ изначально был бы некий шаблонный тип данных, соответствующий литеральному массиву и содержащий информацию о длине этого массива, он бы вполне подошел на роль std::initializer_list.

Также в стандартной библиотеке С++ (и в Boost) существуют кортежи, реализованные с помощью шаблонов. Поскольку такая реализация не является частью языка, синтаксис получился слегка громоздким и неуниверсальным. Так, тип кортежа приходится объявлять явно с указанием типов всех полей; для конструирования объектов применяется функция std::make_tuple; для создания кортежа «на лету» (из существующих переменных) применяется другой шаблон — tie, а получение доступа к элементам осуществляется с помощью специального шаблонного метода, который требует константного индекса.

std::tuple<int,char> t1(10,'x');
auto t2 = std::make_tuple ("test", 3.1, 14, 'y');
int myint; char mychar;
std::tie (myint, mychar) = t1;                            // unpack elements
std::tie (std::ignore, std::ignore, myint, mychar) = t2;  // unpack (with ignore)
std::get<2>(t2) = 100;   
char mychr = std::get<3>(t2);


В примере применяется распаковка со специальным значением std::ignore. Это в точности соответствует символу подчеркивания "_", применяемому для тех же целей при групповых возвратах из функций в Go и Rust.

Похожим способом (хотя и упрощенно по сравнению с С++) реализованы кортежи в C#. Для создания используются методы Tuple.Create(), набора шаблонных классов Tuple<>, для доступа к элементам — поля с фиксированными именами Item1… item8 (чем достигается константность индекса).

В языке D есть достаточно богатая поддержка кортежей. С помощью конструкции tuple можно сформировать кортеж, и — в том числе — осуществить множественный возврат из функции. Для доступа к элементам кортежа используется индексация константными индексами. Также кортеж можно сконструировать с помощью шаблона Tuple, что позволяет создать кортеж с именованными полями.

auto t = Tuple!(int, "number", string, "message")(123, "hello");
writeln("by index 0 : ", t[]);
writeln("by .number : ", t.number);
writeln("by index 1 : ", t[1]);
writeln("by .message: ", t.message);



Кортежи можно передавать в функции. Для этого применяется индексация с диапазоном. Синтаксически это выглядит как будто передается один аргумент, а на самом деле кортеж раскрывается сразу в несколько аргументов. При этом в D, в отличие от Go, нет требования точного равенства количества аргументов функции и элементов кортежа, то есть можно смешивать передачу одиночных аргументов и кортежей.

void bar(int i, double d, char c) { }
auto t = tuple(1, "2", 3.3, '4');
bar(t[], t[$-2..$]);


В D существует еще множество возможностей, связанных с кортежами — Compile-time foreach для обхода кортежей на этапе компиляции, шаблон AliasSeq, оператор tupleof… в общем все это требует отдельной большой статьи.

А напоследок рассмотрим реализацию кортежей в малоизвестном расширении языка Си — CForAll или C∀ (забавно, но на момент написания статьи я не смог нагуглить сайт языка — весьма вероятно что он давно закрылся и упоминаний просто не осталось; вот поэтому я регулярно сканирую сеть в поисках новых языков программирования и скачиваю все до чего смогу дотянуться).

Кортежи в C∀можно объявить на уровне языка, заключив список объектов в квадратные скобки. Тип кортежа создается аналогично — в квадратные скобки заключается список типов. Объекты и типы кортежей можно объявлять явно. Кортежи можно передавать в функции, где они разворачиваются в списки аргументов (в отличие от Go, где такое возможно только при точном совпадении кортежа и списка аргументов функции).

[ int, int ] w1; // объект-кортеж из двух элементов
[ int, int, int ] w2; // объект-кортеж из трех элементов
void f (int, int, int); // функция, принимающая три аргумента

f( 1, 2, 3 ); // просто числа
f( [ 1, 2, 3 ] ); // кортеж-объект объявленный прямо на месте
f( w1, 3 ) // кортеж и число
f( w2 ) // кортеж-объект


Еще одна интересная тема — вложенные кортежи и правила их раскрытия. В С/С++ также применяется вложенность — при инициализации массивов структур, элементы которых также являются массивами и структурами. В C∀ существуют правила, называемые «tuple coercions», в частности — раскрытие кортежей с внутренней структурой (flattering) и наоборот, структурирование (structuring), когда «плоский» кортеж подстраивается под сложную внутреннюю структуру (хотя эта возможность весьма спорная, обсуждение будет в следующей части). И все это относится лишь к присваиванию, никаких упоминаний использования этих возможностей с другими операциями нет.

[ a, b, c, d ] = [ 1, [ 2, 3 ], 4 ];


В C∀ предусмотрено как групповое, так и множественное присваивание. 

[ x, y, z ] = 1.5;
[ x, y, z ] = [ 1, 2, 3 ];


и даже использование кортежей для доступа к полям структур

obj.[ f3, f1, f2 ] = [ x, 11, 17 ];


Ввиду отсутствия компилятора проверить все эти возможности на практике не удалось, но это безусловно отличная пища для размышлений. Собственно, этим размышлениям и будет посвящена следующая часть статьи.

astafevm

В предыдущей части я рассмотрел реализации кортежей в различных языках программирования (причем я рассматривал компилируемые не скриптовые языки со статической типизацией и классическим си-подобным синтаксисом, что вызвало удивление у некоторых читателей). В этой части я предлагаю выйти за рамки существующего и заняться по сути дизайном языка программирования. Исходные данные — такие же: компилируемый не скриптовый язык со статической типизацией и си-подобным синтаксисом, включающий императивную парадигму (хотя и не ограничивающийся ею разумеется).
Итак, попробуем помечтать и поэкспериментировать — а что вообще можно сделать с кортежами? Как выжать из них максимум возможностей? Как с их помощью сделать язык программирования мощнее и выразительнее, как вызвать восхищение у истинных Хакеров Кода и при этом не слишком запутать обычных программистов? Какие неожиданные возможности появляются в языке, если правильно и грамотно экстраполировать семангтику кортежей в разных направлениях, и какие затруднения при этом возникают?
Итак, если вам нравится размышения и холивары на тему дизайна языков программирования, то прошу под кат.

СИНТАКСИС
Для начала определимся с синтаксисом. Можно долго рассматривать разные варианты оформления кортежей в коде — в фигурных скобках, в круглых, вообще без скобок… мне по ряду причин нравится вариант с фигурными, его и возьмем за основу (по крайней мере пока не возникнет реальная необходимость в другом синтаксисе). Итак, кортеж — это последовательность имен или выражений, перечисленных через запятую и заключенных в фигурные скобки. Как унифицированная инициализация в С++.



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

Еще хотелось бы отметить простую идею создания кортежей из диапазонов и повторений. Диапазоны — способ задания кортежей для перечислимых элементов (к которым относятся целые числа и элементы перечислений)


Повторения — взятый из Ассемблера способ, позволяющий заполнить кортеж одним и тем же значением


В данной статье я этими способами пользоваться не буду, и упомянул их просто так — как красивую идею.

ОБЩИЕ ИДЕИ
В первой части я упомянул некую мысль, которая вызвала некоторое непонимание — о том, что кортеж «не совсем тип». Если открыть ту же Википедию, то там четко сказано

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


Однако, что же я имел в виду, говоря что это «не совсем тип»? Иногда (и довольно часто) хочется иметь некую языковую конструкцию, которая бы позволяла локально группировать произвольные объекты на этапе компиляции. Некий групповой псевдоним, который был бы просто эквивалентом своих составных частей без введения каких-либо дополнительных сущностей. В действительности, это просто способ взглянуть на кортежи с несколько другой стороны. Например, множественный возврат из функций. Его вполне можно рассматривать как возврат одного объекта типа «кортеж»; но с другой стороны, операция присваивания значений при множественном возврате — всего лишь одна из множества операций; на примере Rust мы видели возможность другой операции над кортежами — сравнения на равенство. А что если разрешить выполнять над кортежами все операции? Сразу возникают различные вопросы — какие это могут быть операции, могут ли они выполняться над кортежами разной длины, над кортежем и одиночным значением? Также можно рассмотреть вопросы семантики передачи кортежей в функции (возможно например «раскрытие» кортежа при передаче в функцию, или выполнение функции над всеми элементами кортежа и т.д.). Безусловно, нам понадобятся удобные средства создания и декомпозиции кортежей, доступа к элементам кортежа. Возможно, что-то еще?..

МНОЖЕСТВЕННЫЙ ВОЗВРАТ ИЗ ФУНКЦИЙ И ТИП VOID
Начнем с самого простого, и уже реализованного во многих языках — с множественного возврата из функций. Функция может возвращать несколько значений, и мы называем это возвратом кортежа из нескольких значений — точно так-же, как функция может принимать несколько аргументов. Напрашивается следующее обобщение: функция, возвращающая одно значение, возвращает кортеж из одного значения. По крайней мере, можно принять как пожелание, чтобы кортежи из одного значения свободно конвертировались в эти самые значения, и наоборот.

Дальнейшая экстраполяция приводит нас к типу void. Как известно, это специальный тип, используемый в большинстве языков программирования для обозначения того, что функция не возвращает результата. Создание объектов этого типа запрещено. Совсем не трудно догадаться, что void — это не полноценный тип, а обозначение для кортежа нулевой длины. Создание объектов такого типа в привычном смысле действительно невозможно; но если наш компилятор умеет работать с кортежами более продвинутым способом, то ничто не мешает нам ввести «псеводобъект» типа void в виде пустого кортежа {} и что-то с ним делать. Вопрос — что? И в каких случаях это может быть полезно? Пока просто отметим это и перейдем к следующим экстраполяциям.

МНОЖЕСТВЕННЫЕ ОПЕРАЦИИ
Мы рассмотрели множественный возврат из функций и связанное с ним множественное присваивание. Но присваивание — это всего лишь одна из множества возможных операций. По аналогии с присваиванием (с которого все и начиналось), попытаемся построить другие операции над кортежами:

{x,y,z} = foo(); // обычный множественный возврат, считаем что foo() возвращает 3 значения
{x,y,z} += foo(); // а если присваивание, совмещенное с операцией?
{x,y,z} ++; // или унарная операция
{x,y,z} = {1,2,3} + {a,b,c}; // или полноценная бинарная операция и присваивание


Пока выглядит неплохо, хотя возможно немного настораживает мысль о том, как будут выглядеть такие выражения с большим количеством операций. Важно то, что каждый объект в выражении — кортеж из одинакового количества элементов. Отступление от этого правила порождает различные неоднозначности, которых при дизайне языка программирования лучше избегать. Тем ни менее рассмотреть их нужно обязательно (а по возможности и разрешить).

ГРУППОВЫЕ ОПЕРАЦИИ
Кроме множественных операций, еще весьма заманчивыми кажутся групповые операции над кортежем и одиночным значением. По сути, это первое отступление от правила одинакового количества элементов в каждом операнде выражения. Но хочется чтобы получилось красиво, поэтому пробуем:

{i, j, k} = ; // обнуляем все три переменные... все просто, прозрачно и очень удобно, разве тут могут быть неопределенности?
++{i, j, k}; // тоже очень неплохо смотрится...
{i, j, k} += 100; // и это тоже
rec.{x,y,z}.At(i).flag = true; // и даже так


Общий вид такой бинарной операции — «tuple operator value», или «value operator tuple» (большинство операций коммутативны); А под форму «value operator tuple» попадает присваивание одной переменной целого кортежа, и в частности — результата множественного возврата функции.

x = {1, 2, 3}; // чему должен быть равен x после присваивания ?
x = foo(); // если foo() возвращает несколько значений, то чему равен x ?


В ситуации с присваиванием множественного возврата из функции очевидно хочется, чтобы в «x» было записано первое возвращаемое значение, а следующие проигнорированы. Вот такая вот неожиданная неоднозначность. Но решать ее как-то надо — уж очень хочется иметь такой синтаксис. Он мог бы быть полезен не только для группового присваивания, но и еще в ряде интересных случаев. Например, индексация массива и даже доступ к полю структуры по имени — тоже операции. 

arr[ {1,2,3} ] = { 10, 20, 30 }; // групповой доступ к массиву
obj.{x,y,z} = {10,20,30};        // и даже к полям структуры... 
{obj1, obj2, obj3}.x = { 10, 20, 30 };    // и наоборот... 
{arr1, arr2, arr3} [  ] = {10, 20, 30 }; // и с массивами аналогично


Несмотря на кажущуюся очевидность выражений, формально arr[ { 1,2,3 } ] — это форма «value operator tuple», где «value» — это arr, «tuple» — {1,2,3}, а «operator» — квадратные скобки. В отличие от присваивания кортежа единственному значению, здесь вопросов не возникает — результат такой операции должен быть кортежем { arr[1], arr[2], arr[3] }. Но для компилятора что присваивание, что индексация — всего лишь бинарные операции. Значит, выражение вида x = {1,2,3} должно разворачиваться в {x=1, x=2, x=3}, то есть переменной x последовательно присваиваются все значения кортежа, и результатом также является кортеж (кстати, если бы такой язык программирования был в реальности — это был бы интересный вопрос на засыпку для всяких собеседований: чему будут равны его элементы? {1,2,3} или {3,3,3}?) 
Таким образом, вопрос о правильной организации операций над кортежем и единственным элементом пока остается открытым.

БИНАРНЫЕ ОПЕРАЦИИ НАД КОРТЕЖАМИ С РАЗНЫМИ РАЗМЕРАМИ
Рассмотрим более общий случай — размеры кортежей — операндов бинарной операции не совпадают. Что делать в этом случае? Как всегда, самое простое решение — запретить :) Но попробуем разобраться… В Go и Rust предусмотрена специальная метапеременная "_" (подчеркивание), которую используют, когда какой-то из элементов кортежа справа от оператора присваивания не нужен. В нашем синтаксисе это будет выгледеть так:


Операция со вторым компонентом кортежа просто игнорируется. Использование метапеременной "_" в Go и Rust обязательно при множественном присваивании в случае, если какие-то результаты возвращаемого кортежа не нужны. Таким образом, в этих языках требование обязательного совпадения размеров кортежей сохраняется. Но в этих языках нет других множественных операций кроме присваивания; при попытке экстраполяции метапеременной "_" на другие операции получаются настолько интересные результаты, что их следует рассмотреть в отдельной главе. 

Попробуем рассмотреть общий случай: что делать, если написано такое выражение (@ — обобщенная бинарная операция)


Какие есть возможности?

  • Выполнять операции по размеру кортежа с меньшим размером. То есть результат {a@x, b@y}. Противоречит групповым операциям (если считать одиночные значения эквивалентом кортежей с одним элементом) — при групповой операции одиночный операнд должен взаимодействовать с каждым элементом кортежа.
  • Выполнять операции по размеру кортежа с большим размером. При этом элементы большего кортежа, на которых не хватило элементов меньшего кортежа, остаются без изменений. {a@x, b@y, z}. Также противоречит групповым операциям по той же причине.
  • Выполнять операции по размеру кортежа с большим размером. Когда меньший кортеж закончится, циклически переходить на его начало. {a@x, b@y, a@z}. Это соответствует групповым операциям, но заумно.
  • Выполнять операции по размеру кортежа с большим размером Когда меньший кортеж закончится, выполнять операции с последним элементом меньшего кортежа. {a@x, b@y, b@z}. Тоже соответствует групповым операциям, и тоже заумно.
  • Выполнять операции попарно «каждый элемент одного кортежа с каждым элементом другого кортежа» («декартово произведение»). {a@x, a@y, a@z, b@x, b@y, b@z}. Тоже соответствует групповым операциям, и тоже заумно. И кстати, возникает вопрос как группировать пары — по первому кортежу или по второму, или еще каким-то способом.


Возможно есть и другие варианты. Но уже сейчас ясно одно: если и давать такую возможность программисту, то указание способа взаимодействия кортежей должно делаться явно. В данном случае это практически обязательное условие сохранения ясности в коде. Для этого могут применяться какие-то ключевые слова, охватывающие такое выражение. Пока не будем конкретизировать список таких слов (предварительно можно считать что для каждого из вариантов может быть свое ключевое слово)

ДОСТУП К ЭЛЕМЕНТАМ
А сейчас следует обратиться еще к одной важной возможности — доступу к элементам кортежа. 
Традиционно для индексации используются квадратные скобки, а для доступа по имени — точка; хотя вариант Swift с индексом через точку не так уж и плох, он неоднозначен при применении именованных числовых констант вместо чисел, да и вообще непривычен; я предпочту использовать квадратные скобки для доступа по индексу (важно — по константному индексу) и точку для доступа по имени (если оно есть) 



Вроде все просто? На самом деле уже нет. Поскольку мы ввели множественные и групповые операции над кортежами, а к таковым относится и индексация "[ ]", и обращение к именованным членам структуры ".", то при использовании этих операций над кортежами, элементами которых являются сложные объекты (для которых определена индексация или «точка») — неясно что делать: обращаться к элементу кортежа или выполнять групповую операцию над всеми элементами кортежа?



Еще один интересный аспект — получение (конструирование) кортежей. По соглашению, принятому в начале статьи, мы используем фигурные скобки для простого конструирования объекта-кортежа. Однако, в некоторых случаях может понадобиться построить кортеж из другого кортежа путем исключения или добавления элементов. Синтаксически это можно сделать с использованием «множественной индексации», применяя по сути те же правила, что и к массивам или структурам.
Для получения кортежа можно было бы использовать множественную индексацию или диапазоны:



(операция индексации сама по себе содержит скобки, поэтому внутри квадратных скобок еще одни фигурные можно не писать)
При наличии у элементов составного объекта имен, можно обращаться по именам:

obj.{a, d, e} // перечисление полей
obj.{b .. f}  // диапазон - по порядку объявления полей


Следует отметить интересное следствие — поскольку кортежи индексируются константными индексами, то напрашивается идея приводить имена полей к константным индексам (и возможно наоборот)

Таким образом, есть как минимум две «специальные» операции, «точка» и «квадратные скобки», которые могут действовать на сам кортеж как целостный объект. Остальные операции для кортежа как-бы не определены, хотя можно предположить, что нам понадобится например конкатенация кортежей — плоское склеивание двух и более кортежей в один длинный. Поэтому встает открытым вопрос: нужно ли как-то выделять операции доступа непосредственно к элементам кортежа? Или правильнее выделять операции над каждым элементом кортежа?

ОТ ОПЕРАЦИЙ К ФУНКЦИЯМ
Любая операция эквивалентна некоторой функции. Например, унарная операция битовой инверсии ~x может быть представлена как neg(x), а бинарное сложение x+y как sum(x, y); 
поэтому, рассматривая операции над кортежами как множественные операции, возникает вопрос — что делать, если в таком выражении участвует вызов функции?
Для начала унарная операция:

~{1,2,3};     // в форме операции
neg({1,2,3}); // в форме функции


по аналогии с «групповым присваиванием», мы должны развернуть кортеж следующим образом:


на первый взгляд это кажется вполне логично; сама функция принимает единственное значение и возвращает единственное значение; должна возвращать единственное значение, чтобы из них можно было составить кортеж. 
Вероятно, аналогично могли бы раскрываться и функции с двумя аргументами. Например


в


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

С другой стороны, вспомним синтаксис Go, D, C∀: там кортеж, переданный в функцию, разворачивается внутри списка аргументов, заменяя собой соответствующее число аргументов. И в общем это тоже весьма логично и ожидаемо — но опять несовместимо с «групповой» сементикой! Возможно ли как-то разрешить это противоречие? И это мы еще не рассматривали сложные варианты, когда размерности кортежей-аргументов не совпадают, когда смешиваются кортежи и одиночные значения, когда мы хотим получить декартово произведение из результатов операции над элементами кортежей и т.д.

Решение, кажущееся достаточно неплохим, пришло как ни странно из С++. Там есть такая возможность, как шаблоны с переменным числом параметров, и для передачи пакета параметров (кстати тоже кортежа) в другой шаблон используется синтаксис с троеточием. Троеточие визуально маркирует, что данный аргумент «раскрывается» в данном контексте (и это очень важно для восприятия кода!). Важно, что это сразу видно в коде. Единственное что не видно — так это на сколько аргументов он раскрывается. Но если хочется указать подробно — ничто не мешает воспользоваться доступом к отдельным элементам кортежа



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

МЕТАПЕРЕМЕННАЯ "_"
Возвращаемся к рассмотрению поведения метапеременной "_", которая используется в некоторых языках для игнорирования элементов кортежа-источника при присваивании кортежей. Посмотрим, можно ли экстраполировать эту метапеременную на более сложные случаи (ведь присваивание — это всего лишь бинарная операция).


По аналогии, операция сложения числа 2 с "_" будет проигнорирована, но что получится в результате? Вообще существует две возможности: или оставить число 2 в результирующем кортеже, или распространить туда "_". В первом случае "_" может рассматриваться как "нейтральный элемент". для любой операции (то есть для любой операции "@" и любого аргумента «x» справедливо x @ _ == _ @ x == x). Например, выражение x = y * ~(_ + z) может быть трансформировано в x = y * ~z.
Однако тут не все однозначно. Например, унарную операцию смены знака "-x" можно записать как бинарную операцию вычитания числа из нуля «0-x». Если вместо «x» поставить "_", то вот такое выражение будет иметь разный смысл в зависимости от способа записи.



Во втором случае при появлении "_" в какой-то позиции кортежа все дальнейшие вычисления для этой позиции от узла синтаксического дерева, содержащего "_", до корня дерева (т.е. конца выражения, как правило — точки с запятой), отбрасываются (то есть справедливо x @ _ == _ @ x == _ ). То есть наличие хотя-бы одного "_" в i-том элементе кортежа означает, что все вычисления с i-тым элементом всех кортежей во всем выражении выкидываются.
Я затрудняюсь сказать, какой способ работы с "_" лучше. Это еще один вопрос, требующий тщательного обдумывания.

ВЛОЖЕННЫЕ КОРТЕЖИ
Еще один интересный аспект, практически не рассматриваемый в документации к языкам — вложенность кортежей. Наиболее очевидное и реально существующее (даже в языке Си) применение вложенных кортежей — инициализация вложенных структур. 


Такой код инициализирует поля x, y, i, но оставляет неинициализированными z и j. Без вложенных кортежей это было бы не так наглядно (и кстати, это пример того как в операторе присваивания/инициализации участвуют кортежи разных размеров). Таким образом, вложенные кортежи имеют вполне конкретные варианты использования. Но это означает также необходимость продумать все особенности экстраполяции операций над вложенными кортежами. 

ОПЕРАЦИИ СРАВНЕНИЯ
Операции сравнения кортежей формально должны образовывать кортеж элементов типа bool. Рассмотрим на примере операции равенства:

{x, y, z} == {1, 2, 3} // например {false, true, false}


Но операторы if, while и т.д. требуют единственного значения типа bool! Как же преобразовать кортеж логических переменных в одно значение? 
Существует как минимум две возможности

  1. Все элементы попарно равны (то есть весь результирующий кортеж состоит из true)
  2. Существует хотя-бы одна пара равных эломентов (то есть хотя-бы один элемент результирующего кортежа равен true).


Это очень похоже на кванторы всеобщности и существования из логики предикатов. В тех языках программирования, где сравнение кортежей возможно, обычно по умолчанию подразумевается квантор всеобщности. Это очевидно для структур и любых составных типов: действительно, два составных объекта равны, если равны соответствующие составные части этих объектов. Поэтому, если мы рассматриваем кортеж как тип — это единственная возможность. 
Но всегда ли такое поведение нужно пользователю? Возможно, что и не всегда; поэтому имеет смысл не только ввести квантор существования (если условие выполняется хотя-бы для одной пары элементов) но и более сложные варианты сравнения.

При сравнении кортежа с одиночным элементом интуитивно ожидаемое поведение также подразумевает «логическое И» между результатами сравнения каждого элемента кортежа с единственным значением:


В случае сравнения кортежей разной длины ситуация аналогична любым другим операциям — любая «подразумевающаяся по умолчанию» логика нежелательна ввиду возможности потенциальных ошибок (программист может забыть указать элемент) и отсутствия какой-то однозначной интуитивной интерпретации, преемлемой для всех.

ПРЕДВАРИТЕЛЬНЫЕ ИТОГИ
Поздравляю, если вы дочитали до этого места! Как видно, введение подобного синтаксиса с одной стороны позволяет красиво записывать некоторые простые выражения; с другой, вопросов и неоднозначностей гораздо больше чем ответов. Эта длинная статья — по сути выжимка по теме «кортежи и групповые операции» из моих личных заметок, которые я делал в течение достаточно долгого времени (обычно это происходит так — сижу, работаю, вдруг приходит какая-то идея — открываю Evernote или Wiznote и записываю). Возможно, у вас тоже появлялись схожие мысли и идеи, или появились в процессе чтения статьи — прошу в комментарии!

×