Вы ещё пишете код слева направо? Тогда мы идём к вам
А ведь выражениям совсем не обязательно вытягиваться в струнку слева направо. Кто сказал, что то же a < b нельзя записать по вертикали?
Мы привыкли втискивать код в одномерную плоскость строки, но что будет, если освоить второе измерение? Разумеется, чтобы всё это закрутилось, среда разработки и парсер должны работать как единый слаженный механизм. Но главный вопрос: ради чего всё это? Давайте разберем несколько любопытных примеров.
❯ Функции от трёх аргументов
Трехаргументные (или тернарные) функции в инфиксной записи — зверь довольно редкий.
В том же Python, да и во многих других языках, можно связать оператором and сразу цепочку условий:
x and y and zНо технически это лишь последовательное применение нескольких бинарных функций.
Даже идиома в Python, сокращающая (a < b) and (b < c) до изящного:
a < b < c— по сути, всё та же комбинация.
А теперь знакомьтесь: забавный оператор andFlip, с которым я экспериментировал. Именно он наглядно показывает, зачем нужна настоящая трёхместная инфиксная запись. Вот как выглядит его суть:
def andFlip(args):
if args[0] and args[1]:
args[2] = not args[2]
return args[2]Он принимает два входных сигнала и, если оба истинны, инвертирует целевое значение.
Записать такую штуку традиционным инфиксным способом красиво просто невозможно. Но давайте всё же попытаемся.
Обозначим оператор andFlip символом @@. Как это будет выглядеть на практике?
Способ 1: традиционный
Можно каррировать andFlip: тогда инфиксный @@ принимает два аргумента и возвращает функцию, ожидающую третий:
(a1 @@ a2) a3Что ж, сносно. Работать будет. Но…
Способ 2: экзотический
Забудьте на секунду привычные правила синтаксиса. Будь у меня волшебная палочка, я бы заставил тернарную запись выглядеть вот так:
Почему бы не задействовать ось Y, раз уж она у нас есть?
Выстраивать цепочки становится невероятно удобно:
В этой цепочке выражение x @@ y переключает флаг t1, а затем этот же t1 передается дальше — в t1 @@ z, который переключает t2.
Получается полноценный тройной переключатель: целевой флаг изменится только тогда, когда все три условия (x, y и z) истинны.
Кстати, раз уж мы перешли в двухмерную плоскость, то вот эта запись:
Абсолютно эквивалентна такой:
Пример из жизни: дверь курятника
Взгляните на автоматическую дверь, которую я поставил в курятнике.
С помощью фотодатчика и встроенных часов микроконтроллер отслеживает три параметра:
x— датчик освещенности зафиксировал порог рассвета/заката;y— показания оставались стабильными в течение пяти минут;z— за последнее время дверь не меняла своего положения.
Дверь срабатывает, только если сходятся все три фактора. Проплывающее облако на мгновение заденет x, но не продержится достаточно долго для y — и дверь останется закрытой.
В основе контроллера курятника лежит ровно та же цепочка, что мы разбирали выше:
Вы спросите: а почему бы просто не написать x and y and z? Да потому что тогда дверь превратилась бы в послушное зеркало датчиков и тут же захлопнулась обратно, стоило бы условию хотя бы на миг пропасть. Оператор @@ же фиксирует состояние: дверь открылась — и всё, а промежуточное значение сохраняется в техническом бите‑черновике t1.
Но самое интересное начинается, когда двухмерные языки осваивают вертикальное сцепление!
❯ Вертикальные цепочки
Изменчивая переменная t1 не позволяет назвать такое выражение «чистой функцией». Но можно ли обойти это ограничение? Способны ли мы сбросить t1 в исходное состояние, не выходя за рамки единого выражения?
Давайте разберем идею вертикальных цепочек на двух головоломках.
Задачка 1: сбросить t1 в ноль
Как уже говорилось, в предыдущих примерах переменная t1 меняет свое значение. Задача: как возвратить её к 0, чтобы со стороны выражение казалось чистым и неизменяемым? Соблазн просто дописать в конце t1 = 0 велик, но суть именно в том, чтобы сделать всё внутри одного двухмерного выражения.
Попробуйте решить сами, прежде чем листать дальше!
А вот и отгадка. Поскольку t1 сбрасывается повторением той же операции, которая её инвертировала, мы можем объединить выражения в единый двухмерный блок:
Да уж, выглядит как магия.
Поток данных движется привычным образом: слева направо и сверху вниз. Если x и y истинны, первая строчка x @@ y переключает t1, а вторая x @@ y — возвращает его в исходное состояние. Если же условия не выполнены, t1 просто не затрагивается.
Эта головоломка напрямую моделирует вентиль CCCX из квантовых вычислений, где обратимые вычисления промежуточного значения t1 внутри той же схемы крайне важны.
Задачка 2: вернуть временную переменную к исходному значению
А что если t1 изначально содержит неизвестное булево значение n? Задача усложняется.
Прошлый трюк для сброса t1 к исходному значению уже не пройдет.
Убедитесь сами: если t1 = 1, а x, y и z истинны, то t2 останется равным 0, что неверно.
Так каково же решение?
Та‑дам!
Выглядит гениально. Это единое 2D‑выражение корректно сбрасывает t1 обратно в n, выдавая при этом верный результат для t2.
В основе этой задачки лежит концепция сброса заимствованного кубита из квантовой физики. Переменная t1 здесь выступает в роли «грязного» значения, которое мы берем на время, но обязаны вернуть в исходный вид.
Вот как эта схема выглядит в Qiskit:
from qiskit import QuantumCircuit, QuantumRegister
x = QuantumRegister(1, "x")
y = QuantumRegister(1, "y")
z = QuantumRegister(1, "z")
t1 = QuantumRegister(1, "t1") # начальное неизвестное состояние n
t2 = QuantumRegister(1, "t2") # цель, изначально 0
qc = QuantumCircuit(x, y, z, t1, t2)
qc.ccx(x, y, t1)
qc.ccx(t1, z, t2)
qc.ccx(x, y, t1)
qc.ccx(t1, z, t2)Реализация на Qiskit воспринимается значительно тяжелее из‑за необходимости постоянного отслеживания переменных туда‑сюда.
Кстати, вот эквивалентная квантовая схема:
Схема в чем‑то понятнее, но плохо масштабируется. А вот синтаксис двухмерного языка берет лучшее от обоих форматов.
❯ Конструирование новых операторов
Для начала познакомимся с младшим братом оператора @@. Одиночный @ принимает лишь одно условие: запись x @ y переключает y всякий раз, когда x истинно.
Имея в арсенале @ и @@, мы можем конструировать новые операторы прямо в пространстве. Слева от := задается двухмерная форма, а справа — её раскрытие:
Этот хитроумный оператор инвертирует b и c, если a истинно; затем переключает a, если оба результата оказались истинными. По сути, он вычисляет a ^ ((a^b) & (a^c)) и записывает результат в a. Это не что иное, как функция большинства (мажоритарный вентиль) для трех входов, — отсюда и название maj.
И выбор maj далеко не случаен. Это вентиль MAJ из классической квантовой схемы сумматора с последовательным переносом, где мажоритарность значений (перенос, бит, бит) определяет следующий бит переноса.
Вы могли заметить, что maj оставляет за собой «мусор» в b и c (там сохраняются a^b и a^c) — ровно так же, как было с нашей переменной t1. Чтобы прибраться за собой, введем парный оператор uma (unmajority and add). Он восстанавливает a, отменяет мажоритарность c и прибавляет сумму к b:
Посмотрим, что произойдет, если объединить их в цепь.
❯ 3-битный сумматор
Теперь мы можем собрать полноценный 3-битный сумматор из трех вентилей maj, протягивающих перенос по шинам a. Финальный @ сбрасывает бит переноса на c3, а нижняя строчка операторов uma подчищает все промежуточные значения:
Перед нами законченный 3-битный сумматор в виде одного двухмерного выражения, которое само за собой убирает! В шинах b теперь записана сумма a + b, в c3 — бит переноса, а c0 и шины a возвращаются в исходное состояние. (Подайте c0 = 1 — и совершенно бесплатно получите a + b + 1.)
Обратите внимание, как верхняя и нижняя строки зеркально отражают друг друга. Верхняя часть погружается вправо до b0 и протягивает перенос вверх; нижняя — уходит влево к b2 и сворачивает вычисления. Общие метки a1 и a0 служат опорными точками, работая и на верхний maj, и на нижний uma. Точка . обозначает вертикальную связь с вышестоящей шиной (в данном случае a2). Цепочка переноса сшивается по вертикали: этап отмены вычислений соприкасается с этапом расчета — ровно как в наших головоломках.
❯ Экспресс‑обзор других пространственных языков
Давайте бегло пробежимся по другим интересным двухмерным языкам.
Befunge
Дедушка всех 2D‑языков Befunge (1993 год): указатель инструкций физически перемещается по сетке, а стрелки >v<^ задают его курс. Вывод «Hello World!» буквально рикошетит от границ:
Orca
Orca — язык‑секвенсор для лайвкодинга, где каждая буква является оператором, считывающим соседние клетки: например, A складывает значения слева и справа и записывает результат прямо под собой. Знакомо выглядит, правда?
Racket #2d
Вполне реалистичное расширение языка: #2dcond в Racket — это конструкция cond, вычисляемая в двух измерениях. Левый столбец проверяет условие b, верхняя строка — a, а исполняется клетка на их пересечении.
Hexagony
Hexagony работает на шестиугольной сетке с вершиной вверху. Шесть указателей команд стартуют из шести углов. Вот как выглядит «Hello World»:
Релейная логика
Релейная логика (ladder logic) — язык, на котором ПЛК управляют целыми заводами (а при прямых руках — и дверью курятника). Каждая «ступенька» представляет собой релейную цепь, считываемую от шины к шине, а параллельные ветки работают как оператор ИЛИ. Классическая схема с самоблокировкой вычисляет Run = (Start OR Run) AND (NOT Stop):
И отдельно отметим самый успешный пространственный язык всех времен — электронную таблицу, где каждая формула живет по 2D‑координатам и обращается к соседям.
❯ «Это вода»
Некоторые знают мое пристрастие к гипотезе Сепира — Уорфа, согласно которой язык определяет мышление:
У некоторых австралийских аборигенов нет понятий «лево» и «право» — только стороны света. Поэтому они всегда идеально ориентируются, где север.
В русском языке есть четкое разделение на «голубой» и «синий» (в отличие от английского
blue), поэтому русскоязычные люди быстрее различают эти оттенки, когда они находятся на границе понятий.В Haskell всё чисто функционально, поэтому ты начинаешь видеть монады буквально на каждом шагу.
Точно так же технологии определяют, какими становятся создаваемые нами языки программирования:
Командная строка заставляет вводить текст слева направо — и мы привыкаем думать о коде горизонтально, а не вертикально.
Стандартные клавиатуры заточены под английский язык и символы ASCII — и наше мышление замыкается в этих рамках.
Осознание собственных паттернов — полезная гимнастика для ума. Об этом говорил еще Дэвид Фостер Уоллес в своей знаменитой речи «Это вода». И хотя его выступление было не совсем про квантовую обратимость вычислений, а скорее про взросление и эмпатию, суть одна: ставя под сомнение привычные рамки, мы открываем дверь для принципиально новых идей.
Может быть интересно:
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram‑канале ↩