Почему большинство основных языков не поддерживают синтаксис «x <y <z» для трехсторонних логических сравнений?


34

Если я хочу сравнить два числа (или другие хорошо упорядоченные объекты), я бы сделал это с x < y. Если я захочу сравнить три из них, ученик средней школы по алгебре предложит попробовать x < y < z. Затем программист во мне ответит: «Нет, это не правильно, вы должны это сделать x < y && y < z».

Кажется, что большинство языков, с которыми я сталкивался, не поддерживают этот синтаксис, что странно, учитывая, насколько часто он встречается в математике. Python является заметным исключением. JavaScript выглядит как исключение, но на самом деле это просто неудачный побочный продукт приоритета операторов и неявных преобразований; в node.js 1 < 3 < 2оценивает true, потому что это действительно так (1 < 3) < 2 === true < 2 === 1 < 2.

Итак, мой вопрос заключается в следующем: почему x < y < zне доступно на языках программирования с ожидаемой семантикой?


1
Вот файл грамматики, который они легко вставляют прямо в документацию по Python - я не думаю, что это так сложно: docs.python.org/reference/grammar.html
Аарон Холл,

Я не знаю других языков так же хорошо, как Python, но я могу говорить о простоте его интерпретации Python. Возможно, я должен ответить. Но я не согласен с выводом gnasher729 об ущербе.
Аарон Холл

@ErikEidt - Требуется возможность писать математические выражения так, как нас учили в старших классах (или раньше). Каждый, кто склонен к математике, знает, что означает $ a <b <c <d $. То, что функция существует, не означает, что вы должны ее использовать. Те, кому это не нравится, всегда могут сделать личное или проектное правило, запрещающее его использование.
Дэвид Хаммен,

2
Я думаю, что все сводится к тому, что команде C # (в качестве примера) лучше исследовать LINQ, и в будущем, возможно, типы записей и сопоставление с образцом будут лучше, чем добавлять синтаксический сахар, который спасет людей от 4 нажатий клавиш, а не на самом деле. добавить любую выразительность (вы также можете написать helpermethods, например, static bool IsInRange<T>(this T candidate, T lower, T upper) where T : IComparable<T>если это действительно мешает вам видеть &&s)
Сара

1
SQL довольно «основной», и вы можете написать «х между 1 и 10»
JoelFan

Ответы:


30

Это бинарные операторы, которые в цепочке обычно и естественно создают абстрактное синтаксическое дерево, подобное:

нормальное абстрактное синтаксическое дерево для бинарных операторов

Когда вычисляется (что вы делаете из листьев), это приводит к логическому результату x < y, затем вы пытаетесь сделать ошибку типа boolean < z. Для того, x < y < zчтобы работать так, как вы обсуждали, вы должны создать специальный случай в компиляторе для создания синтаксического дерева, такого как:

синтаксическое дерево особого случая

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


1
"тогда вы получаете ошибку типа, пытаясь сделать логическое <z" - не, если компилятор допускает цепочку, вычисляя y на месте для сравнения z. «Это добавляет много места для того, чтобы что-то пошло не так, чего разработчики языка скорее бы избегали, если это возможно». На самом деле, у Python нет проблем с этим, и логика синтаксического анализа сводится к одной функции: hg.python.org/cpython/file/tip/Python/ast.c#l1122 - не так много места для работы неправильно. «иногда действует как бинарный оператор, а иногда эффективно действует как троичный оператор». В Python вся цепочка сравнения является троичной.
Аарон Холл

2
Я никогда не говорил, что это невозможно, просто дополнительная работа с дополнительной сложностью. Другие языки не должны писать какой-либо отдельный код только для обработки своих операторов сравнения. Вы получаете это бесплатно с другими бинарными операторами. Вам просто нужно указать их приоритет.
Карл Билефельдт

Да, но ... это уже троичный оператор доступен во многих языках?
JensG

1
@JensG Обозначение троичного означает, что требуется 3 аргумента. В контексте вашей ссылки это троичный оператор условия. Видимо, «трина» в термине, придуманном для оператора, который, кажется, принимает 2, но на самом деле занимает 3. Моя основная проблема с этим ответом - это в основном FUD.
Аарон Холл

2
Я один из тех, кто одобряет этот принятый ответ. (@JesseTG:. Пожалуйста , unaccept этого ответа) Этот вопрос путает какие x<y<zсредства, или , что более важно, x<y<=z. Этот ответ интерпретируется x<y<zкак троичный оператор. Именно так не следует интерпретировать это четко определенное математическое выражение. x<y<zвместо стенографии (x<y)&&(y<z). Индивидуальные сравнения все еще бинарные.
Дэвид Хаммен,

37

Почему x < y < zобычно не доступно на языках программирования?

В этом ответе я заключаю, что

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

Введение

Я могу говорить с точки зрения Pythonist по этому вопросу. Я являюсь пользователем языка с этой функцией, и мне нравится изучать детали реализации языка. Помимо этого, я немного знаком с процессом изменения языков, таких как C и C ++ (стандарт ISO регулируется комитетом, а версия определяется по годам), и я наблюдал, как в Ruby и Python вносятся принципиальные изменения.

Документация и реализация Python

Из документации / грамматики мы видим, что мы можем связать любое количество выражений с операторами сравнения:

comparison    ::=  or_expr ( comp_operator or_expr )*
comp_operator ::=  "<" | ">" | "==" | ">=" | "<=" | "!="
                   | "is" ["not"] | ["not"] "in"

и документация далее заявляет:

Сравнения могут быть связаны произвольно, например, x <y <= z эквивалентно x <y и y <= z, за исключением того, что y вычисляется только один раз (но в обоих случаях z вообще не оценивается, когда найдено x <y) быть ложным).

Логическая эквивалентность

Так

result = (x < y <= z)

логически эквивалентны с точки зрения оценки x, yи z, за исключением того , yвычисляется дважды:

x_lessthan_y = (x < y)
if x_lessthan_y:       # z is evaluated contingent on x < y being True
    y_lessthan_z = (y <= z)
    result = y_lessthan_z
else:
    result = x_lessthan_y

Опять же, разница в том, что у оценивается только один раз (x < y <= z).

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

Проверка разобранного абстрактного синтаксического дерева

Мы можем проверить, как Python анализирует цепочки операторов сравнения:

>>> import ast
>>> node_obj = ast.parse('"foo" < "bar" <= "baz"')
>>> ast.dump(node_obj)
"Module(body=[Expr(value=Compare(left=Str(s='foo'), ops=[Lt(), LtE()],
 comparators=[Str(s='bar'), Str(s='baz')]))])"

Таким образом, мы видим, что Python или любой другой язык действительно не трудно разобрать.

>>> ast.dump(node_obj, annotate_fields=False)
"Module([Expr(Compare(Str('foo'), [Lt(), LtE()], [Str('bar'), Str('baz')]))])"
>>> ast.dump(ast.parse("'foo' < 'bar' <= 'baz' >= 'quux'"), annotate_fields=False)
"Module([Expr(Compare(Str('foo'), [Lt(), LtE(), GtE()], [Str('bar'), Str('baz'), Str('quux')]))])"

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

Вывод по Python

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

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

Так почему же x <y <z обычно не доступно в языках программирования?

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

Подобные вопросы можно задать о других более важных языковых особенностях

Почему множественное наследование недоступно в Java или C #? Здесь нет хорошего ответа на любой вопрос . Возможно, разработчики были слишком ленивы, как утверждает Боб Мартин, и приведенные причины являются лишь оправданиями. И множественное наследование - довольно большая тема в информатике. Это, безусловно, важнее, чем цепочка операторов.

Существуют простые обходные пути

Цепочка операторов сравнения элегантна, но отнюдь не так важна, как множественное наследование. И так же, как Java и C # имеют обходные пути интерфейсов, так и каждый язык для множественных сравнений - вы просто объединяете сравнения с логическими "и", что работает достаточно легко.

Большинство языков регулируются комитетом

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

Могут ли языки, которые не предлагают эту функцию, измениться?

Если язык позволяет x < y < zбез ожидаемой математической семантики, это было бы серьезным изменением. Если бы он не позволял это во-первых, добавить это было бы почти тривиально.

Ломать изменения

Что касается языков с критическими изменениями: мы обновляем языки с критическими изменениями в поведении, но пользователям, как правило, это не нравится, особенно пользователям функций, которые могут быть повреждены. Если пользователь полагается на прежнее поведение x < y < z, они, вероятно, будут громко протестовать. И поскольку большинство языков управляется комитетом, я сомневаюсь, что у нас будет много политической воли, чтобы поддержать такие изменения.


Честно говоря, я не несу проблемы с семантикой , предусмотренных языках, операции сравнения цепи , такие как `х <у <г ' , но это тривиально для разработчиков мысленно карту x < y < zк (x < y) && (y < z). Выбирая гниды, ментальная модель для цепного сравнения - общая математика. Классическое сравнение - это не математика в целом, а логическая логика. x < yвыдает двоичный ответ {0}. y < zвыдает двоичный ответ {1}. {0} && {1}производит описательный ответ. Логика составлена, а не наивно скована.
К. Алан Бейтс

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

2
Основная причина, по которой немногие языки реализуют эту функцию, заключается в том, что до Гвидо никто даже не думал об этом. Языки, которые наследуются от C, не могут получить это «правильное» (математически правильное) сейчас, главным образом потому, что разработчики C получили «неправильное» (математически неправильное) более 40 лет назад. Существует много кода, который зависит от противоречивой природы интерпретации этих языков x<y<z. У языка когда-то был шанс получить что-то подобное, и этот шанс есть у самого языка.
Дэвид Хаммен,

1
@ K.AlanBates Вы делаете 2 замечания: 1) цепочка операторов неаккуратна и 2) что синтаксический сахар не имеет значения. К первому: я продемонстрировал, что операторная цепочка на 100% детерминирована, не так ли? Возможно, некоторые программисты слишком ленивы, чтобы расширить свои возможности для понимания конструкции? Ко второму пункту: для меня это звучит так, будто вы прямо спорите против читабельности? Разве синтаксический сахар обычно не считается хорошей вещью, когда он улучшает читабельность? Если так думать нормально, почему программист не хочет общаться таким образом? Код должен быть написан для чтения, нет?
Аарон Холл

2
I have watched both Ruby and Python implement breaking changes.Для тех , кому любопытно, вот изменение ломка в C # 5.0 с участием переменных цикла и закрытия: blogs.msdn.microsoft.com/ericlippert/2009/11/12/...
user2023861

13

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

Вы можете попросить троичного оператора. Примером может быть x <y <z. Какие комбинации операторов мы допускаем? Очевидно, x> y> z или x> = y> = z или x> y> = z или, возможно, x == y == z должно быть разрешено. Что насчет x <y> z? х! = у! = г? Что означает последнее: x! = Y и y! = Z или что все три разные?

Теперь продвижение аргументов: в C или C ++ аргументы будут преобразованы в общий тип. Итак, что x <y <z означает, что x двойное, а y и z длинные long int? Все три повышены в два раза? Или y берется как double один раз и как long long в другой раз? Что произойдет, если в C ++ один или оба оператора перегружены?

И наконец, вы допускаете любое количество операндов? Как <b> c <d> e <f> g?

Ну, все становится очень сложно. Теперь я не возражаю против того, чтобы x <y <z вызывал синтаксическую ошибку. Потому что полезность этого невелика по сравнению с ущербом, нанесенным новичкам, которые не могут понять, что на самом деле делает x <y <z.


4
Короче говоря, это просто сложная функция для хорошего дизайна.
Джон Пурди,

2
Это на самом деле не причина объяснять, почему ни один хорошо известный язык не содержит эту функцию. На самом деле, довольно легко включить его в язык четко определенным образом. Это просто вопрос просмотра его в виде списка, связанного с операторами аналогичного типа, а не с тем, чтобы каждый оператор был двоичным. То же самое можно сделать для сумм x + y + z, с той лишь разницей, что это не подразумевает какой-либо семантической разницы. Так что просто ни один известный язык никогда не обращал на это внимания.
cmaster

1
Я думаю, что в Python это что-то вроде оптимизации ( x < y < zэквивалентно, ((x < y) and (y < z))но с yоценкой только один раз ), которую я представляю, что скомпилированные языки оптимизируют свой путь. «Потому что его полезность невелика по сравнению с ущербом, который наносят новичкам, которые не могут понять, что на самом деле делает x <y <z». Я думаю, что это невероятно полезно. Вероятно, будет -1 за это ...
Аарон Холл

Если цель состоит в том, чтобы разработать язык, который исключает все вещи, которые могут сбить с толку самых глупых программистов, такой язык уже существует: COBOL. Я бы предпочел использовать сам Python, где действительно можно писать a < b > c < d > e < f > g, с «очевидным» значением (a < b) and (b > c) and (c < d) and (d > e) and (e < f) and (f > g). То, что ты можешь писать, не значит, что ты должен писать. Устранение таких чудовищ является целью обзора кода. С другой стороны, написание 0 < x < 8на python имеет очевидное (без пугающих кавычек) значение, что x лежит между 0 и 8, исключая.
Дэвид Хаммен,

@DavidHammen, по иронии судьбы, COBOL действительно разрешает <b <c
JoelFan

10

Во многих языках программирования x < yэто двоичное выражение, которое принимает два операнда и оценивает один логический результат. Поэтому, если сцепление несколько выражений, true < zи false < zне будет иметь смысла, и если эти выражения успешно оценить, они , вероятно , чтобы получить неверный результат.

Гораздо проще x < yпредставить вызов функции, который принимает два параметра и выдает один логический результат. На самом деле, именно столько языков реализуют это под капотом. Это составное, легко компилируемое, и это просто работает.

x < y < zСценарий гораздо более сложный. Теперь компилятор, по сути, имеет к моде три функции: x < y, y < z, и результат этих двух значений вместе операцию AND, все в контексте , возможно , грамматики неоднозначного языка .

Почему они сделали это по-другому? Потому что это однозначная грамматика, намного проще в реализации и намного легче получить правильную.


2
Если вы разрабатываете язык, у вас есть возможность сделать его правильным .
JesseTG

2
Конечно, это отвечает на вопрос. Если вопрос действительно почему , ответ будет «потому что это то, что выбрали дизайнеры языка». Если вы можете придумать лучший ответ, чем это, пойти на это. Обратите внимание, что Гнашер по существу сказал то же самое в первом абзаце своего ответа .
Роберт Харви

3
Опять ты расщепляешь волосы. Программисты, как правило, делают это. "Вы хотите вынести мусор?" «Нет . » "Вы будете выносить мусор?" "Да."
Роберт Харви

2
Я также оспариваю последний абзац. Python поддерживает сравнения цепочек, и его синтаксический анализатор - LL (1). Также не обязательно сложно определить и реализовать семантику: Python просто говорит, что e1 op1 e2 op2 e3 op3 ...это эквивалентно, за e1 op e2 and e2 op2 e3 and ...исключением того, что каждое выражение оценивается только один раз. ( a == b is True

2
@RobertHarvey. re:answerВот тут-то и отправился мой разум, чтобы прокомментировать основной вопрос. Я не рассматриваю поддержку x < y < zдобавления какого-либо конкретного значения в семантику языка. (x < y) && (y < z)более широко поддерживается, более эксплицитен, более выразителен, более легко переваривается в составные части, более сложен, более логичен, легче реорганизуется.
К. Алан Бейтс

6

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

Теперь давайте посмотрим, как мы могли бы реализовать что-то вроде

a < b < c

Мы могли бы оценить это строго слева направо (слева-ассоциативно):

a.__lt__(b).__lt__(c)

Но теперь мы призываем __lt__к результату a.__lt__(b), который является Boolean. Это бессмысленно.

Давайте попробуем правоассоциативно:

a.__lt__(b.__lt__(c))

Нет, это тоже не имеет смысла. Теперь у нас есть a < (something that's a Boolean).

Хорошо, а как насчет того, чтобы рассматривать его как синтаксический сахар? Давайте составим цепочку из n <сравнений, отправив n-1-иное сообщение. Это может означать, мы посылаем сообщение __lt__к a, передавая bи в cкачестве аргументов:

a.__lt__(b, c)

Хорошо, это работает, но здесь есть странная асимметрия: aнужно решить, будет ли она меньше b. Но bне может решить, будет ли оно меньше c, вместо этого это решение также принимается a.

Как насчет того, чтобы интерпретировать это как n-ary сообщение, отправленное на this?

this.__lt__(a, b, c)

В заключение! Это может работать. Это означает, однако, что упорядочение объектов больше не является свойством объекта (например, aявляется ли он меньше, чем bсвойство aили из b), а вместо этого является свойством контекста (то есть this).

С основной точки зрения это кажется странным. Однако, например, в Haskell, это нормально. OrdНапример, может быть несколько разных реализаций класса типов, и то a, меньше или меньше b, зависит от того, какой экземпляр класса типов находится в области видимости.

Но на самом деле, это не так, что странно на всех! И Java ( Comparator), и .NET ( IComparer) имеют интерфейсы, которые позволяют вам внедрять свои собственные порядковые отношения, например, в алгоритмы сортировки. Таким образом, они полностью признают, что упорядочение не является чем-то фиксированным для типа, а зависит от контекста.

Насколько я знаю, в настоящее время нет языков, которые бы выполняли такой перевод. Однако, есть приоритет: и Ioke, и Seph имеют то, что их конструктор называет «тройными операторами» - операторы, которые синтаксически двоичны, но семантически троичны. Особенно,

a = b

это не интерпретируются как отправка сообщения =для aпрохождения в bкачестве аргумента, а также отправка сообщения =на «текущий Ground» (понятие подобного , но не идентичное this) проходит aи в bкачестве аргументов. Итак, a = bинтерпретируется как

=(a, b)

и нет

a =(b)

Это можно легко обобщить на n-арные операторы.

Обратите внимание, что это действительно свойственно языкам OO. В ОО у нас всегда есть один единственный объект, который в конечном итоге отвечает за интерпретацию отправки сообщения, и, как мы видели, для чего-то подобного не сразу очевидно, a < b < cкакой объект должен быть.

Это не относится к процедурным или функциональным языкам. Например, в схеме , Common Lisp , и Clojure , то <функция п-арной, и может быть вызвана с произвольным числом аргументов.

В частности, <делает не средний «меньше», а эти функции интерпретируются несколько иначе:

(<  a b c d) ; the sequence a, b, c, d is monotonically increasing
(>  a b c d) ; the sequence a, b, c, d is monotonically decreasing
(<= a b c d) ; the sequence a, b, c, d is monotonically non-decreasing
(>= a b c d) ; the sequence a, b, c, d is monotonically non-increasing

3

Это просто потому, что дизайнеры не думали об этом или не думали, что это хорошая идея. Python делает это, как вы описали, с простой (почти) LL (1) грамматикой.


1
Это все равно будет разбираться с обычной грамматикой практически на любом основном языке; это просто не будет правильно понято по причине, которую дал @RobertHarvey.
Мейсон Уилер

@MasonWheeler Нет, не обязательно. Если правила написаны так, что сравнения взаимозаменяемы с другими операторами (скажем, потому что они имеют одинаковый приоритет), то вы не получите правильное поведение. Тот факт, что Python помещает все сравнения на один уровень, позволяет ему рассматривать последовательность как конъюнкцию.
Нил Г,

1
На самом деле, нет. Поместите 1 < 2 < 3в Java или C #, и у вас нет проблем с приоритетом операторов; у вас проблема с недопустимыми типами. Проблема в том, что он все равно будет анализироваться в точности так, как вы его написали, но вам нужна специальная логика в компиляторе, чтобы превратить ее из последовательности отдельных сравнений в цепочечное сравнение.
Мейсон Уилер

2
@MasonWheeler То, что я говорю, это то, что язык должен быть разработан, чтобы он работал. Одна часть этого - получить правильную грамматику. (Если правила написаны так, что сравнения взаимозаменяемы с другими операторами, скажем, потому что они имеют одинаковый приоритет, то вы не получите правильное поведение.) Другая часть этого - интерпретация AST как конъюнкции, которая в C ++ не делает Суть моего ответа в том, что это решение дизайнера языка.
Нил Г,

@MasonWheeler Я думаю, что мы согласны. Я просто подчеркнул, что нетрудно получить грамматику для этого. Это просто вопрос заранее решить, что вы хотите, чтобы это работало таким образом.
Нил Г,

2

Следующая программа на C ++ компилируется с nary peep from clang, даже с предупреждениями, установленными на максимально возможный уровень ( -Weverything):

#include <iostream>
int main () { std::cout << (1 < 3 < 2) << '\n'; }

С другой стороны, набор компиляторов gnu предупреждает меня об этом comparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses].

Итак, мой вопрос заключается в следующем: почему x <y <z обычно не доступно в языках программирования с ожидаемой семантикой?

Ответ прост: обратная совместимость. Существует огромное количество кода, который использует эквивалент 1<3<2и ожидает, что результат будет истинным.

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


+1, потому что эта программа выводит «1» в результате выражения, которое явно неверно в математике. Хотя это и придумано, реальный пример с непонятными именами переменных стал бы кошмаром отладки, если бы эта языковая функция была добавлена.
Сет Баттин

@SethBattin - Это не отладочный кошмар в Python. Единственная проблема в Python: « if x == y is True : ...Мое мнение: люди, которые пишут такой код, заслуживают того, чтобы подвергнуться каким-то сверхспециальным, необычайным пыткам, которые (если бы он был жив сейчас) сделали бы Торквемаду слабым.
Дэвид Хаммен,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.