Преимущества чистой функции

82

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

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

например strlen(), это чистая функция, а rand()нечистая.

http://ideone.com/33XJU

Вышеупомянутая программа ведет себя так же, как и при отсутствии pureобъявления.

Каковы преимущества объявления функции как pure[если нет изменений в выходе]?

Зеленый Гоблин
источник
7
Да - посмотрите сгенерированную сборку.
Филип Кендалл
4
Я не думаю, что это определение чистоты является правильным - printfнапример, оно подходит (вызов его дважды с одними и теми же аргументами дает одно и то же возвращаемое значение), но оно не является чистым.
tdammers
14
@tdammers: Действительно, ему не хватает роли ...and no side-effects....
Frerich Raabe
2
@Ben: откуда берется энтропия? Здесь мы имеем дело с (теоретически) детерминированными машинами, единственный способ получить в них истинную энтропию - из внешних источников, что означает побочные эффекты. Конечно, мы могли бы позволить языкам программирования определять недетерминированные функции, делая вид, что технических побочных эффектов нет, а функции действительно недетерминированы; но если мы это сделаем, большая часть практических преимуществ отслеживания чистоты будет потеряна.
tdammers
3
tdammers правильный - определение pure, данное выше, неверно. Чистый означает, что вывод зависит только от входов функции; также не должно быть заметных побочных эффектов. «Одинаковый результат для одного и того же входа» - это очень неточное описание этих требований. en.wikipedia.org/wiki/Pure_function
Dancrumb

Ответы:

145

pure позволяет компилятору знать, что он может сделать определенную оптимизацию функции: представьте себе небольшой код вроде

С чистой функцией компилятор может знать, что ему нужно выполнить оценку fun(10)только один раз, а не 1000 раз. Для сложной функции это большой выигрыш.

Филип Кендалл
источник
То
@mob Что ты имеешь в виду? Почему бы и нет?
Конрад Рудольф
15
Потому что вы можете изменить строку (последовательность символов, начинающуюся с некоторого адреса), не изменяя ввод (указатель на адрес, с которого начинается строка), то есть вы не можете запоминать ее. Это была бы только чистая функция на языке с неизменяемыми строками (скажем, Java).
моб
5
@KonradRudolph: Представьте себе строку длиной 1000. Позвони strlenему. Тогда снова. То же самое, да? Теперь измените второй символ как \0. Все strlenеще возвращает 1000 сейчас? Начальный адрес такой же (== ввод такой же), но функция теперь возвращает другое значение.
Майк Бейли,
5
@mob Хорошее возражение, очевидно, вы правы. Меня ввел в заблуждение тот факт, что даже книги утверждают, что strlen(в GCC / glibc) на самом деле чистый. Но взгляд на реализацию glibc показал, что это неверно.
Конрад Рудольф
34

Когда вы говорите, что функция «чистая», вы гарантируете, что она не имеет видимых извне побочных эффектов (и, как говорится в комментарии, если вы солгаете, могут произойти неприятности). Знание того, что функция «чистая» имеет преимущества для компилятора, который может использовать это знание для выполнения определенных оптимизаций.

Вот что говорится об атрибуте в документации GCCpure :

чистый

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

Ответ Филиппа уже показывает, как знание того, что функция «чиста», может помочь в оптимизации цикла.

Вот одно для общего исключения подвыражения (дано fooчисто):

Может стать:

АрджунШанкар
источник
3
Я не уверен, что это делают, но чистые функции также позволяют компилятору переупорядочивать при вызове функции, если он сочтет это полезным. Когда есть вероятность побочных эффектов, компилятор должен быть более консервативным.
mpdonadio
@MPD - Да, звучит разумно. А поскольку callинструкция является узким местом для суперскалярных процессоров, некоторая помощь компилятора может помочь.
ArjunShankar
Я смутно вспоминаю, как несколько лет назад использовал компилятор DSP, который использовал бы эту технику для получения возвращаемых значений раньше / позже. Это позволило свести к минимуму срыв трубопровода.
mpdonadio
1
Можно ли предварительно вычислить «foo (99)», поскольку 99 является константой, а foo всегда будет возвращать один и тот же результат? Может, в какой-нибудь двухэтапной компиляции?
markwatson
1
@markwatson - я не уверен. Бывают случаи, когда это просто невозможно. например, if fooявляется частью другого модуля компиляции (другого файла C) или находится в предварительно скомпилированной библиотеке. В обоих случаях компилятор не знает, что fooделает, и не может предварительно вычислить.
ArjunShankar
29

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

Фрерих Раабе
источник
3
+1, ваш тезис о тестировании интересен. Не требуется установка и демонтаж.
ArjunShankar
15

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

похожа на продолжение чистой функции

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

Должно быть ясно, почему проще рассуждать о чистой функции, чем о нечистой.

Джорджио
источник
1
+1: Использование вселенной в качестве потенциального входа - очень хороший способ объяснить разницу между чистым и нечистым.
ArjunShankar
действительно, это идея монад.
Кристофер Мицински
7

В качестве дополнения я хотел бы упомянуть, что C ++ 11 несколько кодирует вещи с помощью ключевого слова constexpr. Пример:

Ограничения на использование constexpr делают эту функцию доказуемо чистой. Таким образом, компилятор может более агрессивно оптимизировать (просто убедитесь, что вы используете хвостовую рекурсию, пожалуйста!) И оценивать функцию во время компиляции, а не во время выполнения.

Итак, чтобы ответить на ваш вопрос, заключается в том, что если вы используете C ++ (я знаю, что вы сказали C, но они связаны), написание чистой функции в правильном стиле позволяет компилятору делать с функцией всевозможные классные вещи: -)

Роберт Мейсон
источник
4

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

Кеширование

Допустим, у вас есть чистая функция f которая вызывается 100000 раз, поскольку она детерминирована и зависит только от ее параметров, компилятор может вычислить ее значение один раз и использовать при необходимости.

Параллелизм

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

Передача по ссылке

Функция f(struct t)получает свой аргумент tпо значению, и, с другой стороны, компилятор может передать tпо ссылке, fесли она объявлена ​​как чистая, при этом гарантируя, что значение tне изменится и даст прирост производительности


В дополнение к соображениям времени компиляции, чистые функции можно довольно легко протестировать: просто вызовите их.

Не нужно создавать объекты или имитировать подключения к БД / файловой системе.

Ури Горен
источник