Чистые функции: подразумевается ли "без побочных эффектов" "всегда один и тот же вывод при одинаковом вводе"?
Два условия, которые определяют функцию как pure
:
- Никаких побочных эффектов (т.е. допускаются только изменения в локальной области)
- Всегда возвращать один и тот же вывод, учитывая один и тот же ввод
Если первое условие всегда верно, есть ли случаи, когда второе условие не верно?
Т.е. действительно ли это необходимо только при первом условии?
Ответы
Ответ 1
Вот несколько контрпримеров, которые не изменяют внешнюю область, но все еще считаются нечистыми:
-
function a() { return Date.now(); }
-
function b() { return window.globalMutableVar; }
-
function c() { return document.getElementById("myInput").value; }
-
function d() { return Math.random(); }
function d() { return Math.random(); }
(который по общему признанию изменяет PRNG, но не считается наблюдаемым)
Доступ к непостоянным нелокальным переменным достаточен, чтобы иметь возможность нарушить второе условие.
Я всегда считаю два условия чистоты взаимодополняющими:
- оценка результата не должна влиять на состояние стороны
- результат оценки не должен зависеть от состояния стороны
Термин " побочный эффект" относится только к первому, функции, изменяющей нелокальное состояние. Однако иногда операции чтения также рассматриваются как побочные эффекты: когда они являются операциями и также включают запись, даже если их основная цель - получить доступ к значению. Примерами этого являются генерация псевдослучайного числа, которое изменяет внутреннее состояние генератора, считывание из входного потока, который продвигает позицию считывания, или считывание с внешнего датчика, который включает команду "выполнить измерение".
Ответ 2
"Нормальный" способ выразить, что такое чистая функция, заключается в ссылочной прозрачности. Функция является чистой, если она является ссылочно прозрачной.
Относительная прозрачность, примерно, означает, что вы можете заменить вызов функции его возвращаемым значением или наоборот в любой точке программы, не меняя смысла программы.
Так, например, если C printf
был ссылочно прозрачным, эти две программы должны иметь одинаковое значение:
printf("Hello");
а также
5;
и все следующие программы должны иметь одинаковое значение:
5 + 5;
printf("Hello") + 5;
printf("Hello") + printf("Hello");
Потому что printf
возвращает количество написанных символов, в данном случае 5.
Это становится еще более очевидным с void
функциями. Если у меня есть функция void foo
, то
foo(bar, baz, quux);
должен быть таким же, как
;
Т.е. поскольку foo
ничего не возвращает, я должен иметь возможность заменить его ничем, не меняя смысла программы.
Понятно, что ни printf
ни foo
являются ссылочно прозрачными, и поэтому ни один из них не является чистым. На самом деле, функция void
никогда не может быть прозрачной по ссылкам, если она не запрещена.
Я считаю, что с этим определением гораздо легче справиться, чем с тем, которое вы дали. Он также позволяет вам применять его с любой степенью детализации: вы можете применять его к отдельным выражениям, к функциям, ко всем программам. Это позволяет вам, например, говорить о такой функции:
func fib(n):
return memo[n] if memo.has_key?(n)
return 1 if n <= 1
return memo[n] = fib(n-1) + fib(n-2)
Мы можем проанализировать выражения, составляющие функцию, и легко сделать вывод, что они не являются ссылочно прозрачными и, следовательно, не чистыми, поскольку они используют изменяемую структуру данных, а именно массив memo
. Тем не менее, мы также можем посмотреть на функцию и увидеть, что она прозрачна по ссылкам и, следовательно, чиста. Это иногда называют внешней чистотой, то есть функцией, которая кажется чистой для внешнего мира, но реализована нечистой внутри.
Такие функции по-прежнему полезны, потому что, хотя примеси заражают все вокруг, внешний чистый интерфейс создает своего рода "барьер чистоты", когда примеси заражают только три строки функции, но не проникают в остальную часть программы., Эти три строки гораздо проще проанализировать на правильность, чем всю программу.
Ответ 3
Мне кажется, что второе условие, которое вы описали, является более слабым, чем первое.
Позвольте мне привести вам пример. Предположим, у вас есть функция для добавления функции, которая также ведет журнал в консоли:
function addOneAndLog(x) {
console.log(x);
return x + 1;
}
Второе условие, которое вы указали, выполнено: эта функция всегда возвращает один и тот же вывод, если дан один и тот же ввод. Это, однако, не чисто функция, потому что она включает в себя побочный эффект входа в консоль.
Строго говоря, чистая функция - это функция, которая удовлетворяет свойству ссылочной прозрачности. Это свойство, которое мы можем заменить приложением-функцией значением, которое оно производит, не изменяя поведения программы.
Предположим, у нас есть функция, которая просто добавляет:
function addOne(x) {
return x + 1;
}
Мы можем заменить addOne(5)
на 6
любом месте нашей программы, и ничего не изменится.
В отличие от этого, мы не можем заменить addOneAndLog(x)
значением 6
любом месте нашей программы без изменения поведения, поскольку первое выражение приводит к записи чего-либо в консоль, а второе - нет.
Мы рассматриваем любое из этого дополнительного поведения, которое addOneAndLog(x)
кроме возврата результата как побочного эффекта.
Ответ 4
Там может быть источник случайности извне системы. Предположим, что часть вашего расчета включает комнатную температуру. Затем выполнение функции будет каждый раз давать разные результаты в зависимости от случайного внешнего элемента комнатной температуры. Состояние не изменяется при выполнении программы.
Во всяком случае, все, что я могу придумать.
Ответ 5
Проблема с определениями FP в том, что они очень искусственные. Каждая оценка/расчет имеет побочные эффекты для оценщика. Это теоретически верно. Отрицание этого показывает только то, что апологеты ФП игнорируют философию и логику: "оценка" означает изменение состояния некоторой интеллектуальной среды (машины, мозга и т.д.). Это характер процесса оценки. Без изменений - без "исчислений". Эффект может быть очень заметен: нагрев процессора или его неисправность, выключение материнской платы в случае перегрева и т.д.
Когда вы говорите о ссылочной прозрачности, вы должны понимать, что информация о такой прозрачности доступна для человека как создателя всей системы и держателя семантической информации и может быть недоступна для компилятора. Например, функция может читать некоторый внешний ресурс, и в ее сигнатуре будет монада ввода-вывода, но она будет постоянно возвращать одно и то же значение (например, результат current_year > 0
). Компилятор не знает, что функция всегда будет возвращать один и тот же результат, поэтому функция нечиста, но имеет свойство прозрачности по ссылкам и может быть заменена константой True
.
Итак, чтобы избежать такой неточности, мы должны различать математические функции и "функции" в языках программирования. Функции в Haskell всегда нечисты, и определение чистоты, связанной с ними, всегда очень условно: они работают на реальном оборудовании с реальными побочными эффектами и физическими свойствами, что неправильно для математических функций. Это означает, что пример с функцией "printf" совершенно неверен.
Но не все математические функции также чисты: каждая функция, у которой t
(время) в качестве параметра может быть нечистой: t
содержит все эффекты и стохастическую природу функции: в общем случае у вас есть входной сигнал и вы не имеете представления о фактических значениях, это может быть даже шум.
Ответ 6
Если первое условие всегда верно, есть ли случаи, когда второе условие не верно?
да
Рассмотрим простой фрагмент кода ниже
public int Sum(int a, int b) {
Random rnd = new Random();
return rnd.Next(1, 10);
}
Этот код будет возвращать случайный вывод для того же заданного набора входов - однако он не имеет никакого побочного эффекта.
Общий эффект обеих точек # 1 и # 2, упомянутых вами при объединении, означает: в любой момент времени, если функция Sum
с тем же i/p заменяется результатом в программе, общее значение программы не меняется. Это не что иное, как ссылочная прозрачность.