Операторы понятнее, чем ключевые слова или функции? [закрыто]

14

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

Haskell несколько печально известен этим, поскольку пользовательские операторы легко создавать, и часто новый тип данных поставляется с несколькими операторами для использования на нем. Например, библиотека Parsec содержит множество операторов для объединения парсеров, например, с самоцветами, >.и .> я даже не могу вспомнить, что они значат прямо сейчас, но я помню, что с ними было очень легко работать, как только я запомнил, что они на самом деле имеют в виду. leftCompose(parser1, parser2)Был бы вызов функции такой, как было бы лучше? Конечно, более многословный, но более понятный в некоторых отношениях.

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

На любом новом языке это может показаться довольно сложной задачей. Например, в F # приведение использует математически выведенный оператор приведения типов вместо синтаксиса приведения стиля C # или многословного стиля VB. C #: (int32) xVB: CType(x, int32)F #:x :> int32

Теоретически новый язык может иметь операторы для большинства встроенных функций. Вместо defили decили varдля объявления переменной, почему нет ! nameили @ nameили что-то подобное. Это, безусловно, сокращает объявление, сопровождаемое связыванием: @x := 5вместо declare x = 5или let x = 5 Большинству кода потребуется много определений переменных, так почему бы и нет?

Когда оператор понятен и полезен, а когда скрыт?

CodexArcanum
источник
1
Я хотел бы, чтобы один из 1000 разработчиков, которые когда-либо жаловались мне на отсутствие перегрузки операторов в Java, ответил бы на этот вопрос.
smp7d
1
Я думал об APL, когда закончил вводить вопрос, но в некотором смысле это проясняет вопрос и переносит его на несколько менее субъективную территорию. Я думаю, что большинство людей могли бы согласиться с тем, что APL теряет что-то в простоте использования из-за большого количества операторов, но количество людей, которые жалуются, когда в языке нет перегрузки операторов, безусловно, говорят в пользу того, что некоторые функции хорошо подходят для выступать в качестве операторов. Между запрещенными пользовательскими операторами и только операторами, должны быть скрыты некоторые рекомендации для практической реализации и использования.
CodexArcanum
На мой взгляд, отсутствие перегрузки операторов нежелательно, потому что она дает привилегии нативным типам по сравнению с пользовательскими (обратите внимание, что Java делает это и другими способами). Я гораздо более амбивалентен в отношении пользовательских операторов в стиле Haskell, которые выглядят как открытое приглашение к неприятностям ...
прибывающая буря
2
@comingstorm: Я на самом деле думаю, что путь на Haskell лучше. Когда у вас есть конечный набор операторов, которые вы можете перегрузить разными способами, вы часто вынуждены повторно использовать операторы в разных контекстах (например, +для конкатенации строк или <<для потоков). С другой стороны, в Haskell оператор является либо просто функцией с произвольным именем (то есть не перегруженной), либо частью класса типов, что означает, что, хотя он полиморфен, он выполняет одинаковую логическую функцию для каждого типа, и даже имеет такую ​​же подпись типа. Так >>это >>для каждого типа и никогда не будет немного сдвиг.
Тихон Джелвис

Ответы:

16

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

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

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

JK.
источник
4
Я хотел бы дать вам секунду +1 за ссылку на газету Дейкстры.
AProgrammer
Отличная ссылка, вам пришлось опубликовать учетную запись PayPal! ;)
Адриано Репетти
8

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

Например, declare x = 5чтение как "declare x equals 5"или let x = 5может быть прочитано как "let x equal 5", что очень понятно, когда читается вслух, однако чтение @x := 5читается как "at x colon equals 5"(или, "at x is defined to be 5"если вы математик), что не имеет смысла вообще.

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

Рейчел
источник
5
Я не думаю, что @x := 5это трудно понять - я все еще читаю это вслух себе set x to be equal to 5.
FrustratedWithFormsDesigner
3
В этом случае @Rachel :=имеет более широкое применение в математической нотации вне языков программирования. Кроме того, такие заявления x = x + 1становятся немного абсурдными.
Ccoakley
3
По иронии судьбы я забыл, что некоторые люди не признают :=это задание. Несколько языков действительно используют это. Smalltalk, я думаю, был, пожалуй, самым известным. Вопрос о том, должны ли присваивание и равенство быть отдельными операторами, - это совершенно другой набор червей, один из которых, вероятно, уже затронут другим вопросом.
CodexArcanum
1
@ccoakley Спасибо, я не знал, что :=использовалось в математике. Единственное определение, которое я нашел для него, было «определено как», поэтому @x := 5будет переведено на английский язык "at x is defined to be 5", что для меня все еще не так важно, как следовало бы.
Рейчел
1
Конечно, Паскаль использовал: = как назначение, из-за отсутствия левой стрелки в ASCII.
Vatine
4

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

Но копать немного дальше, есть два аспекта.

Синтаксис (инфикс для оператора, а не префикс для синтаксиса вызова функции) и наименование.

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

Сравните a * b + c * d + e * fс +(+(*(a, b), *(c, d)), *(e, f))или + + * a b * c d * e f(оба они доступны для тех же условий, что и инфиксная версия). Тот факт, что +отдельные термины вместо длинного предшествуют одному из них, делают его более читабельным в моих глазах (но вы должны помнить правила приоритета, которые не нужны для синтаксиса префиксов). Если вам нужно сложить вещи таким образом, долгосрочная выгода будет стоить затрат на обучение. И вы сохраняете это преимущество, даже если вы называете своих операторов буквами вместо использования символов.

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

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

AProgrammer
источник
Если пользователи могут добавлять операторы, почему бы не позволить им установить приоритет и ассоциативность операторов, которые они добавили?
Тихон Джелвис
@TikhonJelvis, это была в основном консервативность, но есть некоторые аспекты, которые следует учитывать, если вы хотите использовать его для чего-то еще, кроме примеров. Например, вы хотите связать приоритет и ассоциативность с оператором, а не с заданным членом перегруженного набора (вы выбрали элемент после того, как синтаксический анализ выполнен, выбор не может повлиять на синтаксический анализ). Таким образом, чтобы интегрировать библиотеки с использованием одного и того же оператора, они должны согласовать его приоритет и ассоциативность. Прежде чем добавить такую ​​возможность, я постараюсь выяснить, почему так мало языков следуют Алголу 68 (AFAIK нет) и позволяют их определять.
AProgrammer
Ну, Haskell позволяет вам определять произвольные операторы и устанавливать их приоритет и ассоциативность. Разница в том, что вы не можете перегружать операторы как таковые - они ведут себя как обычные функции. Это означает, что у вас не может быть разных библиотек, пытающихся использовать один и тот же оператор для разных вещей. Поскольку вы можете определить свой собственный оператор, гораздо меньше причин использовать одни и те же операторы для разных целей.
Тихон Джелвис
1

Операторы как символы полезны, когда они имеют смысл. +и -, очевидно, являются сложением и вычитанием, например, поэтому почти каждый язык использует их в качестве своих операторов. То же самая со сравнением ( <и >учат в начальной школе, а также <=и >=интуитивные расширения основных сравнений , когда вы понимаете , что нет никакой подчеркнутого-сравнения клавиши на стандартной клавиатуре.)

*и /менее сразу видно, но они используются повсеместно , и их положение на цифровой клавиатуре прямо рядом с +и -ключей помогает обеспечить контекст. C <<и >>находятся в одной категории: когда вы понимаете, что такое сдвиг влево и вправо, не так уж сложно понять мнемонику за стрелками.

Затем вы переходите к некоторым действительно странным вещам, которые могут превратить код C в нечитаемый суп-оператор. (Выбор C здесь, потому что это очень тяжелый операторный пример, синтаксис которого почти всем знаком, либо с помощью C, либо с потомками.) Например, %оператор. Все знают, что это такое: это знак процента ... верно? За исключением C, он не имеет ничего общего с делением на 100; это оператор модуля. Это имеет нулевой смысл мнемонически, но это так!

Еще хуже - логические операторы. &как и имеет смысл, за исключением того, что есть два разных &оператора, которые делают две разные вещи, и оба синтаксически допустимы в любом случае, когда один из них допустим, даже если только один из них действительно имеет смысл в любом данном случае. Это просто рецепт путаницы! Операторы or , xor и not еще хуже, поскольку они не используют мнемонически полезные символы и не являются согласованными. (Нет оператора double- xor , а логический и побитовый не используют два разных символа вместо одного символа и удвоенной версии.)

И что еще хуже, операторы &и *используются повторно для совершенно разных вещей, что усложняет синтаксический анализ как для людей, так и для компиляторов. (Что A * Bозначает? Это полностью зависит от контекста: Aтип или переменная?)

По общему признанию, я никогда не разговаривал с Деннисом Ритчи и спрашивал его о дизайнерских решениях, которые он принимал на языке, но во многом из-за этого философия операторов была «символами ради символов».

Сравните это с Pascal, который имеет те же операторы, что и C, но с другой философией их представления: операторы должны быть интуитивно понятными и легко читаемыми. Арифметические операторы одинаковы. Оператор модуля - это слово mod, потому что на клавиатуре нет символа, который явно означает «модуль». Логические операторы and, or, xorи not, и есть только один друг; компилятор знает, нужна ли вам логическая или побитовая версия, основываясь на том, работаете ли вы с логическими или числовыми значениями, устраняя целый класс ошибок. Это делает код на Паскале намного проще для понимания, чем код на C, особенно в современном редакторе с подсветкой синтаксиса, поэтому операторы и ключевые слова визуально отличаются от идентификаторов.

Мейсон Уилер
источник
4
Паскаль совсем не представляет другую философию. Если вы читаете документы Вирта, он использовал символы для таких вещей, как andи orвсе время. Однако, когда он разработал Pascal, он сделал это на мэйнфрейме Control Data, в котором использовались 6-битные символы. Это вынудило решение использовать слова для многих вещей по той простой причине, что в наборе символов просто не было достаточно места для символов и что-то вроде ASCII. Я использовал и C, и Pascal, и нахожу C значительно более читабельным. Вы можете предпочесть Паскаль, но это дело вкуса, а не факта.
Джерри Гроб
Чтобы добавить к @JerryCoffin замечание о вирте, его последующие языки (Modula, Oberon) используют больше символов.
AProgrammer
@ AProgrammer: И они никогда никуда не ходили. ;)
Мейсон Уилер
Я думаю |(и, соответственно, ||) для или имеет смысл. Вы также видите это все время для чередования в других контекстах, таких как грамматики, и похоже, что это разделяет два варианта.
Тихон Джелвис
1
Преимущество буквенных ключевых слов в том, что их пространство намного больше символов. Например, язык стиля Паскаля может включать в себя операторы как для «модуля», так и для «остатка», а также для целочисленного и деления с плавающей запятой (которые имеют различные значения, помимо типов их операндов).
суперкат
1

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

Я боролся с горячими операторами для таких функций, как сворачивание. Очевидно, что больше опыта с ними облегчит это, но я нахожу foldRightболее читабельным, чем /:.

Мэтт Н
источник
4
Может быть, частота использования входит в это также. Я не думаю, что вы foldдостаточно часто, чтобы оператор экономил много времени. Это также может быть причиной того, что LISPy (+ 1 2 3) кажется странным, но (добавьте 1 2 3) меньше. Мы склонны считать операторы строго бинарными. >>.Операторы стиля Parsec могут быть глупыми, но, по крайней мере, первое, что вы делаете в Parsec, это комбинирование синтаксических анализаторов, так что операторы для него получают много пользы.
CodexArcanum
1

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

Конечно, строку C = A * x + b * cлегче писать и читать, чем C = A.multiplyVector(x).addVector(b.multiplyScalar(c)).

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

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

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

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

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

doppelfish
источник
0

Полагаю, самый крайний пример - APL, следующая программа - «игра в жизнь»:

life←{↑1 ⍵∨.∧3 4=+/,¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵}

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

Проблема в том, что у вас есть набор символов, которые почти все интуитивно понимают: +,-,*,/,%,=,==,&

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

Джеймс Андерсон
источник