Нам не нужен тернарный оператор

Основа компактного кода, тернарный оператор ?: кажется, что он заслужил место в любом языке программирования. Я использую его часто — вы должны использовать его часто. Однако сам оператор мне не нравится. Он кажется сложным и незавершенным. Хотя Я остановил разработку на Leafя нашел лучший вариант.

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

Что это за тернарный оператор?

Название этого оператора вызывает вопросы. Тернарный оператор в типичном математическом смысле или с точки зрения синтаксического анализатора — это оператор, который принимает три аргумента. Оператор — это функция, которая имеет специальный синтаксис, а не общий синтаксис вызова. foo(a,b,c). Наиболее распространены бинарные операторы, мы видим их повсюду.

//binary operators
x + y
x / y
x * y

Есть также несколько унарных операторов, которые мы часто видим. Унарный означает, что у них есть только один аргумент.

//unary operators
-a
~bits
!cond

Нет недостатка в унарных и бинарных операторах. Но какие у нас есть примеры тернарных операторов?

//ternary operators
cond ? true_value : false_value

Вы чешете голову, пытаясь думать о другом? Насколько я знаю, их нет. Вот почему мы в конечном итоге называем это «тройным оператором» вместо того, чтобы использовать имя собственное, например «условный оператор». В языках программирования нет других тернарных операторов.

Цепные операторы

Отступим на секунду. Существуют последовательности и комбинации операторов, которые могут выглядеть как тернарные операторы, но таковыми не являются.

Например, это сравнение Python включает три аргумента.

if a < b < c:
    go()

Это похоже на тернарный оператор. Он должен учитывать все три аргумента для правильной оценки.

Однако если копнуть глубже, это скорее синтаксический сахар, чем настоящий тернарный оператор. Это эквивалентно следующему:

once_b = b
if a < once_b and once_b < c:
    go()

Вы можете найти несколько примеров, которые показывают только a < b and b < c, но они неверны. Оригинальная форма гарантирует, что b оценивается только один раз. я использую once_b = b показать эту гарантию. Я предполагаю, что на практике этого достигает другая внутренняя функция.

Эту конструкцию Python можно расширить, включив в нее еще больше значений.

if a < b < c < d <e < e:
    go()

Это связанный оператор. Нет предела тому, что можно добавить.

<< оператор потока в C++ также может быть связан.

cout << "Hello " << first_name << " " << last_name << endl;

В отличие от цепочек сравнений Python, эта последовательность C++ не требует поддержки синтаксического анализатора. << является обычным бинарным оператором, который может быть цепным. Базовая математика имеет такое же свойство, например a + b + c + d.

Грязный синтаксис и синтаксический анализ

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

Посмотри на cond ? true_value : false_value.

Есть ли что-то, из-за чего кажется, что задействованы три аргумента? Что, если я немного изменю операторов, на неизвестные: expr ⋇ value_a ⊸ value_b? Это похоже на два бинарных оператора.

Даже если я оставлю те же символы, но добавлю вложенные выражения, это выглядит не так: cond ? value_a + value_b : value_c * value_d.

Нет никаких визуальных признаков того, что ? ... : является одним единым оператором. Это синтаксическое ограничение не позволяет вводить новые тернарные операторы. Это вызовет обратную реакцию. ?: уже находится в плохих книгах некоторых программистов, так как им легко злоупотреблять.

Несмотря на ценный синтаксис, тернарный оператор синтаксически запутан. Просто странно, что два разделенных символа определяют три аргумента. Функция с тремя аргументами не является проблемой, поскольку у нас есть надежный синтаксис для нее. foo( a, b, c ).

Добавление к сложности является приоритетом. Все операторы требуют приоритета. Например, a + b * c оценивается как a + (b*c). Умножение имеет более высокий приоритет, чем сложение. Его результат оценивается перед добавлением.

Операторы одного приоритета также обладают ассоциативностью. Например 5 - 2 + 3 оценивается как (5 - 2) + 3 = 6, который является левоассоциативным. Правая ассоциативность будет оцениваться как 5 - (2 + 3) = 0что неправильно.

Правая ассоциативность обычно зарезервирована для унарных операторов, которые имеют только правильный аргумент, и операторов присваивания. Например, если вы достаточно сумасшедший, чтобы написать a = b += cправая ассоциативность оценивает это как a = (b += c).

Тернарный оператор правоассоциативен. Хотя это кажется неправильным. Как оператор с тремя аргументами и двумя символами может иметь ассоциативность, определяемую просто как левый или правый?

cond_a ? val_one : cond_b ? val_two : val_three

//right? associative
cond_a ? val_one : (cond_b ? val_two : val_three)

//left? associative (wrong)
(cond_a ? val_one : cond_b) ? val_two : val_three

//strict-left? associative (wacky)
(((cond_a ? val_one) : cond_b) ? val_two) : val_three

//strict-right? associative (nonsensical)
cond_a ? (val_one : (cond_b ? (val_two : val_three)))

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

Попробуйте представить, что происходит во вложенных тернарных операторах: a ? b ? c : d : e. Вы не часто встретите в коде вложенные тернарии. Их слишком сложно разобрать мысленно.

Вы найдете много кода, зависящего от квазиправой ассоциативности. Это то, что позволяет цепочку.

int a = cond_a ? val_one :
    cond_b ? val_two :
    cond_c ? val_three : val_four;

Бинарное решение

Когда я работал над Leaf, я хотел избежать тернарного оператора. Мне понравилась предоставляемая им функция, но я хотел найти более фундаментальный подход к ее предоставлению.

Решение пришло в виде языковых опций. Они были основными в Leaf, поэтому имели поддержку оператора.

Первый оператор был дефолтным. Если бы было установлено необязательное значение, оно оценивалось бы как это. В противном случае он будет оценивать предоставленное значение по умолчанию.

var a : optional
print( a | 1 )  //prints 1, since `a` has no value

var b : optional = 2
print( b | 3 )  //prints 2, since that's the value in `b`

|| Оператор в JavaScript во многих случаях достигает того же эффекта. В C# есть нулевой оператор объединения ?? который использует null точно так же, как Leaf использует неустановленные параметры.

Это звучит как половина того, что делает тернарный оператор. Мы могли бы посмотреть на это так: cond ? some_val | default_val. То есть рассмотрим всю левую часть, cond ? some_val для создания необязательного значения. Учитывая необязательный параметр, у нас уже есть операторы, которые принимают это значение по умолчанию, когда оно не установлено.

Лист включил ? оператор для создания необязательного.

var a = true ? 5  //a is an optional set to value 5
var b = false ? 6  //b is an optional without a value 

Сам по себе этот оператор часто полезен для необязательных параметров. В сочетании с | оператор по умолчанию имитирует традиционный тернарный оператор.

var a = cond ? true_value | false_value
int a = cond ? true_value : false_value;

? и | оба являются бинарными операторами. Нет необходимости иметь фактический тернарный оператор для получения той же условной оценки. Мы получаем ту же функцию без синтаксической путаницы. Так намного понятнее, что происходит, и парсер не требует никаких ухищрений.

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

Увы, я не видел никаких планов по добавлению этого на какой-либо язык. Если вы знаете такой, пожалуйста, оставьте комментарий. Мне было бы любопытно увидеть это.

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *