Почему существует так мало языков с оператором переменного типа?

46

Я имею в виду это так:

<?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';
    }
?>

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

kgongonowdoe
источник
28
В общем, то, что вы хотите сделать, будет охватываться всеми языками, которые поддерживают некоторую форму метапрограммирования с какими-то лямбдами, замыканиями или анонимными функциями, которые были бы обычным способом реализации таких функций. С языками, где методы являются гражданами первого класса, вы сможете использовать их более или менее идентично переменным. Хотя это не совсем тот простой синтаксис, который вы здесь используете, поскольку в большинстве таких языков должно быть ясно, что вы действительно хотите вызвать метод, хранящийся в переменной.
Торстен Мюллер
7
@MartinMaat Функциональные языки делают это много.
Турбьерн Равн Андерсен
7
в haskell операторы - это функции, подобные любой другой функции. тип (+)является Num a => a -> a -> aIIRC. Вы также можете определить функции, чтобы они могли быть записаны с инфиксом ( a + bвместо (+) a b)
Сара
5
@enderland: Ваше изменение полностью изменило цель вопроса. От вопроса о том, существуют ли какие-либо языки, до вопроса, почему существует так мало. Я думаю, что ваше редактирование приведет к большому количеству запутанных читателей
Брайан Оукли
5
@enderland: хотя это правда, полная смена темы только приводит в замешательство. Если это не по теме, сообщество проголосует за его закрытие. Ни один из ответов (в то время, когда я пишу это) не имеет смысла для вопроса, как он написан.
Брайан Оукли

Ответы:

103

Операторы - это просто функции под смешными именами с определенным синтаксисом.

Во многих языках, таких как 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.

9000
источник
2
@tac: Да, это хорошо и может быть даже перенесено на другие языки :) Что касается Haskell, то мне больше всего не хватает этого отдела, который является $оператором, который устраняет скобки (особенно множественные вложенные) - но это может работать только с -вариантные функции, исключая, например, Python и Java. OTOH может быть сделано с помощью унарной функции .
9000
6
Я никогда не был фанатом перегрузки операторов, потому что да, оператор - это просто функция со специальным синтаксисом, но есть подразумеваемый контракт, который обычно идет с операторами, которые не идут с функциями. «+», например, имеет определенные ожидания - приоритет оператора, коммутативность и т. д. - и идти против этих ожиданий - верный путь для запутывания людей и создания ошибок. Одна из причин, по которой я люблю javascript, я бы предпочел, чтобы они различали + для сложения и объединения. У Perl это было прямо здесь.
fool4jesus
4
Обратите внимание, что в Python есть стандартный operatorмодуль, который позволит вам писать action = operator.addи работать с любым типом, который определяет +(не только int).
Ден04
3
В Haskell +работает с классом типов Num, поэтому вы можете реализовать его +для любого нового типа данных, который вы создаете, но так же, как вы делаете что-либо еще, например, fmap для функтора. Для Haskell вполне естественно допустить перегрузку операторов, им придется много работать, чтобы не допустить этого!
Мартин Каподичи
17
Не уверен, почему люди зацикливаются на перегрузке. Оригинальный вопрос не о перегрузке. Мне кажется, что операторы - это первоклассные значения. Чтобы написать, $operator1 = +а затем использовать выражение, вам вообще не нужно использовать перегрузку операторов !
Андрес Ф.
16

Есть много языков, которые допускают какое-то метапрограммирование . В частности, я удивлен, что нет ответа, говорящего о семействе языков Lisp .

Из википедии:

Метапрограммирование - это написание компьютерных программ с возможностью обрабатывать программы как их данные.

Далее в тексте:

Вероятно, Лисп является наиболее существенным языком с возможностями метапрограммирования, как из-за его исторического приоритета, так и из-за простоты и мощи его метапрограммирования.

Лисп языки

Краткое введение в Lisp следует.

Один из способов просмотра кода - это набор инструкций: сделайте это, затем сделайте это, затем сделайте это другое ... Это список! Список вещей для программы, чтобы сделать. И, конечно, вы можете иметь списки внутри списков для представления циклов и так далее.

Если мы представим список , содержащий элементы а, Ь, с, d , как это: (ABCD) , мы получаем то , что выглядит как вызов функции Лиспа, где aесть функция, и b, c, dявляются аргументы. Если факт типичный "Hello World!" Программа может быть написана так:(println "Hello World!")

Конечно b, cили dмогут быть списки, которые также оценивают что-то. Следующее: (println "I can add :" (+ 1 3) )напечатало бы «Я могу добавить: 4».

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

Преимущество Лисп

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

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

Например, на C-подобном языке, скажем, вы хотите написать ifоператор самостоятельно, что-то вроде:

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

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

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

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

Языки реального мира

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

  • Схема , старый, довольно «чистый» Лисп с небольшим ядром
  • Common Lisp, больший Lisp с хорошо интегрированной объектной системой и множеством реализаций (он стандартизирован ANSI)
  • Ракетка печатного Лисп
  • Clojure мой любимый, примеры выше были код Clojure. Современный Лисп, работающий на 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. Что на самом деле удивительно легко (на самом деле это одно из макро упражнений в clojure koans)! Это также можно увидеть в дикой природе, например, см. Макрос 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. По мере того, как новые абстракции становятся популярными (например, объектно-ориентированное программирование), их легко реализовать в Лиспе. Как и ДНК, такой язык не выходит из моды ».

- Пол Грэм, ANSI Common Lisp

«Программирование на Лиспе похоже на игру с изначальными силами вселенной. Это похоже на молнию между кончиками пальцев. Ни один другой язык даже не чувствует себя близко ».

- Гленн Эрлих, Дорога в Лисп

Nha
источник
1
Обратите внимание, что метапрограммирование, хотя и интересно, не обязательно для поддержки того, о чем спрашивает OP. Любой язык с поддержкой первоклассных функций достаточно.
Андрес Ф.
1
Пример к вопросу OP: (let ((opp # '+)) (print (apply opp' (1 2))))
Каспер ван ден Берг
1
Нет упоминания об Common Lisp?
coredump
3
Я помню, что на панельной дискуссии о языках Кен говорил о приоритете в APL и в заключение сказал: «Я вообще никогда не использую скобки!» И кто-то из аудитории кричал: «Это потому, что Дик использовал их всех!»
JDługosz
2
В C ++ - стиль языка, вы можете переопределить if, но вы должны обернуть thenи elseаргументы с лямбды. PHP и JavaScript имеют function(), C ++ имеет лямбды, и существует расширение Apple для C с лямбдами.
Дамиан Йеррик
9

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

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

  +
 / \
5   *
   / \
  8   8

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

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

Многие языки сценариев позволяют оценивать произвольные выражения (или, по крайней мере, произвольные арифметические выражения, как в случае expr) во время выполнения. Там вы можете просто объединить свои числа и операторы в одно выражение и позволить языку оценить это. В PHP (и многих других) эта функция вызывается eval.

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

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

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

Здесь operator1и operator2должны быть строковые константы, которые известны во время компиляции, например параметры шаблона. number1, number2И number3были оставлены в качестве обычных переменных во время выполнения.

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

MVG
источник
+1 Вы должны начать с «это возможно в PHP с eval()языковой конструкцией » ... Технически это дает именно тот результат, который задан в вопросе.
Armfoot
@ Армфут: Сложно сказать, где находится фокус вопроса. Заголовок подчеркивает аспект «оператор переменной типа» и evalне отвечает этому аспекту, поскольку для evalоператоров это просто строки. Поэтому я начал с объяснения причин, по которым переменные операторного типа могут вызывать проблемы, прежде чем приступить к обсуждению альтернатив.
MvG
Я понимаю вашу точку зрения, но учтите, что, вставляя все в строку, вы в основном подразумеваете, что типы переменных больше не актуальны (поскольку PHP использовался в качестве примера, это, кажется, является предметом вопроса), и в конце концов, вы можно разместить их так же, как если бы некоторые из этих переменных имели «оператор типа» при получении того же результата ... Поэтому я считаю, что ваше предложение дает наиболее точный ответ на вопрос.
Армфут
2

У Алгола 68 была именно эта особенность. Ваш пример в Алголе 68 будет выглядеть так:

int number1 = 5;                              ¢ (тип 'Int') ¢
оп оператор1 = INT ( INT , б ) + б ; ¢ (Тип несуществующего «Оператор») ¢ prio operator1 = 4; int number2 = 5;                              ¢ (Тип 'Int') ¢ op operator2 = int ( int a , b ) a * b ;  ¢


(Тип несуществующего «Оператор») ¢
prio operator2 = 5;
int number3 = 8;                              ¢ (тип 'Int') ¢

ИНТ тест = number1 оператор1 number2 оператор2 number3 ; ¢ 5 + 5 * 8. ¢

var_dump ( тест );

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

int number4 = 9;
оп operator3 = BOOL ( INT , б ) < б ; prio operator3 = 3; если number1 $ operator3 number4 затем ¢ 5 <9 (верно) ¢ печать ( правда ) ц



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

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

'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 , BC Tompsett, Международная конференция по применению Алгола 68, в Университете Восточной Англии, Норвич, Великобритания, 1976 .

Брайан Томпсетт - 汤 莱恩
источник