Говоря о функциональном программировании, невозможно не рассмотреть основы ФП - функции, их представление в виде кода.
С точки зрения дизайна языка, существует два классических способа объявления функции: си-подобный и функциональный.
1. классический Си-подобный:
возвращаемый_тип имя_функции(список_аргументов)
Такой способ достаточно компактен, более чем привычен (C, C++, Java, C#), но в плане дизайна не лишен некоторых недостатков. И хотя в простейших случаях они могут не проявляться, следующий способ в любом случае открывает больше возможностей.
2. классический функциональный
ключевое_слово имя_функции (список_аргументов) возвращаемые_значения
Способ применяется в PHP, JavaScript, Python, Ruby, Go, Rust, Scala, Nemerle. Даже в C++11 подобный способ появился как альтернатива традиционному определению функций (и там это вызвано вполне прагматическими причинами). Практически во всех современных языках разработчики выбирают именно этот способ, и это не случайно.
Особенностью этого способа является то, что объявление функции начинается с ключевого слова. Это удобно для различных парсеров, особенно для IDE. Кроме того, это удобно для программиста - как минимум, проще искать функции в коде. Такой способ позволяет унифицировать внешнйи вид объявления функций с другими объявлениями - переменных, структур, перечислений, классов и т.д. В случае, если функция шаблонная, вполне логично и иногда даже необходимо, чтобы возвращаемый тип был определен после (и на основе) принимаемых (это причина ввода нового синтаксиса в С++).
Кроме того, появляется возможность определить несколько ключевых слов для определения разных типов функций (особенно это касается методов классов).
Несмотря на все это, во многих языках объявление функций способом 2 получается более громоздким, чем "сишный" вариант. Это связано с тем, что 1) выбирают слишком длинные ключевые слова (function, procedure), и 2) вводят много "мусорного" синтаксиса (всякие двоеточия, стрелочки и т.п.).
Таким образом, каждый метод предваряется некоторым ключевым словом:
def — для впервые объявленных функций
redef — для переопределенных (в дочерних классах)
virtual - для виртуальных
override - для переопределенных виртуальных
Кроме того, по аналогичным причинам имеет смысл использовать ключевые слова для конструкторов и деструкторов:
init - конструкторы
final — деструкторы
Никаких декоративных элементов (двоеточий, стрелок) в определении функции не используется. Также можно сократить длину за счет отказа от указания типа для каждого аргумента. Если несколько перечисляемых один за другим аргументов имеют один и тот же тип, то достаточно указать этот тип один раз в самом начале.
def Func2(int x, y) char
{
}
Дополнительно следует сказать про имена конструкторов и деструкторов. Если любой метод обязан иметь имя, то конструкторы и деструкторы - методы init и final могут как иметь произвольные имена, так и не иметь их. Имена, если они есть, подчиняются общим правилам именования методов. Опциональная возможность именования конструкторов и деструкторов дает дополнительную возможность самодокументирования кода. Варинат с именами:
init InitDefault() {}
init InitFromStr(string s) {}
или по-старинке:
init() {}
init(string s) {}
если имя есть, то на функцию-конструктор можно сослаться, можно вызывать ее для переинициализации объекта, взять ее адрес, можно вызвать один конструктор из другого и т.д. Дополнительные преимущества такого подхода - имя класса освобождается от дополнительной нагрузки (в C++ и C# имена конструкторов и деструкторов совпадают с именем класса). Если предположить, что мы имеем дело с языком, в котором каждая программная сущность — объект, то снимается неоднозначность: имя класса — уникальный идентификатор объекта класса, имя конструктора — уникальный идентификатор объекта-функции.
С точки зрения дизайна языка, существует два классических способа объявления функции: си-подобный и функциональный.
1. классический Си-подобный:
возвращаемый_тип имя_функции(список_аргументов)
Такой способ достаточно компактен, более чем привычен (C, C++, Java, C#), но в плане дизайна не лишен некоторых недостатков. И хотя в простейших случаях они могут не проявляться, следующий способ в любом случае открывает больше возможностей.
2. классический функциональный
ключевое_слово имя_функции (список_аргументов) возвращаемые_значения
Способ применяется в PHP, JavaScript, Python, Ruby, Go, Rust, Scala, Nemerle. Даже в C++11 подобный способ появился как альтернатива традиционному определению функций (и там это вызвано вполне прагматическими причинами). Практически во всех современных языках разработчики выбирают именно этот способ, и это не случайно.
Особенностью этого способа является то, что объявление функции начинается с ключевого слова. Это удобно для различных парсеров, особенно для IDE. Кроме того, это удобно для программиста - как минимум, проще искать функции в коде. Такой способ позволяет унифицировать внешнйи вид объявления функций с другими объявлениями - переменных, структур, перечислений, классов и т.д. В случае, если функция шаблонная, вполне логично и иногда даже необходимо, чтобы возвращаемый тип был определен после (и на основе) принимаемых (это причина ввода нового синтаксиса в С++).
Кроме того, появляется возможность определить несколько ключевых слов для определения разных типов функций (особенно это касается методов классов).
Несмотря на все это, во многих языках объявление функций способом 2 получается более громоздким, чем "сишный" вариант. Это связано с тем, что 1) выбирают слишком длинные ключевые слова (function, procedure), и 2) вводят много "мусорного" синтаксиса (всякие двоеточия, стрелочки и т.п.).
Таким образом, каждый метод предваряется некоторым ключевым словом:
def — для впервые объявленных функций
redef — для переопределенных (в дочерних классах)
virtual - для виртуальных
override - для переопределенных виртуальных
Кроме того, по аналогичным причинам имеет смысл использовать ключевые слова для конструкторов и деструкторов:
init - конструкторы
final — деструкторы
Никаких декоративных элементов (двоеточий, стрелок) в определении функции не используется. Также можно сократить длину за счет отказа от указания типа для каждого аргумента. Если несколько перечисляемых один за другим аргументов имеют один и тот же тип, то достаточно указать этот тип один раз в самом начале.
def Func2(int x, y) char
{
}
Дополнительно следует сказать про имена конструкторов и деструкторов. Если любой метод обязан иметь имя, то конструкторы и деструкторы - методы init и final могут как иметь произвольные имена, так и не иметь их. Имена, если они есть, подчиняются общим правилам именования методов. Опциональная возможность именования конструкторов и деструкторов дает дополнительную возможность самодокументирования кода. Варинат с именами:
init InitDefault() {}
init InitFromStr(string s) {}
или по-старинке:
init() {}
init(string s) {}
если имя есть, то на функцию-конструктор можно сослаться, можно вызывать ее для переинициализации объекта, взять ее адрес, можно вызвать один конструктор из другого и т.д. Дополнительные преимущества такого подхода - имя класса освобождается от дополнительной нагрузки (в C++ и C# имена конструкторов и деструкторов совпадают с именем класса). Если предположить, что мы имеем дело с языком, в котором каждая программная сущность — объект, то снимается неоднозначность: имя класса — уникальный идентификатор объекта класса, имя конструктора — уникальный идентификатор объекта-функции.
No comments:
Post a Comment