Чому не визначені користувачем оператори частіше?


94

Однією з особливостей, які мені не вистачає у функціональних мовах, є думка про те, що оператори - це лише функції, тому додавання спеціального оператора часто так само просто, як додавання функції. Багато процедурних мов дозволяють перевантажувати оператори, тому в деякому сенсі оператори все ще функціонують (це дуже вірно в D, коли оператор передається як рядок у параметрі шаблону).

Здається, що там, де дозволена перевантаження оператора, часто доцільно додавати додаткові спеціальні оператори. Я знайшов цю публікацію в блозі , яка стверджує, що користувацькі оператори не дуже добре працюють із позначенням інфіксації через правила пріоритетності, але автор дає декілька рішень цієї проблеми.

Я озирнувся і не зміг знайти жодної процедурної мови, яка б підтримувала власні оператори мовою. Є хаки (наприклад, макроси в C ++), але це навряд чи те саме, що мовна підтримка.

Оскільки ця функція є досить тривіальною у застосуванні, чому вона не частіша?

Я розумію, що це може призвести до деякого потворного коду, але це не зупиняло дизайнерів мови в минулому від додавання корисних функцій, які можна легко зловживати (макроси, потрійний оператор, небезпечні вказівники).

Фактичні випадки використання:

  • Впровадити відсутні оператори (наприклад, у Lua немає побітових операторів)
  • Міміка D ~(об'єднання масивів)
  • DSL
  • Використовувати |як синтаксис цукрового синтаксису в стилі Unix (використовуючи супровідні програми / генератори)

Мене також цікавлять мови, які дозволяють користувальницьким операторам, але мене більше цікавить, чому це було виключено. Я думав над тим, щоб змусити мову сценаріїв додати визначені користувачем оператори, але зупинився, коли зрозумів, що цього не бачив ніде, тому, мабуть, є вагома причина, чому дизайнери мови розумніші за мене не дозволили.


4
З цього питання триває дискусія Reddit .
Динамічний

2
@dimatura: Я не знаю багато про R, але користувацькі оператори все ще не здаються дуже гнучкими (наприклад, правильний спосіб не визначає фіксованість, масштаб, перевантаження тощо), що пояснює, чому вони не використовуються дуже багато. Це відрізняється від інших мов, безумовно, в Haskell, який дуже багато використовує власні оператори інфіксації . Ще один приклад процедурної мови з розумно підтримкою на замовлення - Nimrod , і, звичайно, Perl це також дозволяє .
близько

4
Інший варіант, у Lisp насправді немає різниці між операторами та функціями.
BeardedO

4
Ще не згадуйте про Prolog, іншу мову, де оператори - це лише синтаксичний цукор для функцій (так, навіть математичних), який дозволяє визначати власні оператори зі спеціальним пріоритетом.
Девід Кауден

2
@BeardedO, операторів інфіксації в Lisp взагалі немає. Після того, як ви їх представите, вам доведеться розібратися з усіма питаннями, що мають пріоритет, тощо.
SK-логіка

Відповіді:


134

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

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


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

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

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

Як я пропустив цю стеві-рент? О людина, закладки
Джордж Мауер

До якої філософії я б потрапив, якщо думаю, що програмісти, швидше за все, пишуть найкращий код, якщо мови дають їм інструменти, щоб сказати, коли компілятор повинен робити різні умовиводи (наприклад, аргументи автоматичного боксу до Formatметоду) і коли він повинен відмовитись ( наприклад аргументи автоматичного боксу на ReferenceEquals). Чим більше мова здатностей дає програмістам сказати, коли певні умовиводи були б недоречними, тим безпечніше вона може запропонувати зручні умовиводи, коли це доречно.
supercat

83

З огляду на вибір між об'єднатими масивами з ~ або з "myArray.Concat (secondArray)", я б, мабуть, віддав перевагу останньому. Чому? Тому що ~ - це абсолютно безглуздий персонаж, який має лише своє значення - значення конкатенації масиву - вказаний у конкретному проекті, де він був написаний.

В основному, як ви сказали, оператори нічим не відрізняються від методів. Однак, хоча методи можуть бути зрозумілими, зрозумілими іменами, що доповнюють розуміння потоку коду, оператори непрозорі та ситуативні.

Ось чому мені також не подобається .оператор PHP (конкатенація рядків) або більшість операторів Haskell або OCaml, хоча в цьому випадку з'являються деякі загальновизнані стандарти для функціональних мов.


27
Те саме можна сказати про всіх операторів. Те, що робить їх хорошими, а також може зробити оператори, визначені користувачем, хорошими (якщо вони визначені розумно), це: Незважаючи на характер, який використовується для них просто символом, широке використання та, можливо, мнемічні властивості, мають своє значення у вихідному коді негайно очевидно для тих, хто це знайомий. Тож ваша відповідь на даний момент не є переконливою IMHO.

33
Як я вже згадував наприкінці, деякі оператори мають універсальну розпізнаваність (наприклад, стандартні арифметичні символи, побітні зсуви, І / АБО і т. Д.), І тому їх терміновість переважає їх непрозорість. Але здатність визначити довільних операторів, здається, залишає найгірше для обох світів.
Авнер Шахар-Каштан

11
Тож ви також виступаєте за заборону, наприклад, однолітерних імен на мовному рівні? Більш загально, не допускати нічого в мові, що, здається, приносить більше шкоди, ніж користі? Це правильний підхід до мовного дизайну, але не єдиний, тому я не думаю, що ви не можете цього припустити.

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

10
Для того, щоб визначити довгий час, це те, що кодування зазвичай не полягає в тому, щоб зробити комп'ютер, щоб зробити щось за вас, це створити документ, який можуть читати і люди, і комп'ютери, для людей це набагато важливіше, ніж комп'ютери. Ця відповідь цілком правильна, якщо наступній людині (або вам через 2 роки) доведеться витратити навіть 10 секунд, намагаючись зрозуміти, що означає ~, ви, можливо, також витратили 10 секунд, набравши замість цього виклик методу.
Білл К

71

Оскільки ця функція є досить тривіальною у застосуванні, чому вона не частіша?

Ваше приміщення неправильне. Це не "досить тривіально для реалізації". Насправді це приносить мішок проблем.

Давайте подивимося на запропоновані «рішення» у публікації:

  • Без пріоритету . Сам автор каже: "Не використовувати правила пріоритету - це просто не варіант".
  • Семантичний усвідомлений розбір . Як йдеться у статті, це вимагатиме від компілятора багато семантичних знань. Стаття насправді не пропонує рішення для цього, і дозвольте сказати, це просто не банально. Компілятори розроблені як компроміс між потужністю та складністю. Зокрема, автор згадує про крок попереднього розбору для збору відповідної інформації, але попередній розбір є неефективним, і компілятори дуже важко намагаються мінімізувати проходи аналізу.
  • Немає спеціальних операторів інфіксації . Ну, це не рішення.
  • Гібридний розчин . Це рішення містить у собі багато (але не всі) недоліки семантичного усвідомленого розбору. Зокрема, оскільки компілятор повинен трактувати невідомі маркери як потенційно представлені власні оператори, він часто не може створювати змістовні повідомлення про помилки. Він також може зажадати визначення зазначеного оператора для продовження розбору (для збору інформації про тип тощо), ще раз вимагаючи додаткового проходу розбору.

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


2
Дякую за голос розуму. Мій досвід говорить про те, що люди, які відкидають речі як тривіальні, є або експертами, або блаженно неосвіченими, і найчастіше в останній категорії: x
Матьє М.

14
кожна з цих проблем була вирішена мовами, які існують 20 років ...
Philip JF

11
І все ж це надзвичайно тривіально реалізувати. Забудьте про всі алгоритми розбору книг-драконів, це 21 століття, і саме час рухатися далі. Навіть розширення синтаксису мови є простим, а додавання операторів заданого пріоритету тривіальне. Погляньте, скажімо, на аналізатор Haskell Це набагато, набагато простіше, ніж парсери для "мейнстріму" роздутих мов.
SK-логіка

3
@ SK-логіка Ваше твердження про ковдру не переконує мене. Так, розбір перейшов. Ні, реалізація довільних пріоритетів оператора залежно від типу не є "тривіальною". Створення хороших повідомлень про помилки з обмеженим контекстом не є "тривіальним". Створювати ефективні парсери з мовами, для перетворення яких потрібно кілька пропусків, неможливо.
Конрад Рудольф

6
У Haskell синтаксичний аналіз "легкий" (порівняно з більшістю мов). Пріоритетність не залежить від контексту. Частина, яка надає важкі повідомлення про помилки, пов’язана з класом типу та розширеними системними функціями типу, а не з визначеними користувачем операторами. Це перевантаження, а не визначення користувача, що є важкою проблемою.
Philip JF

25

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

Оператори Infix мають більше проблем, ніж прості правила пріоритетності (хоч, щоб бути тупими, посилання, на яке ви посилаєтеся, тривілізує вплив цього дизайнерського рішення). Одне - це вирішення конфлікту: що відбувається, коли ви визначаєте a.operator+(b)і b.operator+(a)? Перевага однієї над іншою призводить до порушення очікуваної комутативної властивості цього оператора. Помилка помилки може призвести до того, що модулі, які в іншому випадку працювали, будуть порушені один раз разом. Що відбувається, коли ви починаєте кидати похідні типи в суміш?

Справа в тому, що оператори - це не просто функції. Функції або є окремими, або належать їх класу, що забезпечує чітке уподобання, якому параметру (якщо такий є) належить поліморфна відправка.

І це ігнорує різні проблеми з упаковкою та вирішенням, які виникають у операторів. Причина дизайнерів мов (за великим рахунком) визначення оператора обмеження infix полягає в тому, що це створює купу проблем для мови, забезпечуючи при цьому дискусійну вигоду.

І відверто кажучи, тому що вони не банальні для реалізації.


1
Я вважаю, це причина, що Java не включає їх, але мови, які їх мають, мають чіткі правила переваги. Я думаю, це схоже на матричне множення. Це не комутативно, тому ви повинні бути в курсі, коли використовуєте матриці. Я думаю, що те саме стосується занять, але так, я погоджуюся, що мати некомутативне +- це зло. Але це справді аргумент проти визначених користувачем операторів? Це здається аргументом проти перевантаження оператора взагалі.
beatgammit

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

22
Я називаю фігня, сер. Існує життя поза ОО-гетто, і там функції не обов'язково належать до жодного об'єкта, тому пошук потрібної функції точно такий же, як пошук потрібного оператора.
Матьє М.

1
@matthieuM. - Звичайно. Правила власності та відправлення є більш єдиними в тих мовах, які не підтримують функції членів. На даний момент, оригінальним питанням стає "чому мови, що не належать до ОО", не є більш поширеними? " що є цілком іншим балградом.
Теластин

11
Ви подивилися, як Haskell робить власні оператори? Вони працюють точно як звичайні функції, за винятком того, що вони також мають асоційований пріоритет. (Насправді так можуть нормальні функції, тому навіть там вони насправді не відрізняються.) В основному, оператори інфікуються за замовчуванням, а імена - префіксом, але це єдина різниця.
Тихон Єлвіс

19

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

Навіщо використовувати ~ для об'єднання в масив? Чому б не використовувати <<, як це робить Ruby ? Тому що програмісти, з якими ви працюєте, напевно, не є програмістами Ruby. Або D програмістів. То що вони роблять, коли стикаються з вашим кодом? Вони повинні піти і подивитися, що означає символ.

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

Досить справедливо, ви думаєте? Це була лише невелика команда. Особисто я не згоден. Кожному новому розробнику судилося заплутатися в цій термінології. У нас не вистачає проблем з вивченням нового домену?

З іншого боку, я з задоволенням використовувати в ??оператор в C # , тому що я очікую інших розробників C # знати , що це таке, але я б не перевантажувати його мовою , який не підтримує його за замовчуванням.


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

@tjameson: Ти маєш рацію, насправді це було дещо дотично до того, що я намагався зробити, і не особливо добре написане. Я думав про ?? Наприклад, як я це писав, і віддаю перевагу цьому прикладу. Видалення подвійного. Параграф NaN.
pdr

6
Я думаю, що ваш "кожен новий розробник судився заплутатися в цій термінології", це дещо невдало, турбуватися про криву навчання нових розробників до вашої кодової бази - це те саме, що турбуватися про криву навчання нових розробників для нова версія .NET. Я думаю, що важливіше, коли розробники дізнаються це (новий .NET або ваш код), чи є сенс і показувати цінність? Якщо так, крива навчання варта того, тому що кожному розробникові потрібно вивчити його лише один раз, хоча код потрібно керувати незліченною кількістю разів, тож якщо він покращує код, це незначна вартість.
Джиммі Хоффа

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

3
Також аргумент "Кожному новому розробнику судилося заплутатися у цій термінології. Чи не вистачає нам проблем з вивченням нового домену?" можна було застосувати до OOP ще в 80-х, структуроване програмування до цього, або IoC 10 років тому. Настав час припинити використовувати цей помилковий аргумент.
Маурісіо Шеффер

11

Я можу придумати кілька причин:

  • Вони не банальні для впровадження - дозволяючи довільним спеціальним операторам може зробити ваш компілятор набагато складнішим, особливо якщо ви дозволені визначеним користувачем правилам пріоритетності, фіксованості та арності. Якщо простота - це доброчесність, то перевантаження оператора відводить вас від гарного дизайну мови.
  • Вони зловживають - здебільшого кодерами, які вважають, що це "круто", щоб переосмислити операторів і почати переосмислювати їх для всіляких спеціальних класів. Невдовзі ваш код завалений набором персоналізованих символів, які ніхто більше не може прочитати чи зрозуміти, оскільки оператори не дотримуються звичайних добре зрозумілих правил. Я не купую аргумент "DSL", якщо ваш DSL не є підмножиною математики :-)
  • Вони завдають шкоди читабельності та ремонтопридатності - якщо операторів регулярно переохочувати, це може бути важко помітити, коли цей об'єкт використовується, і кодери змушені постійно запитувати себе, що робить оператор. Набагато краще давати змістовні назви функцій. Введення кількох додаткових символів коштує дешево, довгострокові проблеми з технічним обслуговуванням дорогі.
  • Вони можуть порушити неявні очікування ефективності . Наприклад, я звичайно очікував би пошук елемента в масиві O(1). Але з перевантаженням оператора, це someobject[i]може бути O(n)операцією залежно від реалізації оператора індексації.

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

Деякі цікаві випадки, які слід врахувати:

  • Липси : взагалі взагалі не розрізняють операторів і функцій - +це лише звичайна функція. Ви можете визначати функції, які вам подобаються (зазвичай існує спосіб їх визначення в окремих просторах імен, щоб уникнути конфлікту із вбудованими +), включаючи операторів. Але існує культурна тенденція до використання значущих імен функцій, тому цим не зловживають сильно. Крім того, в нотації префікса Lisp має тенденцію до виключного використання, тому в "синтаксичному цукрі", яке надає перевантаження оператора, є менше значення.
  • Java - відключає перевантаження оператора. Іноді це дратує (для таких речей, як випадок "Складний номер"), але в середньому це, мабуть, правильне дизайнерське рішення для Java, яке призначене як простий, загальноприйнятий мовний протокол OOP. Код Java насправді досить легкий для розробників з низьким / середнім рівнем кваліфікації внаслідок такої простоти.
  • C ++ має дуже складну операційну перевантаження. Іноді цим зловживають ( cout << "Hello World!"хтось?), Але підхід має сенс з огляду на позиціонування C ++ як складної мови, яка дозволяє програмування високого рівня, але все одно дозволяє вам наблизитися до металу за продуктивністю, так що ви можете, наприклад, написати складний клас номерів, який веде себе точно так, як ви хочете, не погіршуючи продуктивність. Зрозуміло, що це ваша власна відповідальність, якщо ви стріляєте собі в ногу.

8

Оскільки ця функція є досить тривіальною у застосуванні, чому вона не частіша?

Це не тривіально реалізовувати (якщо тільки тривіально реалізовано). Це також не надто отримує вас, навіть якщо реалізовано в ідеалі: приріст читабельності від лаконічності компенсується втратами для читабельності від незнайомості та непрозорості. Коротше кажучи, це нечасто, оскільки зазвичай не варто часу розробників чи користувачів.

Однак, я можу придумати три мови, які це роблять, і вони роблять це по-різному:

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

4
Ви сказали, що це "не тривіально", і одразу перерахували три мови, що демонструють кілька шалено тривіальних реалізацій. Зрештою, це зовсім тривіально, чи не так?
SK-логіка

7

Однією з головних причин користувальницькі оператори не відштовхуються від того, що тоді будь-який оператор може означати / може щось робити.

Наприклад cstream, сильно критикується перевантаження лівої зміни.

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

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


6
Я не бачу, як частини щодо перевантаження оператора поширюються на визначення абсолютно нових операторів.

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

3
І чим це відрізняється від перевантаження методу?
Йорг W Міттаг

@ JörgWMittag з перевантаженим методом додається (більшість часу) змістовне ім'я. із символом важче коротко пояснити, що станеться
храповик виродка

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

4

Ми не використовуємо визначені користувачем оператори з тієї ж причини, що не використовуємо визначені користувачем слова. Ніхто не назвав би їхню функцію «свирпом». Єдиний спосіб донести свою думку до іншої людини - це використовувати загальну мову. А це означає, що і слова, і знаки (оператори) повинні бути відомі суспільству, для якого ви пишете свій код.

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


1
Додайте до цього можливість виявити значення функції. У разі "sworp" ви можете досить легко вставити його в поле пошуку і знайти його документацію. Вставте оператора в пошукову систему - це зовсім інший парк куль. Наші нинішні пошукові системи просто затягують пошук операторів, і я витрачав багато часу, намагаючись знайти значення в деяких випадках.
Марк Н

1
Скільки "школи" ви рахуєте? Наприклад, я думаю, що ∈ for elem- це відмінна ідея, і, безумовно, оператор повинен розуміти всіх, але інші, здається, не згодні.
Тихон Єлвіс

1
Це не проблема із символом. Це проблема з клавіатурою. Я, наприклад, використовую hasmall-режим emacs з увімкненим красувачем Unicode. І автоматично перетворює оператори ascii в символи unicode. Але я не зміг би його набрати, якби не було еквівалента ascii.
Вагіф Верді

2
Природні мови побудовані лише з "визначених користувачем слів" і нічого іншого. І деякі мови насправді заохочують користувальницькі слова.
SK-логіка

4

Що стосується мов, які підтримують таке перевантаження: Scala робить насправді набагато чистішим та кращим способом може C ++. Більшість символів можна використовувати в іменах функцій, тож ви можете визначити операторів, таких як! + * = ++, якщо вам подобається. Існує вбудована підтримка infix (для всіх функцій, що беруть один аргумент). Я думаю, ви також можете визначити асоціативність таких функцій. Однак ви не можете визначити пріоритет (лише з потворними хитрощами, дивіться тут ).


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 ++ є зручним ярликом для команди з одним розробником, але це викликає всілякі плутанини в довгостроковій перспективі просто тому, що виклики методів "приховуються" так, що це не просто інструменти на зразок доксигену розбираються, і люди повинні зрозуміти ідіоми, щоб правильно їх використовувати.

Іноді це набагато важче зрозуміти, ніж ви очікували, навіть. Колись у великому кросплатформенному проекті C ++ я вирішив, що було б гарною ідеєю нормалізувати спосіб побудови шляхів шляхом створення FilePathоб'єкта (подібного до Fileоб’єкта Java ), який би мав оператора / використовувався для об'єднання іншого частина шляху до нього (щоб ви могли зробити щось на кшталт, File::getHomeDir()/"foo"/"bar"і це зробить правильно на всіх наших підтримуваних платформах). Кожен, хто бачив це, по суті сказав би: "Що за чорт? Струнний поділ? ... О, це мило, але я не довіряю йому робити все правильно".

Аналогічно, у графічному програмуванні чи інших областях дуже багато випадків, коли математика векторів / матриць трапляється дуже багато, коли спокусливо робити такі речі, як Матриця * Матриця, Вектор * Вектор (крапка), Вектор% вектор (хрест), Матриця * Вектор ( матричне перетворення), матриця ^ вектор (матричне перетворення у спеціальному випадку, ігноруючи однорідну координату - корисно для поверхневих нормалей) тощо, але, хоча це економить час на розбір для людини, яка написала векторну математичну бібліотеку, вона лише закінчується вгору плутаючи проблему для інших. Це просто не варто.


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.