2Особенности функциональных языков Как любая группа языков программирования, функциональные языки обладают некоторыми особенностями, которые выделяют их среди остальных языков и делают их именно языками функционального программирования.
2.1Основные свойства Среди свойств функциональных языков обычно выделяют следующие:
краткость и простота;
строгая типизация;
функции – это значения;
чистота (отсутствие побочных эффектов);
отложенные (ленивые) вычисления;
модульность.
Рассмотрим каждое из этих свойств подробнее.
Краткость и простота Программы на функциональных языках обычно короче и проще, чем те же самые программы на императивных языках. Одним из самых распространенных примеров является быстрая сортировка Хоара.
На императивном языке C быстрая сортировка обычно реализуется следующим образом:
void qsort (int a[], int l, int r)
{
int i = l, j = r, x = a[(l + r) / 2];
do
{
while (a[i] < x) i++;
while (x < a[j]) j--;
if (i <= j)
{
int temp = a[i];
a[i++] = a[j];
a[j--] = temp;
}
}
while (i <= j);
if (l < j) qsort (a, l, j);
if (i < r) qsort (a, i, r);
}
На функциональном языке Haskell эта же сортировка записывается гораздо короче и нагляднее:
qsort [] = []
qsort (x:xs) = qsort [y | y <- xs, y < x]
++ [x] ++ qsort [y | y <- xs, y >= x]
Этот пример следует читать так: если сортируемый список пуст, то результатом сортировки также будет является пустой список, иначе выделяются голова и хвост списка (первый элемент списка и список из оставшихся элементов, возможно пустой), и результатом будет являться конкатенация (сращивание) отсортированного списка из всех элементов хвоста, которые меньше головы, списка из самой головы и списка из всех элементов хвоста, которые больше либо равны голове.
Код на функциональном языке выигрывает по размеру кода и его наглядности. Кроме того, приведенная реализация на языке C сортирует массив, элементы которого имеют целочисленный тип (int), а реализация на Haskell может сортировать списки элементов любых типов, на которых определена операция сравнения.
Строгая типизация Большинство языков функционального программирования имеют строгую типизацию. Строгая типизация подразумевает выполнение следующих обязательных условий [3]:
каждое значение, переменная, аргумент и возвращаемое значение функции на этапе проектирования программы безусловно привязывается к определённому типу данных, который не может быть изменён во время выполнения программы;
функции могут принимать и возвращать значения, имеющие строго тот же тип данных, что указан при описании функции;
каждая операция требует аргументов строго определённых типов;
неявное преобразование типов не допускается (то есть транслятор воспринимает любую попытку использовать значение не того типа, который был описан для переменной, аргумента, функции или операции, как ошибку).
В теории программирования строгая типизация является непременным элементом обеспечения надёжности разрабатываемых программных средств [3]. При правильном применении (подразумевающем, что в программе объявляются и используются отдельные типы данных для логически несовместимых значений) она защищает программиста от простых, но труднообнаруживаемых ошибок, связанных с совместным использованием логически несовместимых значений, возникающих иногда просто из-за элементарной описки. Подобные ошибки выявляются ещё на этапе компиляции программы, тогда как при возможности неявного приведения практически любых типов друг к другу (как, например, в классическом языке Си) эти ошибки выявляются только при тестировании, причём не все и не сразу.
Функции как значения В функциональных языках программирования функции могут использоваться как любые другие объекты, они могут передаваться другим функциям в качестве аргументов, возвращаться в качестве результата других функций, храниться в списках и других структурах данных. Функции, принимающие в качестве аргументов или возвращающие как результат другие функции называются функциями высших порядков.
Использование функций высших порядков позволяет создавать более гибкие функции, повышая тем самым возможность повторного использования кода. В качестве примера обычно приводится передача функции сравнения элементов в функцию сортировки.
Чистота Чистота заключается в отсутствии побочных эффектов при вычислении значений функции. Побочный эффект функции — это возможность в процессе вычисления своих вычислений чтения и изменения значений глобальных переменных, осуществления операций ввода/вывода, реагирования на исключительные ситуации, изменение значений входных переменных. Поэтому, если вызвать такую функцию дважды с одним и тем же набором значений входных аргументов, то результаты вычисления могут различаться, а также может измениться состояние некоторых глобальных объектов (например, значения переменных). Такие функции называются недетерминированными функциями с побочными эффектами.
В чистом функциональном программировании одна и та же функция при одних и тех же аргументах возвращает всегда один и тот же результат. Созданные объекты нельзя изменять или уничтожать, можно только создавать новые на основе уже существующих. Благодаря чистоте, программы не только становятся понятнее, но и упрощается организация параллелизма в них, так как функции могут вычисляться независимо друг от друга. Если результат чистой функции не используется, то ее вычисление может опускаться без вреда для других выражений. И если нет никакой зависимости по данным между двумя чистыми функциями, то можно поменять порядок их вычисления или вычислить их параллельно. В таком случае компилятор может использовать любую политику вычисления. Это предоставляет свободу компилятору комбинировать и реорганизовывать вычисление выражений в программе.
Отложенные вычисления В традиционных языках перед вызовом функции вычисляются значения всех ее аргументов. Этот метод вызова функций называется «вызовом по значению». Если же какие-то из аргументов не используются, то вычисления были произведены впустую. Во многих функциональных языках используется другой метод вызова функций — «вызов по необходимости». При этом каждый аргумент функции вычисляется только в том случае, если его значение необходимо для вычисления результата функции. Например, операция конъюнкции из языка C++ (&&) не требует вычисления значение второго аргумета, если первый имеет ложное значение.
Ленивые вычисления в некоторых случаях позволяют ускорить выполнение программы, а так же позволяют использовать различные бесконечные структуры данных.
Модульность Механизм модульности позволяет разделять программы на несколько сравнительно независимых частей (или модулей) с четко определенными связями между ними. Тем самым облегчается процесс проектирования и последующей поддержки больших программных систем. Поддержка модульности не является свойством именно функциональных языков программирования, однако поддерживается большинством таких языков.
|