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

Чистые функции имеют ряд интересных и полезных свойств. Всё просто. Они зависят только от своих параметров. И возвращают только свой результат. В языке программирования 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). Оригинальная версия статьи.
+32
23 сентября 2008, 20:09
4
miolini 4,7

комментарии (37)

–11
tr0nd #
а сферические кони в вакууме так и вообще идеальны…
0
kost_bebix #
Хотим еще про D! Больше и разного!
НЛО прилетело и опубликовало эту надпись здесь
+1
miolini #
pure
+4
xsubst #
Почему не оформите в виде «перевода»?
0
miolini #
Тип документа уже нельзя отредактировать. Следующий раз обязательно так и сделаю.
0
Stac #
Артём, если ты в Москве, посмотри анонс встречи программистов. Приходи рассказать про D.
НЛО прилетело и опубликовало эту надпись здесь
0
chiaroscuro #
Это в статье (переводе?) ошибочка.

Разумеется, если в D функция отмечена pure, то пользоваться глобальными переменными не получится. :)
НЛО прилетело и опубликовало эту надпись здесь
0
miolini #
А в чем именно ошибка?
0
miolini #
Получится, если они imutable.
НЛО прилетело и опубликовало эту надпись здесь
+1
miolini #
ну не любая, а, ещё раз повторюсь, imutable. что даёт уверенность в том, что её значение останется неизменным.
НЛО прилетело и опубликовало эту надпись здесь
0
shai_xylyd #
Да ладно, а если функции имеет дело с геометрией и активно используют число pi, то прикажете 3.14 забивать в тело каждой функции? Кажется общепринято порицать copy-paste.
НЛО прилетело и опубликовало эту надпись здесь
0
miolini #
Смотрите мой коммент ниже.
0
miolini #
Ну как всегда две стороны одной медали. Одно дело когда immutable переменные являются стойкими, и, например, относятся к константам из какой-либо науки. Другое дело, когда её объявил коллега. Допустим это BUFFER_SIZE. И захотел в какое-то время поставить ей слишком высокое занчание — получаем возможность «Out of memory». Или слишком низкое — получаем возможность падения производительности. Или, что ещё хуже, таймауты по пути следования данных.
НЛО прилетело и опубликовало эту надпись здесь
0
shai_xylyd #
О! Pi не зависит от постоянной Планка?
НЛО прилетело и опубликовало эту надпись здесь
0
shai_xylyd #
ħ = 4πRr2cm, где
  R — константа ридберга
  r — Боровский радиус
  c — скорость света
  m — масса электрона

?
НЛО прилетело и опубликовало эту надпись здесь
–1
oleganza #
набашорг =)
0
korynd #
Число Пи — отношение длины окружности к её радиусу, поверьте, оно не зависит от постоянной Планка.
0
miolini #
Глобальные переменные можно использовать, если они imutable.
+1
Terentich #
Интересный теоритический материал. Заинтересовал данный язык, однако информации немного по нему. Буду рад, если вы, а также другие хабрапользователи, будете снабжать читателей новыми статьями. ;)
0
miolini #
Да беспроблем.
+1
FlasteR #
Автор, исправь ошибку в самом начале поста:
0
miolini #
Какую?
+1
FlasteR #
Чистые функии
Надо: Чистые функиЦи
0
miolini #
И как её не заметил ( Спасибо!
0
FlasteR #
Точнее Чистые функЦии (сам ошибся указывая на чужую ошибку)
0
miolini #
)
0
Throwable #
Не совсем понимаю как в первая функция запрещает изменение содержимого передаваемого массива. То есть const int[] a предохраняет изменение массива (или только ссылки на него?) в контексте функции, но не за ее пределами. Поэтому при асинхронном выполнений возникнет несоответствие «concurrent array modification».

Вообще передача ссылочных типов в «чистые» функции нарушает их изолированность от контекста выполнения. Единственный вариант сохранения изоляции — это делать глубокие unmodified копии передаваемых объектов. Иначе, для пущей чистоты, придется запретить ссылки.
0
korynd #
Могут быть чистые методы? Или достаточно const-методов?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.