Рубрики
Без рубрики

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

Почему тернарный оператор запутан и как мы могли бы от него избавиться.

Автор оригинала: Edaqa Mortoray.

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

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

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

Название этого оператора вызывает вопросы. Тернарный оператор, в типичном математическом смысле или с точки зрения синтаксического анализатора, – это оператор, который принимает три аргумента. Оператор-это функция, которая имеет специальный синтаксис,а не общий синтаксис вызова 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 и b < c , но они неверны. Исходная форма гарантирует, что b оценивается только один раз. Я использую once_b , чтобы показать эту гарантию. Я предполагаю, что на практике этого достигает другая внутренняя функция.

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

if a < b < c < d 

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

Оператор потока << в 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) + , который остается ассоциативным. Правильная ассоциативность будет оцениваться как 5 - (2 + , а это неправильно.

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

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

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 ? б ? 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 во многих случаях достигает того же эффекта. InC# существует нулевой оператор коалесцирования ?? который использует null

Это звучит как половина того, что делает тернарный оператор. Мы могли бы посмотреть на это так: cond ? some_val | default_val . То есть, рассмотрим всю левую часть, минусы|/? 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;

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

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

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