Почему определяемые пользователем операторы не являются более распространенными?


94

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

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

Я огляделся и не смог найти процедурных языков, которые бы поддерживали пользовательские операторы на этом языке. Есть хаки (такие как макросы в C ++), но это не то же самое, что поддержка языка.

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

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

Фактические варианты использования:

  • Реализовать пропущенные операторы (например, Lua не имеет побитовых операторов)
  • Mimic D's ~(конкатенация массивов)
  • DSL,
  • Использовать |как синтаксический сахар в стиле конвейера Unix (используя сопрограммы / генераторы)

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


4
По этому вопросу идет обсуждение в Reddit .
Динамичный

2
@dimatura: Я не знаю много о R, но его пользовательские операторы все еще не кажутся очень гибкими (например, нет правильного способа определения фиксированности, объема, перегрузки и т. д.), что объясняет, почему они не используются часто. Это отличается на других языках, конечно , в Haskell , который использует пользовательские инфиксные операторы уйму . Другим примером процедурного языка с разумной поддержкой нестандартного инфикса является Nimrod , и, конечно, Perl также позволяет им .
оставил около

4
Есть и другой вариант, Лисп фактически не имеет разницы между операторами и функциями.
БородатыйО

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

2
@BeardedO, в Лиспе вообще нет инфиксных операторов. Как только вы представите их, вам придется решать все вопросы с приоритетом и тому подобное.
SK-logic

Ответы:


134

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

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


23
В двух школах мысли plus.google.com/110981030061712822816/posts/KaSKeg4vQtz также +1 за самый прямой (и, вероятно, наиболее верный) ответ на вопрос, почему разработчики языка выбирают одно против другого. Я бы также сказал, что на основе вашего тезиса вы могли бы экстраполировать, что меньшее количество языков позволяет это делать, потому что меньшая часть разработчиков обладает более высокой квалификацией, чем в противном случае (по определению, верхний процент всего будет меньшинство)
Джимми Хоффа,

12
Я думаю, что этот ответ является расплывчатым и лишь незначительно связан с вопросом. (Мне также кажется, что ваша характеристика неверна, поскольку она противоречит моему опыту, но это не важно.)
Конрад Рудольф

1
Как сказал @KonradRudolph, это на самом деле не отвечает на вопрос. Возьмите такой язык, как Prolog, который позволяет вам делать с операторами все, что вы хотите, в том числе определять их приоритет при размещении инфикса или префикса. Я не думаю, что тот факт, что вы можете писать собственные операторы, имеет какое-либо отношение к уровню квалификации целевой аудитории, а скорее потому, что цель Пролога - читать как можно более логично. Включение пользовательских операторов позволяет писать программы, которые читаются очень логично (в конце концов, программа пролога - это просто набор логических утверждений).
Дэвид Коуден

Как я скучал по этой напыщенной речи? О человек, в закладки
Джордж Мауэр

В какую философию я бы попал, если бы думал, что программисты, скорее всего, напишут лучший код, если языки предоставят им инструменты, позволяющие определить, когда компилятор должен делать различные выводы (например, аргументы auto-boxing для Formatметода) и когда он должен отказаться ( например, аргументы автобокса для ReferenceEquals). Чем больше языка способностей дает программистам возможность сказать, когда определенные выводы будут неуместны, тем безопаснее он может предложить удобные выводы в случае необходимости.
суперкат

83

Если бы у меня был выбор между конкатенацией массивов с помощью ~ или «myArray.Concat (secondArray)», я бы предпочел последнее. Почему? Потому что ~ - это абсолютно бессмысленный символ, который имеет только значение - конкатенацию массива - заданный в конкретном проекте, в котором он был написан.

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

Вот почему мне также не нравится .оператор PHP (конкатенация строк) или большинство операторов в Haskell или OCaml, хотя в этом случае появляются некоторые общепринятые стандарты для функциональных языков.


27
То же самое можно сказать обо всех операторах. Что делает их хорошими, а также может сделать пользовательские операторы хорошими (если они определены разумно), так это: несмотря на то, что используемый для них символ является просто символом, широкое использование и, возможно, мнемонические свойства немедленно делают его значение в исходном коде очевидно для тех, кто знаком с этим. Так что ваш ответ в его нынешнем виде не настолько убедителен, ИМХО.

33
Как я упоминал в конце, некоторые операторы имеют универсальную распознаваемость (например, стандартные арифметические символы, битовые сдвиги, И / ИЛИ и т. Д.), И поэтому их краткость перевешивает их непрозрачность. Но способность определять произвольные операторы, кажется, сохраняет худшее из обоих миров.
Авнер Шахар-Каштан

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

9
Я не согласен с тем, что пользовательские операторы «добавляют» функцию, они снимают ограничение. Операторы, как правило, являются просто функциями, поэтому вместо статической таблицы символов для операторов можно использовать динамическую, зависящую от контекста. Я полагаю , что это как оператор перегрузки обрабатываются, так +и , <<конечно , не определены на Object(я не получаю «не подходят для оператора + в ...» , когда делает что на голом классе в C ++).
beatgammit

10
Чтобы понять, нужно много времени, что программирование обычно не заставляет компьютер что-то делать для вас, а генерирует документ, который могут читать как люди, так и компьютеры, причем люди гораздо важнее компьютеров. Этот ответ в точности верен. Если следующий человек (или вы через 2 года) должен потратить хотя бы 10 секунд, пытаясь выяснить, что означает ~, вы могли бы с тем же успехом потратить 10 секунд, набрав вместо этого вызов метода.
Билл К

71

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

Ваша предпосылка неверна. Это не «довольно тривиально для реализации». На самом деле это приносит массу проблем.

Давайте посмотрим на предлагаемые «решения» в посте:

  • Нет приоритета . Сам автор говорит: «Не использовать правила приоритета просто не вариант».
  • Семантический осознанный разбор . Как говорится в статье, для этого потребуется, чтобы компилятор обладал большим семантическим знанием. На самом деле статья не предлагает решения для этого, и позвольте мне сказать вам, это просто не тривиально. Компиляторы разработаны как компромисс между мощностью и сложностью. В частности, автор упоминает этап предварительного синтаксического анализа для сбора соответствующей информации, но предварительный анализ неэффективен, и компиляторы очень стараются минимизировать проходы синтаксического анализа.
  • Нет пользовательских инфиксных операторов . Ну, это не решение.
  • Гибридное решение . Это решение несет в себе многие (но не все) недостатки синтаксического анализа. В частности, поскольку компилятор должен обрабатывать неизвестные токены как потенциально представляющие пользовательские операторы, он часто не может выдавать значимые сообщения об ошибках. Также может потребоваться, чтобы определение указанного оператора продолжало синтаксический анализ (для сбора информации о типе и т. Д.), Еще раз требуя дополнительного прохода синтаксического анализа.

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


2
Спасибо за голос здравомыслия. Мой опыт показывает, что люди, которые отвергают вещи как тривиальные, являются либо экспертами, либо блаженно невежественными, и чаще в последней категории: x
Матье М.

14
каждая из этих проблем была решена на языках, которые существовали в течение 20 лет ...
Филип Дж. Ф.

11
И все же, это исключительно тривиально для реализации. Забудьте обо всех тех алгоритмах разбора книги драконов, это 21-й век, и пришло время двигаться дальше. Даже расширение самого синтаксиса языка легко, а добавление операторов данного приоритета тривиально. Взгляните, скажем, на анализатор Haskell. Это намного, намного проще, чем парсеры для «основных» раздутых языков.
SK-logic

3
@ SK-logic Твое утверждение не убеждает меня. Да, разбор продолжился. Нет, реализация произвольных операторных приоритетов в зависимости от типа не является «тривиальной». Создание хороших сообщений об ошибках в ограниченном контексте не является «тривиальным». Создание эффективных синтаксических анализаторов с языками, для преобразования которых требуется несколько проходов, невозможно.
Конрад Рудольф

6
В Haskell синтаксический анализ «прост» (по сравнению с большинством языков). Приоритет не зависит от контекста. Часть, которая выдает жесткие сообщения об ошибках, связана с классом типов и расширенными функциями системы типов, а не с пользовательскими операторами. Это перегрузка, а не определение пользователя, это сложная проблема.
Филипп JF

25

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

Инфиксные операторы имеют больше проблем, чем простые правила приоритетов (хотя, если говорить прямо, ссылка, на которую вы ссылаетесь, упрощает воздействие этого проектного решения). Одним из них является разрешение конфликтов: что происходит, когда вы определяете a.operator+(b)и b.operator+(a)? Предпочтение одного над другим приводит к нарушению ожидаемого коммутативного свойства этого оператора. Сгенерирование ошибки может привести к тому, что модули, которые в противном случае будут работать, будут сломаны вместе Что происходит, когда вы начинаете бросать производные типы в микс?

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

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

И, честно говоря, потому что они не тривиальны для реализации.


1
Я полагаю, что это причина, по которой Java не включает их, но языки, в которых они есть, имеют четкие правила приоритета. Я думаю, что это похоже на умножение матриц. Это не коммутативный, поэтому вы должны знать, когда вы используете матрицы. Я думаю, что то же самое относится и к занятиям, но да, я согласен, что некоммутативность +- это зло. Но действительно ли это аргумент против пользовательских операторов? Это похоже на аргумент против перегрузки операторов в целом.
beatgammit

1
@tjameson - Да, у языков есть четкие правила для старшинства, для математики . Правила приоритета для математических операций, вероятно, не будут действительно применяться, если операторы перегружены для таких вещей, как boost::spirit. Как только вы разрешите пользовательские операторы, ситуация ухудшится, так как нет хорошего способа даже четко определить приоритет для математики. Я сам немного об этом написал в контексте языка, специально предназначенного для решения проблем с произвольно заданными операторами.
Теластин

22
Я называю ерунду, сэр. За пределами ОО-гетто существует жизнь, и там функции не обязательно принадлежат какому-либо объекту, поэтому поиск правильной функции точно такой же, как и поиск правильного оператора.
Матье М.

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

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

19

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

Зачем использовать ~ для объединения в массив? Почему бы не использовать << как в Ruby ? Потому что программисты, с которыми вы работаете, вероятно, не являются программистами Ruby. Или D программисты. Так что же они делают, когда сталкиваются с вашим кодом? Они должны пойти и посмотреть, что означает этот символ.

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

Как вы думаете, достаточно справедливо? Это была только небольшая команда. Лично я не согласен. Каждому новому разработчику суждено было запутаться в этой терминологии. У нас не хватает проблем с изучением нового домена?

С другой стороны, я с удовольствием использовать в ??оператор в C # , потому что я ожидаю других разработчиков C # знать , что это такое, но я бы не перегружать его на языке , который не поддерживает его по умолчанию.


Я не понимаю ваш пример Double.NaN, это поведение является частью спецификации с плавающей запятой и поддерживается на каждом языке, который я использовал. Вы говорите, что пользовательские операторы не поддерживаются, потому что это может сбить с толку разработчиков, которые не знают об этой функции? Это звучит как тот же аргумент против использования троичного оператора или вашего ??примера.
beatgammit

@tjameson: Вы правы, на самом деле, это было немного касательно того, что я пытался сказать, и не очень хорошо написано. Я думал о ?? пример, как я писал, и предпочитаю этот пример. Удаление двойного.На абзаце.
pdr

6
Я думаю, что ваш «каждый новый разработчик был обречен на путаницу в этой терминологии», это немного тускло, беспокоиться о кривой обучения новых разработчиков к вашей базе кода для меня так же, как беспокоиться о кривой обучения новых разработчиков до новая версия .NET. Я думаю, что более важно, когда разработчики изучают его (новый .NET или ваш код), имеет ли это смысл и показывает ли это ценность? Если это так, то кривая обучения того стоит, потому что каждому разработчику нужно изучать его только один раз, хотя код нужно обрабатывать бесчисленное количество раз, поэтому, если он улучшает код, это незначительные затраты.
Джимми Хоффа

7
@JimmyHoffa Это текущие расходы. Оплачивается авансом за каждого нового разработчика, а также влияет на существующих разработчиков, потому что они должны поддерживать его. Существует также стоимость документации и элемент риска - сегодня это кажется достаточно безопасным, но через 30 лет все уйдут, язык и приложение теперь "унаследованы", документация - огромная куча паров и какой-то бедный лох будет оставлен рвать на себе волосы от блеска программистов, которые были слишком ленивы, чтобы напечатать ".concat ()". Достаточно ли ожидаемого значения для компенсации затрат?
Сильвердраг

3
Кроме того, аргумент «Каждому новому разработчику суждено было запутаться в этой терминологии. У нас недостаточно проблем с изучением нового домена?» мог быть применен к ООП еще в 80-х, структурированное программирование до этого или IoC 10 лет назад. Пришло время прекратить использовать этот ошибочный аргумент.
Маурисио Шеффер

11

Я могу придумать несколько причин:

  • Их нетривиально реализовать - разрешение произвольных пользовательских операторов может сделать ваш компилятор намного более сложным, особенно если вы разрешите пользовательские правила приоритетов, фиксированности и арности. Если простота является достоинством, то перегрузка операторов отвлекает вас от хорошего языкового дизайна.
  • Они подвергаются насилию - в основном со стороны программистов, которые считают «круто» переопределять операторы и начинать переопределять их для всех видов пользовательских классов. Вскоре в вашем коде полно пользовательских символов, которые никто не может прочитать или понять, потому что операторы не следуют общепринятым и понятным правилам. Я не покупаю аргумент "DSL", если только ваш DSL не является подмножеством математики :-)
  • Они ухудшают читабельность и ремонтопригодность - если операторы регулярно переопределяются, может быть трудно определить, когда используется это средство, и кодировщики вынуждены постоянно спрашивать себя, что делает оператор. Гораздо лучше давать значимые имена функций. Ввод нескольких дополнительных символов обходится дешево, проблемы с длительным обслуживанием стоят дорого.
  • Они могут нарушить неявные ожидания производительности . Например, я обычно ожидаю, что поиск элемента в массиве будет O(1). Но с перегрузкой оператора someobject[i]может легко быть O(n)операция в зависимости от реализации оператора индексации.

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

Некоторые интересные случаи для рассмотрения:

  • Лисп : вообще не различать операторы и функции - +это просто обычная функция. Вы можете определять функции по своему усмотрению (обычно есть способ определения их в отдельных пространствах имен, чтобы избежать конфликта со встроенными +), включая операторы. Но существует культурная тенденция использовать значимые имена функций, поэтому этим не злоупотребляют. Кроме того, в префиксной нотации Lisp имеет тенденцию использоваться исключительно, поэтому в «синтаксическом сахаре», который обеспечивают перегрузки операторов, меньше значения.
  • Java - запрещает перегрузку операторов. Иногда это раздражает (для таких вещей, как случай с комплексным числом), но в среднем это, вероятно, правильное решение для разработки Java, которое предназначено как простой универсальный язык ООП. В результате этой простоты Java-код довольно легко поддерживается разработчиками с низким и средним уровнем квалификации.
  • C ++ имеет очень сложную перегрузку операторов. Иногда этим злоупотребляют ( cout << "Hello World!"кто-нибудь?), Но этот подход имеет смысл, учитывая позиционирование C ++ как сложного языка, который позволяет программировать на высоком уровне, в то же время позволяя вам очень близко подходить к производительности, поэтому вы можете, например, написать класс сложных чисел, который ведет себя именно так, как вы хотите, без ущерба для производительности. Понятно, что это ваша собственная ответственность, если вы стреляете себе в ногу.

8

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

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

Тем не менее, я могу думать о трех языках, которые делают это, и они делают это по-разному:

  • Racket, схема, когда это не все, S-выражение-y позволяет и ожидает, что вы напишите, что составляет синтаксический анализатор для любого синтаксиса, который вы хотите расширить (и предоставляет полезные хуки, чтобы сделать это доступным).
  • Haskell, чисто функциональный язык программирования, позволяет определять любой оператор, состоящий исключительно из знаков препинания, и позволяет вам обеспечить уровень фиксированности (доступно 10) и ассоциативность. Тернарные и т. Д. Операторы могут быть созданы из бинарных операторов и функций более высокого порядка.
  • Agda, в зависимости типизированный язык программирования, является чрезвычайно гибкой с операторами (бумага здесь ) , позволяющий как если-то , и если-то-иначе должны быть определены как операторы в одной и той же программы, но его лексический, синтаксический анализатор и оценщик все неидеальной в следствии.

4
Вы сказали, что это «не тривиально», и сразу же перечислили три языка, демонстрирующие некоторые чрезвычайно тривиальные реализации. Так ведь это довольно тривиально, не так ли?
SK-logic

7

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

Например cstream, очень критикуется перегрузка влево.

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

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


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

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

3
И чем это отличается от перегрузки метода?
Йорг Миттаг

@ JörgWMittag с перегрузкой метода (в большинстве случаев) прилагается значимое имя. с символом сложнее кратко объяснить, что произойдет
чокнутый урод

1
@ratchetfreak: Хорошо. +добавить две вещи, -вычитает их, *умножает их. Я чувствую, что никто не заставляет программиста заставлять функцию / метод addфактически добавлять что-либо и doNothingможет запускать ядерное оружие. И a.plus(b.minus(c.times(d)).times(e)гораздо менее читабельно a + (b - c * d) * e(добавленный бонус - в первом укусе ошибка в транскрипции). Я не вижу, как первый более значимым ...
Мацей Пехотка

4

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

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


1
Добавьте к этому возможность раскрыть смысл функции. В случае «sworp» вы можете легко вставить его в окно поиска и найти документацию. Застревание оператора в поисковике - это совсем другой шар. Наши текущие поисковые системы просто отстой в поиске операторов, и я потратил много времени, пытаясь найти значения в некоторых случаях.
Марк Х

1
Сколько "школы" вы считаете? Например, я думаю, что for elemявляется отличной идеей, и, безусловно, оператор должен понимать каждый, но другие, похоже, не согласны.
Тихон Джелвис

1
Это не проблема с символом. Это проблема с клавиатурой. Я, например, использую emacs haskell-mode с включенным украшением Юникода. И он автоматически конвертирует ascii операторы в символы юникода. Но я бы не смог набрать его, если бы не было эквивалента ASCII.
Вагиф Верди

2
Естественные языки состоят только из «пользовательских слов» и ничего более. И некоторые языки на самом деле поощряют формирование пользовательских слов.
SK-logic

4

Что касается языков, которые поддерживают такую ​​перегрузку: Scala делает, на самом деле гораздо чище и лучше, чем C ++. Большинство символов можно использовать в именах функций, поэтому вы можете определять операторы, как! + * = ++, если хотите. Есть встроенная поддержка инфикса (для всех функций, принимающих один аргумент). Я думаю, что вы также можете определить ассоциативность таких функций. Вы не можете, однако, определить приоритет (только с уродливыми уловками, см. Здесь ).


4

Одна вещь, которая еще не упоминалась, - это случай Smalltalk, где все (включая операторов) является отправкой сообщения. «Операторы» , как +, |и так далее, на самом деле одинарные методы.

Все методы могут быть переопределены, поэтому a + bозначает целочисленное сложение, если aи bоба являются целыми числами, и означает векторное сложение, если они оба OrderedCollections.

Нет никаких правил приоритета, так как это всего лишь вызовы методов. Это имеет важное значение для стандартных математических обозначений: 3 + 4 * 5значит (3 + 4) * 5, нет 3 + (4 * 5).

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


3

Вы боретесь против двух вещей здесь:

  1. Почему операторы существуют в языках в первую очередь?
  2. Каково преимущество операторов над функциями / методами?

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

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

Перегрузки операторов в C ++ обеспечивают благоприятную почву для изучения этого. «Злоупотребление» перегрузкой операторов происходит в форме перегрузок, которые нарушают некоторые из семантических контрактов, которые широко понимаются (классический пример - перегрузка оператора + такая, что a + b! = B + a, или где + изменяет любой из его операнды).

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


1

Я всегда считал, что перегрузки операторов в C ++ удобны для команды разработчиков, но в долгосрочной перспективе это приводит к всевозможным путаницам просто потому, что вызовы методов «скрыты» таким образом, что это нелегко чтобы такие инструменты, как doxygen, можно было разобрать, и люди должны понимать идиомы, чтобы правильно их использовать.

Иногда даже понять гораздо сложнее, чем вы ожидаете. Давным-давно, в большом кроссплатформенном проекте C ++ я решил, что было бы неплохо нормализовать способ построения путей, создав FilePathобъект (аналогичный Fileобъекту Java ), в котором был бы оператор / использовался для объединения другого часть пути к нему (так что вы можете сделать что-то подобное, File::getHomeDir()/"foo"/"bar"и это будет делать правильно на всех наших поддерживаемых платформах). Каждый, кто видел это, по сути говорил: «Какого черта? Струнное деление? ... О, это мило, но я не верю, что это правильно».

Точно так же в графическом программировании или в других областях много случаев, когда математика векторов / матриц часто встречается, когда соблазнительно делать такие вещи, как матрица * матрица, вектор * вектор (точка), вектор% вектор (крест), матрица * вектор ( матричное преобразование), Matrix ^ Vector (матричное преобразование в особом случае, игнорирующее однородную координату - полезно для нормалей поверхности) и т. д., но, хотя это экономит немного времени на разбор для человека, который написал библиотеку векторной математики, он только заканчивается путать вопрос больше для других. Это просто не стоит того.


0

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

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

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

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


«Один и тот же символ на экране будет иметь разные значения в зависимости от того, что вокруг него», - так обстоит дело уже для многих операторов в большинстве языков.
rkj

-1

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

Операторы, как правило, представляют собой короткие логические конструкции, которые хорошо определены и задокументированы на базовом языке (сравните, назначьте ..). Они также , как правило , трудно понять без документации (сравните a^bс xor(a,b), например). Существует довольно ограниченное количество операторов, которые могут иметь смысл в обычном программировании (>, <, =, + и т. Д.).

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

Ваши случаи использования ~и |были бы возможны при простой перегрузке операторов (C #, C ++ и т. Д.). DSL является допустимой областью использования, но, вероятно, одной из единственных допустимых областей (с моей точки зрения). Я, однако, думаю, что есть лучшие инструменты для создания новых языков внутри. Выполнение настоящего языка DSL на другом языке не так уж сложно, используя любой из этих инструментов компилятор-компилятор. То же самое относится и к «расширению аргумента LUA». Скорее всего, язык определен в первую очередь для решения проблем особым образом, а не как основание для подъязыков (существуют исключения).


-1

Еще одним фактором является то, что не всегда просто определить операцию с доступными операторами. Я имею в виду, да, для любого вида чисел оператор '*' может иметь смысл и обычно реализуется на языке или в существующих модулях. Но в случае типичных сложных классов, которые вам нужно определить (такие как ShipingAddress, WindowManager, ObjectDimensions, PlayerCharacter и т. Д.), Такое поведение неясно ... Что означает добавить или вычесть число в адрес? Умножить два адреса?

Конечно, вы можете определить, что добавление строки в класс ShippingAddress означает пользовательскую операцию, такую ​​как «заменить строку 1 в адресе» (вместо функции «setLine1») и добавление числа «заменить почтовый индекс» (вместо «setZipCode») , но тогда код не очень читабелен и запутан. Обычно мы думаем, что операторы используются в базовых типах / классах, поскольку их поведение интуитивно понятно, понятно и непротиворечиво (по крайней мере, если вы знакомы с языком). Думайте в таких типах, как Integer, String, ComplexNumbers и т. Д.

Таким образом, даже если определение операторов может быть очень полезным в некоторых конкретных случаях, их реальная реализация весьма ограничена, поскольку 99% случаев, когда это будет явным выигрышем, уже реализованы в базовом языковом пакете.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.