Pull to refresh

Чистые функции

Reading time 2 min
Views 14K
Чистые функции имеют ряд интересных и полезных свойств. Всё просто. Они зависят только от своих параметров. И возвращают только свой результат. В языке программирования D возможно следующее:
  • Нельзя писать в глобальную переменную. Или в переменную, которая стала персистентной вне этой функции.
  • Такие переменные не могут быть прочитаны, пока они не станут инвариантными. Под инвариантностью имеется в виду неизменяемость.
  • Чистые функции могут вызывать только чистые функции.
  • Параметры в чистых функциях могут быть неизменяемыми. При этом вызов к такой функции не может быть кеширован или вызван асинхронно, если параметры ссылаются на изменяемые данные.
  • Чистая функция может вызывать исключение.

Ниже приведён пример чистой функции в языке D:

pure int foo(int x, const int[] a)
{
int sum = 0;
foreach (s; a)
sum += s; // изменяемые нестатичные локальные переменные разрешены
return x * sum;
}


И не чистая функция:

int bar(int x, int[] a)
{ static int sum = 0;
foreach (s; a) // ссылка на изменяемый массив
sum += s; // чтение/запись в изменяемые персистентные переменные
return x * sum; // чтение изменяемой персистентной переменной
}


Если указано ключевое слово pure, то свойства такой чистой функции проверяются на этапе компиляции. Главный вопрос в том, если компилятор может проверить функцию на чистоту, то зачем её аннотировать соответствующим образом? Почему бы не перенести на плечи компилятора аннотирование функции чистой, если он видит что это так и есть? Суть в том, чтобы именно програмист знал, что функция такая и не забывал. Иначе возможна такая ситуация, когда он не сможет точно сказать, какова функция. И написать взависимости от этого соотвествующий код.

Отлично. У нас есть чистые функции. Что в этом хорошего?

Прежде всего это самодокументация. Человек, читающий код, сразу поймёт, что функция зависит только от своих параметров, не имеет побочных эффектов и ничего странного не происходит в ней, если она помечена чистой. Это значительно снижает объём ненужной работы. Большая часть функций может быть отмечены как чистые. И именно это уже положительно влияет на поддержку кода.

Чистым функциям не нужна синхронизация при использовании нескольких потоков. Потому что все их переменные или локальные, или неизменяемые.

Ниже приведён хороший пример, описывающий возможные оптимизации кода с использованием чистых функцией компилятором:

int x = foo(3) + bar[foo(3)];

потребуется только один вызов функции foo.

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

Чистые функции могут выполняться асинхронно. Это означает, что не просто код будет выполняться дальше, не дождавшись выполнения функции. Ну а также и то, что выполнение функции возможно будет выполняться на другом ядре процессора. Что становится особо актуальным в наши дни, когда никого не удивишь n-ядерными процессорами. И такая схема работы не может не сказаться положительно на производительности программы.

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

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

Переведено Андреенко Артёмом. Автор Волтер Брайт (соавтор языка D). Оригинальная версия статьи.
Tags:
Hubs:
+32
Comments 37
Comments Comments 37

Articles