Почему большинство языков программирования имеют специальное ключевое слово или синтаксис для объявления функции?

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

Например в 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;
}

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

Я знаю очень мало языков использует эту идею (в CoffeeScript, Хаскел), но самые распространенные языки имеют специальный синтаксис для функций (Java, С++, Python и JavaScript, и с#, PHP, Ruby и т. д.).

Даже в Scala, который обслуживает обе стороны (и вывод типа), его более общим, чтобы написать:

def addOne(x: Int) = x + 1

А не:

val addOne = (x: Int) => x + 1

ИМО, по крайней мере, в Scala, это, наверное, самый понятный вариант, но эта идиома редко следуют:

val x: Int = 1
val y: Int = addOne(x)
val addOne: (Int => Int) = x => x + 1

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

Комментарии к вопросу (26)

Это's, потому что он's важный для Люди признать, что функции не просто "по-другому именованная сущность" по. Иногда имеет смысл манипулировать ими как таковые, но они по-прежнему могут быть признаны на первый взгляд.

Это вовсе't действительно важно, что думает компьютер о синтаксисе, как непонятное скопление символов-это нормально для машины, чтобы интерпретировать, но это будет почти невозможно для людей, чтобы понять и поддержать.

Это действительно та же причина, почему у нас хотя и для петли, переключатель и если еще, и т. д., Хотя все они в конечном счете сводятся к сравнения и скачки инструкции. Причина в том, что он'ы есть на благо людей, сохраняя и понимание кода.

Имея свои функции как "Другая именованная сущность" и как вы предлагаете сделать ваш код труднее увидеть, и поэтому труднее понять.

Комментарии (6)
Решение

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

И в этих языках, функции не просто другое значение:

  • В C++, C# и Java, можно перегрузить функции: вы можете иметь две функции с одинаковыми именами, но разными сигнатурами.
  • В C и C++,# и Java, вы можете иметь значения, которые представляют функции, а указатели на функции, функторы, делегаты и функциональные интерфейсы все отличаются от самих функций c. Отчасти это объясняется тем, что большинство из них на самом деле не просто функции, их функция вместе с некоторым (изменяемый) государства.
  • Переменные-изменяемые по умолчанию (вы должны использовать как const, только для чтения или окончательной запретить мутации), но функции могут'т быть переназначены.
  • С более технической точки зрения, код (который состоит из функции) и данные отдельно. Они, как правило, занимают разные участки памяти, а доступ к ним осуществляется по-разному: код один раз и потом загружается только выполняются (но не чтение или запись), то данные часто постоянно выделяется и освобождается, и пишется и читается, но никогда не выполняется.

И так как C суждено быть "близко к металла", имеет смысл отразить это различие в синтаксисе тоже язык.

  • В "функция-это просто значение" и подход, который лежит в основе функционального программирования уже набирает обороты в общих языков, сравнительно недавно, о чем свидетельствует поздним представлением лямбда-выражения в C++, C# и Java (2011, 2007, 2014).
Комментарии (5)

Возможно, Вам будет интересно узнать, что еще в доисторические времена, язык называется Алгол-68, используемый синтаксис близок к тому, что вы предлагаете. Признавая, что идентификаторы функции связаны со значениями, как и другие идентификаторы, вы могли бы на этом языке объявлении функции (константа), используя синтаксис

function-type name = (parameter-list) result-type : body ;

Конкретно ваш пример будет читать

PROC (INT)INT add one = (INT n) INT: n+1;

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

PROC add one = (INT n) INT: n+1;

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

PROC (INT)INT func var := (INT n) INT: n+1;
PROC func var := (INT n) INT: n+1;

Однако в данном случае обе формы являются в действительности сокращений; с идентификатором кнопку func ВАР обозначает ссылку на локально генерируемой функции, в полностью развернутом виде будет

REF PROC (INT)INT func var = LOC PROC (INT)INT := (INT n) INT: n+1;

Эта конкретная синтаксическая форма легко привыкнуть, но он явно не имел много последователей в других языках программирования. Даже функциональные языки программирования типа Haskell предпочитают стиль ф Н = Н+1 с = following список параметров. Я думаю, причина в основном психологическая, ведь даже математиков Дон'т часто предпочитают, как и я, f = не ⟼ я + 1 за f(я) = я + 1.

Кстати, вышесказанное не подчеркнуть одно важное различие между переменными и функции: функции определения, как правило, связывают с именем одного конкретного функцией значения, которые не могут быть изменены позже, в то время как определения переменных, как правило, ввести идентификатор, с initial значение, но тот, который может изменить позже. (Это не абсолютное правило; переменные функции и функции констант происходят в большинстве языков.) Кроме того, в компилируемых языках значение в определении функции обычно константа времени компиляции, так что вызовы функции могут быть скомпилированы с использованием фиксированного адреса в коде. В C/C++ это даже требование; эквивалент Алгол 68

PROC (REAL) REAL f = IF mood=sunny THEN sin ELSE cos FI;

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

Комментарии (2)

Вы упомянули Java и Scala как примеры. Однако, вы упустили из виду важный факт: это еще'т функции, эти методы. Методов и функций принципиально отличается. Функции-это объекты, методы принадлежат к объектам.

В Scala, которая имеет обе функции и методы, существуют следующие различия между методами и функциями:

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

Таким образом, предложенную вами замену просто не't работа, по крайней мере, для тех случаях.

Комментарии (4)

Причины, по которым я могу думать, являются:

  • Это проще для компилятора, чтобы знать, что мы декларирования.
  • Это'ы важно для нас, чтобы знать (в тривиальном смысле), является ли это функция или переменная. Функции обычно черные ящики и мы не'т заботиться о своей внутренней реализации. Мне не нравится определение типа для возвращаемых типов в Scala, потому что я считаю, что это'ы проще использовать функцию, которая имеет тип возвращаемого значения: зачастую единственное документации.
  • И самое важное-это следовать за толпой стратегия, используемая при разработке языков программирования. C++ был создан, чтобы украсть программистов C и Java был разработан таким образом, что не'т напугать программистов C++ и C#, чтобы привлечь программистов на Java. Даже C#, который я думаю, является очень современным языком с удивительная команда, скопировал некоторые ошибки с Java или даже С.
Комментарии (2)

Повернуть вопрос, что это'т заинтересованы в попытке изменить исходный код на компьютере, который является чрезвычайно ОЗУ-ограниченным, или минимизировать время, чтобы прочитать его с дискеты, что'ы не так с помощью ключевых слов?

Конечно, это'ы приятнее читать Х=Y+Z и чем хранения значения y и Z на х, но это вовсе'т имею в виду, что знаков препинания по своей сути, что "лучше" в чем ключевые слова. Если переменныея,ж, иKявляютсяцелое, аXявляетсяреальным`, рассмотрим следующие строки в Паскаль:

k := i div j;
x := i/j;

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

Хотя есть несколько контекстов, в которых это может быть полезно, чтобы сделать определение функции лаконичным (напр. лямбда-выражение, которое используется в качестве части другого выражения), функции, как правило, должен выделяться и быть легко узнаваемым в качестве функции. Хотя это может быть возможным, чтобы сделать различие более тонкое и использовать ничего, кроме знаков препинания, в чем смысл? Говорю функция Foo(A,Б: число; с: Реал): строка дает понять, что функция's имя, какие параметры он ожидает, и что он возвращает. Может быть, можно сократить на шесть или семь символов, заменив функция с Некоторые знаки препинания, но какие результаты будут достигнуты?

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

Комментарии (1)

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

В языках мл или Миранда наследия, ото, вы определяете функции большую часть времени. Рассмотрим некоторые кода на Haskell, например. Это's буквально в основном последовательность определений функций, многие из них локальные функции и локальные функции этих локальных функций. Таким образом, удовольствие ключевое слово в Haskell будет ошибкой так велика, как требует назначение заявление в императивных языках, чтобы начать с назначить. Причиной назначения это, вероятно, один самых частых заявлении.

Комментарии (0)

В некоторых языках функции не являются ценностями. На таком языке сказать, что

val f = > ;

это определение функции, а

val a = 1 ;

объявляется постоянный, сбивает с толку, потому что вы используете один синтаксис означает две вещи.

Другие языки, такие как ML, Haskell и схемой лечения в 1-й класс ценностей, но предоставляет пользователю специальный синтаксис для объявления функции оценены константы.* Они применяют правило, которое мне;использование сокращает формат&quot&;. Т. е. если конструкция имеет как общие, так и подробные, вы должны дать пользователю стенографии. Это неэлегантно, чтобы дать пользователю два разных синтаксисов, что означает то же самое; иногда элегантность должна быть принесена в жертву полезности.

Если, в вашем языке, функций 1-го класса, то почему бы не попробовать найти синтаксис, который является достаточно сжато, что вы выиграли'т быть уговорены для того чтобы найти синтаксический сахар?

-- Редактирование --

Другой вопрос никто не поднял (пока) - это рекурсия. Если вы позволите

{ 
    val f = > ;
    val g = > ;
    f(x)
}

и вы позволяете

{
    val a = 42 ;
    val b = a + 1 ;
    a
} ,

следует ли из этого, что вы должны позволить

{
    val a = b + 1 ; 
    val b = a - 1 ;
    a
} ?

В "ленивом" языке (как в Haskell), нет никакой проблемы здесь. В языке практически без статических проверок (как Лисп), нет никакой проблемы здесь. Но в статически-проверено нетерпеливый язык, вы должны быть осторожны, о том, как правила статического контроля определены, если вы хотите, чтобы первые два и запретить последние.

-- Конец редактирования --

*Можно утверждать, что Хаскелл не принадлежит в этом списке. Он предлагает два способа, чтобы объявить функцию, но и являются, в некотором смысле, обобщения синтаксис объявления константы прочая

Комментарии (0)

Лично я не вижу никаких фатальных недостатков в вашей идее, вы можете обнаружить, что это'ы сложнее, чем вы рассчитывали, чтобы выразить определенные вещи через ваш новый синтаксис, и/или вы можете обнаружить, что вам нужно пересмотреть его (добавляя различные частные случаи и другие особенности и т. д.), Но я сомневаюсь, что вы'll найти себе нуждающихся, чтобы отказаться от этой идеи полностью.

Синтаксис, который вы'ве предлагаемой выглядит более или менее как вариант какие-то нотации стили иногда используется для выражения функции и типы функций в математике. Это означает, что, как и любая грамматика, она, вероятно, будет более привлекательна для некоторых программистов, чем другие. (Как математик, мне оно нравится.)

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

  • В С и C++ семья, функции, разве'т, как правило, рассматриваться как "объекты" Ну, т. е. куски типизированных данных, которые будут скопированы и помещены в стек и все такое. (Да, вы можете иметь указатели на функции, но, тем не менее указать на исполняемый код, а не в "Сведения" в обычном смысле.)
  • В большинстве ОО языков, там's специальные обработки для методов (т. е. функции-члены); то есть, они'повторно не только функции, объявленные внутри области определения класса. Самое главное отличие заключается в том, что объект, на котором вызывается метод обычно передается как неявный первый параметр метода. Питон делает это явно с " Я " (которое, кстати, фактически не с сайта; вы могли бы сделать любой допустимый идентификатор, первый аргумент метода).

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

Комментарии (0)

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

Если динамический ссылочный объект и изменяемый объект передается в функцию, функция может изменять значение объекта, как он работает. Такой побочный эффект может сделать это трудно, чтобы следовать, что функция будет делать, если он находится внутри сложного выражения, и это является распространенной проблемой в таких языках, как C++ и Java.

Рассмотрим отладку какой-то модуль ядра в Java, где каждый объект имеет метод toString (операция). Хотя можно предположить, что метод toString() должен восстановить объект, он может разобрать и собрать объект с целью перевода его значения строкового объекта. Если вы'вновь пытается отладить методы, метод toString() будет вызывать (в крюк-и-шаблон сценария), чтобы делать свою работу, и случайно выделите объект в окне "переменные" в большинстве IDE's, он может врезаться в отладчике. Это потому, что IDE будет попробовать метод toString() объекта, который вызывает очень код, вы're в процессе отладки. Не примитивное значение когда-нибудь, как эту хрень потому, что смысловым примитивных значений определяется по языку, а не программиста.

Комментарии (0)

Есть очень простая причина такого различия в большинстве языков: необходимо различать оценки и * объявление. Ваш пример хороший: почему не как переменные? Ну, переменных, выражений оценили сразу.

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

Комментарии (1)

Это может быть полезно на динамических языках, где тип-это не так важно, но это's не то, что читается в статических типизированных языках, где всегда вы хотите знать тип переменной. Кроме того, в объектно-ориентированных языках это'ы очень важно знать тип переменной, для того, чтобы знать, какие операции он поддерживает.

В вашем случае, функция с 4 переменными будут:

(int, long, double, String => int) addOne = (x, y, z, s) => {
  return x + 1;
}

Когда я смотрю на заголовок функции и Смотри (х, у, Z, с), но я не знаю типы этих переменных. Если я хочу знать, типа Я, который является третий параметр, я'll должны смотреть на начало функции и начинаем считать 1, 2, 3 и затем, чтобы увидеть, что тип "двойной". В бывший, как я смотрю прямо и вижу двойной Z.

Комментарии (4)