Чому існує так мало мов із оператором змінного типу?


47

Я маю на увазі це таким чином:

<?php
    $number1 = 5;   // (Type 'Int')
    $operator1 = +; // (Type non-existent 'Operator')
    $number2 = 5;   // (Type 'Int')
    $operator2 = *; // (Type non-existent 'Operator')
    $number3 = 8;   // (Type 'Int')

    $test = $number1 $operator1 $number2 $operator2 $number3; //5 + 5 * 8.

    var_dump($test);
?>

Але також таким чином:

<?php
    $number1 = 5;
    $number3 = 9;
    $operator1 = <;

    if ($number1 $operator1 $number3) { //5 < 9 (true)
        echo 'true';
    }
?>

Не здається, що в будь-яких мовах цього є - чи є вагома причина, чому їх немає?


28
Взагалі те, що ви хочете зробити, буде охоплено всіма мовами, які підтримують певну форму метапрограмування з якимись лямбдами, закриттями або анонімними функціями, що було б загальним способом реалізації таких функцій. З мовами, де методи є громадянами першого класу, ви могли б використовувати їх більш-менш ідентичні змінним. Хоча це не саме той простий синтаксис, який ви використовуєте тут, оскільки в більшості таких мов повинно бути зрозуміло, що ви дійсно хочете викликати метод, збережений у змінній.
thorsten müller

7
@MartinMaat Функціональні мови роблять це багато.
Thorbjørn Ravn Andersen

7
У haskell оператори - це функції, як і будь-яка інша функція. тип (+)- Num a => a -> a -> aIIRC. Ви також можете визначити функції, щоб їх можна було записати у фіксованому вигляді ( a + bзамість (+) a b)
sara

5
@enderland: Ваша редакція повністю змінила мету питання. Це пішло від запитання, чи існують якісь мови, до запитання, чому існує так мало. Я думаю, що ваша редакція призведе до безлічі розгублених читачів.
Брайан Оуклі

5
@enderland: хоча це правда, повна зміна теми служить лише для плутанини. Якщо це поза темою, громада проголосує за її закриття. Жодна з відповідей (у той час, коли я це пишу) не має сенсу для запитання як написаного.
Брайан Оуклі

Відповіді:


104

Оператори - це лише функції під кумедними іменами, з якимись спеціальними синтаксисами.

У багатьох мовах, таких як C ++ і Python, ви можете переосмислити операторів, переосмисливши спеціальні методи вашого класу. Потім стандартні оператори (наприклад +) працюють відповідно до логіки, яку ви надаєте (наприклад, об'єднання рядків або додавання матриць чи будь-якої іншої).

Оскільки такі функції, що визначають оператор, є лише методами, ви можете передавати їх навколо, як і функцію:

# python
action = int.__add__
result = action(3, 5)
assert result == 8

Інші мови дозволяють безпосередньо визначати нових операторів як функції та використовувати їх у формі інфіксу.

-- haskell
plus a b = a + b  -- a normal function
3 `plus` 5 == 8 -- True

(+++) a b = a + b  -- a funny name made of non-letters
3 +++ 5 == 8 -- True

let action = (+)
1 `action` 3 == 4 -- True

На жаль, я не впевнений, що PHP підтримує щось подібне, і якщо підтримка це буде хорошою справою. Використовуйте звичайну функцію, це читабельніше, ніж $foo $operator $bar.


2
@tac: Так, це приємно і навіть може бути сортовано на інші мови :) Що стосується Haskell, то мені найбільше не вистачає, це цей відділ - це $оператор, який забороняє дужки (особливо кілька вкладених) - але це може працювати лише з -варіадичні функції, виключаючи, наприклад, Python та Java. Склад одинарної функції OTOH може бути добре виконаний .
9000

6
Я ніколи не був прихильником перевантаження операторів, тому що так, оператор - це лише функція зі спеціальним синтаксисом, але є мається на увазі контракт, який зазвичай йде з операторами, які не мають функції. Наприклад, "+" має певні очікування - пріоритет оператора, комутативність тощо - і суперечити цим очікуванням - це вірний шлях до плутанини людей та генерування помилок. Однією з причин того, що, хоча я люблю JavaScript, я вважаю за краще, щоб вони розрізняли + для додавання та конкатенації. Перл мав це саме там.
crazy4jesus

4
Зверніть увагу, що Python має стандартний operatorмодуль, який дозволить вам писати action = operator.add, і він може працювати для будь-якого типу, який визначає +(не тільки int).
dan04

3
У Haskell +працює над типним класом Num, щоб ви могли реалізувати +будь-який новий тип даних, який ви створюєте, але так само, як і будь-що інше, наприклад, fmap для функтора. Це таке природне пристосування для Haskell, що дозволяє оператору перевантажуватись, їм доведеться наполегливо працювати, щоб цього не допустити!
Мартін Каподічі

17
Не впевнений, чому люди перешкоджають перевантаженню. Оригінальне питання не стосується перевантаження. Мені здається, це стосується операторів як першокласних значень. Для того, щоб написати, $operator1 = +а потім використовувати його вираз, вам зовсім не потрібно використовувати перевантаження оператора !
Андрес Ф.

16

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

З Вікіпедії:

Метапрограмування - це написання комп’ютерних програм з можливістю трактувати програми як їх дані.

Пізніше в тексті:

Лісп - це, мабуть, квінтесенціальна мова із засобами метапрограмування, як через його історичний пріоритет, так і через простоту та силу його метапрограмування.

Мови Lisp

Зрозумілий короткий вступ до Lisp випливає.

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

Якщо ми представимо список , що містить елементи а, Ь, с, d , як це: (ABCD) , ми отримуємо те , що виглядає як виклик функції Лиспа, де aє функція, і b, c, dє аргументи. Насправді типовий "Hello Hello!" програма може бути написана так:(println "Hello World!")

Звичайно b, cчи dможуть бути списки, які також оцінюють щось. Наступне: (println "I can add :" (+ 1 3) )буде надрукувати "" Я можу додати: 4 ".

Отже, програма - це серія вкладених списків, а перший елемент - функція. Хороша новина - ми можемо маніпулювати списками! Тож ми можемо маніпулювати мовами програмування.

Перевага Lisp

Lisps - це не стільки мови програмування, скільки інструментарій для створення мов програмування. Програмована мова програмування.

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

Наприклад, мовою, схожою на C, скажімо, що ви хочете написати ifоператора самостійно, щось на зразок:

my-if(condition, if-true, if-false)

my-if(false, print("I should not be printed"), print("I should be printed"))

У цьому випадку обидва аргументи будуть оцінені та надруковані у порядку, залежному від порядку оцінки аргументів.

У Lisps написання оператора (ми називаємо це макросом) та написання функції - це приблизно одне й те саме і використовується однаково. Основна різниця полягає в тому, що параметри макросу не оцінюються перед тим, як передаватися як аргументи макросу. Це важливо, щоб мати можливість записувати деякі оператори, як ifвище.

Мови реального світу

Покажіть, як саме тут трохи поза сферою, але я радимо спробувати програмування в одному Lisp, щоб дізнатися більше. Наприклад, ви можете подивитися:

  • Схема , старий, досить «чистий» Лісп з невеликим стрижнем
  • Загальний Lisp, більший Lisp з добре інтегрованою об'єктною системою та багатьма реалізаціями (це стандартизована ANSI)
  • Ракетка набрала Lisp
  • Clojure мій улюблений, наведеними вище прикладами був код Clojure. Сучасний Lisp, що працює на JVM. Існує кілька прикладів макросів Clojure і на SO (але це не правильне місце для початку. Я спершу поглянув би на 4clojure , braveclojure або clojure koans )).

О, і до речі, Lisp означає LISt Processing.

Щодо ваших прикладів

Я надам приклади, використовуючи Clojure нижче:

Якщо ви можете написати addфункцію в Clojure (defn add [a b] ...your-implementation-here... ), ви можете назвати її +так (defn + [a b] ...your-implementation-here... ). Це насправді те, що робиться в реальній реалізації (частина функції трохи більше задіяна, але визначення по суті таке, як я писав вище).

Що з інфікцією позначень? Ну Clojure використовує prefix(або польську) позначення, тому ми могли б зробити infix-to-prefixмакрос, який перетворив би префіксний код у код Clojure. Що насправді напрочуд просто (це насправді одна з макро-вправ у кожурних коанах)! Його також можна побачити в дикій природі, наприклад, див. Макрос Incanter$= .

Ось найпростіша версія з пояснень:

(defmacro infix [form]
  (list (second form) (first form) (nth form 2)))

;; takes a form (ie. some code) as parameter
;; and returns a list (ie. some other code)
;; where the first element is the second element from the original form
;; and the second element is the first element from the original form
;; and the third element is the third element from the original form (indexes start at 0)
;; example :
;; (infix (9 + 1))
;; will become (+ 9 1) which is valid Clojure code and will be executed to give 10 as a result

Щоб ще більше підбити крапку, деякі цитати Lisp :

«Частина того, що робить Лісп відмітним, - це те, що він створений для розвитку. Ви можете використовувати Lisp для визначення нових операторів Lisp. Оскільки нові абстракції стають популярними (наприклад, об’єктно-орієнтоване програмування), завжди виявляється легко реалізувати їх у Lisp. Як і ДНК, така мова не виходить зі стилю ».

- Пол Грехем, ANSI Common Lisp

«Програмування в Ліспі - це як гра з первісними силами Всесвіту. Відчувається блискавка між кінчиками пальців. Жодна інша мова навіть не відчуває себе близько.

- Гленн Ерліх, Дорога до Ліспа


1
Зауважте, що метапрограмування, хоча й цікаве, не потрібне для підтримки того, про що просить про те, що йдеться в ОП. Будь-яка мова з підтримкою функцій першого класу достатня.
Андрес Ф.

1
Приклад до питання ОП: (нехай ((opp # '+)) (надрукувати (застосувати opp' (1 2))))
Каспер ван ден Берг

1
Немає згадки про Common Lisp?
coredump

3
Я пам’ятаю, на панельній дискусії про мови, Кен говорив про перевагу в APL і завершив «Я навряд чи взагалі використовую дужки!» І хтось із глядачів закричав: "Це тому, що Дік їх усіх використав!"
JDługosz

2
На мові стилю C ++ ви можете повторно доповнити if, але вам потрібно обгорнути thenі elseаргументи лямбдами. У PHP та JavaScript є function(), у C ++ є лямбди, і існує розширення Apple на C з лямбдами.
Damian Yerrick

9

$test = $number1 $operator1 $number2 $operator2 $number3;

Більшість мовних реалізацій мають крок, коли аналізатор аналізує ваш код і будує з нього дерево. Так, наприклад, вираз 5 + 5 * 8буде розбиратися як

  +
 / \
5   *
   / \
  8   8

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

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

Багато мов сценаріїв дозволяють оцінювати довільні вирази (або принаймні довільні арифметичні вирази, як у випадку expr) під час виконання. Там ви можете просто об'єднати свої цифри та оператори в один вираз і дозволити мові оцінити це. У PHP (та багатьох інших) ця функція називається eval.

$test = eval("$number1 $operator1 $number2 $operator2 $number3");

Існують також мови, які дозволяють створювати код під час компіляції. Вираз Mixin в D приходить на розум, коли я вважаю , ви могли б написати що - щось на зразок

test = mixin("number1 " + operator1 + " number2 " + operator2 + "number3");

Тут operator1і operator2повинні бути рядкові константи, які відомі під час компіляції, наприклад параметри шаблону. number1, number2і number3залишалися звичайними змінними часу виконання.

Інші відповіді вже обговорювали різні способи того, як оператор і функція є більш-менш одне і те ж, залежно від мови. Але, як правило, існує синтаксична різниця між вбудованим символом оператора інфіксації на зразок +і іменем, подібним до виклику operator1. Деталі я залишу на ті інші відповіді.


+1 Ви повинні почати з "це можливо в PHP з eval()мовної конструкції " ... Технічно це забезпечує саме бажаний результат, поставлений у питанні.
Armfoot

@Armfoot: Важко сказати, де лежить фокус питання. Заголовок підкреслює аспект "змінна типу оператора", і evalне відповідає цьому аспекту, оскільки для evalоператорів це лише рядки. Тому я почав із пояснення того, чому змінні типу оператора можуть викликати проблеми, перш ніж розпочати обговорення альтернатив.
MvG

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

2

Algol 68 мав саме таку особливість. Ваш приклад в Algol 68 виглядатиме так:

int число1 = 5;                              ¢ (тип 'Int') ¢
оп оператор1 = INT ( INT , б ) + б ; ¢ (Тип неіснуючого "Оператора") ¢ пріоритетний оператор1 = 4; int число2 = 5;                              ¢ (тип 'Int') ¢ оп оператор2 = INT ( ІНТ , б ) * б ;  ¢


(Введіть неіснуючий "Оператор") ¢
prio operator2 = 5;
int число3 = 8;                              ¢ (тип 'Int') ¢

int test = число1 оператор1 число2 оператор2 число3 ; ¢ 5 + 5 * 8. ¢

var_dump ( тест );

Ваш другий приклад виглядатиме так:

int число4 = 9;
op operator3 = bool ( int a , b ) a < b ;
Пріо operator3 = 3;
якщо число1 $ operator3 число4, то ¢ 5 <9 (справжнє) ¢
друкувати ( справжнє )
fi

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

Насправді, хоча мова написана за допомогою шрифтування, машини дня не могли обробляти шрифти (паперова стрічка та перфокарти), і використовувались штриховки . Програма, ймовірно, буде введена як:

'INT' NUMBER4 = 9;
'OP' 'OPERATOR3' = 'BOOL' ('INT' A,B) A < B;
'PRIO' 'OPERATOR3' = 3;
'IF' NUMBER1 'OPERATOR3' NUMBER4 'THEN' 'C' 5 < 9 'C'
PRINT('TRUE')
'FI'

Ви також можете пограти в цікаві ігри з мовою, коли зможете визначити власні символи для операторів, якими я користувався раз, багато років тому ... [2].


Список літератури:

[1] Неофіційне вступ до Алголу 68 Ч.Лінді та С.Г. ван дер Меулен, Північна Голландія, 1971 р .

[2] Алгол 68 Фрази, Інструмент для сприяння написанню укладача в Алголі 68 , до н.е. Томпсетт, Міжнародна конференція із застосування Алгола 68, Університет Східної Англії, Норвіч, Великобританія, 1976 .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.