Большинство языков программирования (как динамически, так и статически типизированных) имеют специальные ключевые слова и / или синтаксис, которые выглядят значительно иначе, чем объявления переменных для объявления функций. Я вижу функции как объявление другой именованной сущности:
Например в Python:
x = 2
y = addOne(x)
def addOne(number):
return number + 1
Почему бы нет:
x = 2
y = addOne(x)
addOne = (number) =>
return number + 1
Точно так же на языке как Java:
int x = 2;
int y = addOne(x);
int addOne(int x) {
return x + 1;
}
Почему бы нет:
int x = 2;
int y = addOne(x);
(int => int) addOne = (x) => {
return x + 1;
}
Этот синтаксис кажется более естественным способом объявления чего-либо (будь то функция или переменная) и одного ключевого слова меньше, как def
или function
в некоторых языках. И, IMO, он более последовательный (я смотрю там же, чтобы понять тип переменной или функции) и, вероятно, делает синтаксический анализатор / грамматику немного проще для написания.
Я знаю, что очень немногие языки используют эту идею (CoffeeScript, Haskell), но большинство распространенных языков имеют специальный синтаксис для функций (Java, C ++, Python, JavaScript, C #, PHP, Ruby).
Даже в Scala, которая поддерживает оба способа (и имеет вывод типов), чаще пишут:
def addOne(x: Int) = x + 1
Скорее, чем:
val addOne = (x: Int) => x + 1
IMO, по крайней мере в Scala, это, пожалуй, самая легко понятная версия, но за этой идиомой редко следуют:
val x: Int = 1
val y: Int = addOne(x)
val addOne: (Int => Int) = x => x + 1
Я работаю над своим собственным игрушечным языком, и мне интересно, есть ли какие-нибудь подводные камни, если я создаю свой язык таким образом, и есть ли какие-либо исторические или технические причины, по которым этот шаблон широко не используется?
(int => int) addOne = (x) => {
это гораздо более "особенный" и "сложный", чемint addOne(int) {
...Ответы:
Я думаю, что причина в том, что большинство популярных языков происходят из семейства языков C или находились под его влиянием, в отличие от функциональных языков и их корня, лямбда-исчисления.
И в этих языках функции не просто еще одно значение:
const
,readonly
илиfinal
запретить мутации), но функции не могут быть переназначены.С более технической точки зрения код (который состоит из функций) и данные являются отдельными. Как правило, они занимают разные части памяти, и к ним обращаются по-разному: код загружается один раз, а затем только выполняется (но не читается или записывается), тогда как данные часто постоянно выделяются и освобождаются и записываются и читаются, но никогда не выполняются.
И поскольку C должен был быть «близок к металлу», имеет смысл отразить это различие и в синтаксисе языка.
Подход «функция - это просто ценность», который лежит в основе функционального программирования, получил распространение в распространенных языках лишь сравнительно недавно, о чем свидетельствует позднее введение лямбд в C ++, C # и Java (2011, 2007, 2014).
источник
Это потому, что людям важно осознавать, что функции - это не просто «другая именованная сущность». Иногда имеет смысл манипулировать ими как таковыми, но они все же могут быть распознаны с первого взгляда.
На самом деле не имеет значения, что компьютер думает о синтаксисе, поскольку непонятный набор символов хорош для интерпретации машиной, но для людей это почти невозможно понять и поддерживать.
Это действительно та же самая причина, по которой у нас есть циклы while и for, switch и if else и т. Д., Хотя все они в конечном итоге сводятся к инструкции сравнения и перехода. Причина в том, что он существует для блага людей, которые поддерживают и понимают код.
Наличие ваших функций в качестве «другой именованной сущности» так, как вы предлагаете, сделает ваш код труднее для понимания и, следовательно, труднее для понимания.
источник
whatsisname
был больше адресован первому пункту (и предупредил о некоторой опасности удаления этих отказоустойчивых), в то время как ваш комментарий больше связан со второй частью вопроса. Действительно, можно изменить этот синтаксис (и, как вы описали, это уже было сделано много раз ...), но он не подойдет всем (так как упс не подходит всем тожеВозможно, вам будет интересно узнать, что еще в доисторические времена язык под названием ALGOL 68 использовал синтаксис, близкий к тому, что вы предлагаете. Признавая, что идентификаторы функций связаны со значениями так же, как и другие идентификаторы, вы могли бы на этом языке объявить функцию (константу), используя синтаксис
Конкретно ваш пример будет читать
Признание избыточности в том, что начальный тип может быть считан из RHS декларации, и, поскольку тип функции всегда начинается с
PROC
, это может (и обычно) заключаться вно обратите внимание, что
=
все еще стоит перед списком параметров. Также обратите внимание, что если вы хотите, чтобы переменная функции (которой впоследствии могло быть назначено другое значение того же типа функции),=
должна быть заменена на:=
, давая один изОднако в этом случае обе формы фактически являются аббревиатурами; поскольку идентификатор
func var
обозначает ссылку на локально сгенерированную функцию, полностью развернутая форма будетК этой конкретной синтаксической форме легко привыкнуть, но она явно не имела большого числа последователей в других языках программирования. Даже функциональные языки программирования , такие как Haskell предпочитают стиль
f n = n+1
с=
следующим списком параметров. Я думаю, причина в основном психологическая; В конце концов, даже математики не часто предпочитают, как и я, f = n ⟼ n + 1, а не f ( n ) = n + 1.Кстати, в приведенном выше обсуждении подчеркивается одно важное различие между переменными и функциями: определения функций обычно связывают имя с одним конкретным значением функции, которое не может быть изменено позднее, тогда как определения переменных обычно вводят идентификатор с начальным значением, но тот, который может измениться позже. (Это не абсолютное правило; функциональные переменные и не-функциональные константы встречаются в большинстве языков.) Кроме того, в скомпилированных языках значение, определенное в определении функции, обычно является константой времени компиляции, так что вызовы функции могут быть скомпилирован с использованием фиксированного адреса в коде. В C / C ++ это даже требование; эквивалент Алгола 68
не может быть написано на C ++ без введения указателя на функцию. Этот вид особых ограничений оправдывает использование другого синтаксиса для определений функций. Но они зависят от семантики языка, и обоснование не распространяется на все языки.
источник
Вы упомянули Java и Scala в качестве примеров. Однако вы упустили важный факт: это не функции, а методы. Методы и функции принципиально разные. Функции являются объектами, методы принадлежат объектам.
В Scala, которая имеет как функции, так и методы, есть следующие различия между методами и функциями:
Итак, предложенная вами замена просто не работает, по крайней мере, в тех случаях.
источник
Причины, которые я могу придумать:
источник
Если вы не хотите пытаться редактировать исходный код на машине, которая сильно ограничена в ОЗУ, или минимизировать время на считывание его с дискеты, что не так с использованием ключевых слов?
Конечно, читать приятнее,
x=y+z
чем этоstore the value of y plus z into x
, но это не значит, что знаки препинания по своей природе «лучше», чем ключевые слова. Если переменныеi
,j
иk
естьInteger
, иx
естьReal
, рассмотрим следующие строки в Паскале:Первая строка будет выполнять усечающее целочисленное деление, а вторая - деление на действительное число. Различие может быть сделано хорошо, потому что Паскаль использует в
div
качестве своего оператора усечения целочисленного деления, вместо того, чтобы пытаться использовать знак препинания, у которого уже есть другая цель (деление действительного числа).Хотя есть несколько контекстов, в которых может быть полезно сделать определение функции кратким (например, лямбда-выражение, которое используется как часть другого выражения), функции обычно должны выделяться и быть легко визуально распознаваемыми как функции. В то время как возможно было бы сделать различие намного более тонким и использовать только знаки препинания, какой смысл? Говоря
Function Foo(A,B: Integer; C: Real): String
проясняет, как называется функция, какие параметры она ожидает и что она возвращает. Может быть, можно было бы сократить его на шесть или семь символов, заменив ихFunction
некоторыми знаками препинания, но что из этого получится?Еще одна вещь, на которую следует обратить внимание, заключается в том, что в большинстве фреймворков существует фундаментальное различие между объявлением, которое всегда будет ассоциировать имя с конкретным методом или конкретной виртуальной привязкой, и тем, которое создает переменную, которая первоначально идентифицирует конкретный метод или привязку, но может быть изменен во время выполнения для идентификации другого. Поскольку в большинстве процедурных структур это очень семантически разные понятия, имеет смысл, что они должны иметь разный синтаксис.
источник
void f() {}
на самом деле короче, чем лямбда-эквивалент в C ++ (auto f = [](){};
), C # (Action f = () => {};
) и Java (Runnable f = () -> {};
). Краткость лямбд проистекает из логического вывода и опущенияreturn
, но я не думаю, что это связано с тем, что задают эти вопросы.Ну, причина может быть в том, что эти языки, так сказать, недостаточно функциональны. Другими словами, вы довольно редко определяете функции. Таким образом, использование дополнительного ключевого слова допустимо.
На языках наследия ML или Miranda, OTOH, вы определяете функции большую часть времени. Посмотрите на некоторый код на Haskell, например. Буквально это в основном последовательность определений функций, многие из которых имеют локальные функции и локальные функции этих локальных функций. Следовательно, забавное ключевое слово в Haskell было бы ошибкой так же, как и требование оператора вызова на императивном языке для начала с assign . Назначение причин, вероятно, является наиболее частым утверждением.
источник
Лично я не вижу фатального недостатка в вашей идее; вы можете обнаружить, что это сложнее, чем вы ожидали, выразить определенные вещи с помощью вашего нового синтаксиса, и / или вы можете обнаружить, что вам нужно его пересмотреть (добавив различные особые случаи и другие функции и т. д.), но я сомневаюсь, что вы найдете себя необходимость полностью отказаться от идеи.
Предложенный вами синтаксис выглядит более или менее как вариант некоторых стилей обозначений, иногда используемых для выражения функций или типов функций в математике. Это означает, что, как и все грамматики, это, вероятно, понравится некоторым программистам больше, чем другим. (Как математик, мне это нравится.)
Тем не менее, вы должны заметить, что в большинстве языков
def
синтаксис -style (то есть традиционный синтаксис) ведет себя иначе, чем стандартное присвоение переменных.C
иC++
функции обычно не рассматриваются как «объекты», то есть фрагменты типизированных данных, которые нужно скопировать и поместить в стек, и еще много чего. (Да, у вас могут быть указатели на функции, но они по-прежнему указывают на исполняемый код, а не на «данные» в обычном смысле.)self
(который, кстати, на самом деле не является ключевым словом; вы можете сделать любой допустимый идентификатор первым аргументом метода).Вам нужно подумать, точно ли ваш новый синтаксис (и, надеюсь, интуитивно) отражает то, что фактически делает компилятор или интерпретатор. Это может помочь понять, скажем, разницу между лямбдами и методами в Ruby; это даст вам представление о том, чем ваша парадигма «функции-просто-данные» отличается от типичной ОО / процедурной парадигмы.
источник
Для некоторых языков функции не являются значениями. На таком языке сказать, что
это определение функции, тогда как
объявляет константу, сбивает с толку, потому что вы используете один синтаксис для обозначения двух вещей.
Другие языки, такие как ML, Haskell и Scheme, рассматривают функции как значения 1-го класса, но предоставляют пользователю специальный синтаксис для объявления констант, имеющих значение функции. * Они применяют правило «использование сокращает форму». Т.е. если конструкция является как общей, так и многословной, вы должны дать пользователю сокращение. Нелегко давать пользователю два разных синтаксиса, которые означают одно и то же; иногда элегантность должна быть принесена в жертву полезности.
Если на вашем языке функции относятся к 1-му классу, то почему бы не попытаться найти синтаксис, достаточно краткий, чтобы вы не испытали искушения найти синтаксический сахар?
-- Редактировать --
Еще одна проблема, которую еще никто не затронул, - это рекурсия. Если вы позволите
и вы позволяете
из этого следует, что вы должны позволить
На ленивом языке (например, на Haskell) здесь нет проблем. В языке, в котором практически отсутствуют статические проверки (например, LISP), здесь нет проблем. Но в статически проверяемом нетерпеливом языке вы должны быть осторожны с тем, как определяются правила статической проверки, если вы хотите разрешить первые два и запретить последние.
- Конец редактирования -
* Можно утверждать, что Haskell не входит в этот список. Он предоставляет два способа объявления функции, но оба являются, в некотором смысле, обобщением синтаксиса для объявления констант других типов
источник
Это может быть полезно на динамических языках, где тип не так важен, но он не так удобен для чтения в статических типизированных языках, где вы всегда хотите знать тип вашей переменной. Кроме того, в объектно-ориентированных языках очень важно знать тип вашей переменной, чтобы знать, какие операции она поддерживает.
В вашем случае, функция с 4 переменными будет:
Когда я смотрю на заголовок функции и вижу (x, y, z, s), но я не знаю типы этих переменных. Если я хочу узнать тип,
z
который является третьим параметром, мне придется посмотреть на начало функции и начать считать 1, 2, 3, а затем увидеть, что тип являетсяdouble
. По-прежнему я смотрю прямо и вижуdouble z
.источник
var addOne = (int x, long y, double z, String s) => { x + 1 }
не-дебильного статически типизированного языка по вашему выбору (примеры: C #, C ++, Scala). Для этого вполне достаточно даже очень ограниченного локального вывода типа, используемого C #. Таким образом, этот ответ просто критикует определенный синтаксис, который в первую очередь сомнителен и фактически нигде не используется (хотя у синтаксиса Haskell есть очень похожая проблема).Существует очень простая причина для проведения такого различия в большинстве языков: необходимо различать оценку и декларацию . Ваш пример хорош: почему не любят переменные? Ну, выражения переменных сразу оцениваются.
У Haskell есть специальная модель, в которой нет различия между оценкой и объявлением, поэтому нет необходимости в специальном ключевом слове.
источник
В большинстве языков функции объявляются иначе, чем в литералах, объектах и т. Д., Потому что они используются по-разному, по-разному отлаживаются и создают разные потенциальные источники ошибок.
Если в функцию передается ссылка на динамический объект или изменяемый объект, функция может изменять значение объекта при запуске. Этот вид побочного эффекта может затруднить отслеживание того, что будет делать функция, если она вложена в сложное выражение, и это является распространенной проблемой в таких языках, как C ++ и Java.
Рассмотрим отладку какого-либо модуля ядра в Java, где у каждого объекта есть операция toString (). Хотя можно ожидать, что метод toString () должен восстановить объект, ему может потребоваться разобрать и собрать объект, чтобы преобразовать его значение в объект String. Если вы пытаетесь отладить методы, которые toString () будет вызывать (в сценарии с крючками и шаблонами), чтобы выполнить свою работу, и случайно выделить объект в окне переменных большинства IDE, это может привести к сбою отладчика. Это связано с тем, что среда IDE попытается выполнить toString () для объекта, который вызывает сам код, который находится в процессе отладки. Ни одно примитивное значение никогда не будет таким дерьмом, потому что семантическое значение примитивных значений определяется языком, а не программистом.
источник