Чистые функции: подразумевается ли "без побочных эффектов" "всегда один и тот же вывод при одинаковом вводе"?

Два условия, которые определяют функцию как pure:

  1. Никаких побочных эффектов (т.е. допускаются только изменения в локальной области)
  2. Всегда возвращать один и тот же вывод, учитывая один и тот же ввод

Если первое условие всегда верно, есть ли случаи, когда второе условие не верно?

Т.е. действительно ли это необходимо только при первом условии?

Ответы

Ответ 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 заменяется результатом в программе, общее значение программы не меняется. Это не что иное, как ссылочная прозрачность.