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

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

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

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

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


person Magnus    schedule 04.03.2019    source источник
comment
Ваше помещение плохо определено. Ввод слишком широкий. Можно подумать, что функции имеют два типа ввода. Их аргументы и экологические/контекстуальные. Функцию, возвращающую системное время, можно считать чистой (даже если это не так), если вы не различаете эти два вида ввода.   -  person Alexander    schedule 05.03.2019
comment
@Alexander: В контексте чистой функции под вводом обычно понимаются параметры/аргументы, которые передаются явно (с помощью любого механизма, используемого языком программирования). Это часть определения чистой функции. Но вы правы, важно помнить об определении.   -  person sleske    schedule 05.03.2019
comment
Тривиальный контрпример: вернуть значение глобальной переменной. Никаких побочных эффектов (глобальный только когда-либо читается!), но каждый раз потенциально разные результаты. (Если вам не нравятся глобальные переменные, верните адрес локальной переменной, которая зависит от стека вызовов во время выполнения).   -  person Peter - Reinstate Monica    schedule 05.03.2019
comment
Вам нужно расширить определение побочных эффектов; вы говорите, что чистый метод не производит побочных эффектов, но вы должны также отметить, что чистый метод не использует побочные эффекты, произведенные где-либо еще.   -  person Eric Lippert    schedule 05.03.2019
comment
@sleske Возможно, это общеизвестно, но отсутствие этого различия является точной причиной путаницы OP.   -  person Alexander    schedule 05.03.2019
comment
@EricLippert OP просто цитирует, например. критериям википедии (у меня возникает соблазн назвать функцию, которая обменивается данными через сторонние каналы, функцией дырявого кишечника .) Именно критерий 2 подразумевает отсутствие потребления какого-либо (изменяемого) внешнего состояния. Я бы подумал, что эти два утверждения эквивалентны.   -  person Peter - Reinstate Monica    schedule 05.03.2019


Ответы (6)


Вот несколько контрпримеров, которые не меняют внешнюю область видимости, но по-прежнему считаются нечистыми:

  • function a() { return Date.now(); }
  • function b() { return window.globalMutableVar; }
  • function c() { return document.getElementById("myInput").value; }
  • function d() { return Math.random(); } (что, по общему признанию, изменяет PRNG, но не считается наблюдаемым)

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

Я всегда считаю два условия чистоты взаимодополняющими:

  • оценка результата не должна влиять на стороннее состояние
  • на результат оценки не должно влиять дополнительное состояние

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

person Bergi    schedule 04.03.2019
comment
Спасибо Берги. По какой-то причине я думал, что побочные эффекты включают чтение переменных вне локальной области видимости, но я думаю, что это только побочный эффект, если он записывает такие внешние переменные. - person Magnus; 05.03.2019
comment
Обратите внимание, что эти два условия на самом деле логически независимы: этот ответ показывает, что 1. не влечет 2., но также есть много контрпримеров, что 2. не влечет 1. — например, функция setDate(newDate), которая устанавливает текущую дату компьютера на newDate имеет свойство 2. (возвращаемое значение может быть newDate или ничего), но все равно нарушает 1. (устанавливает текущую дату). - person sleske; 05.03.2019
comment
Если prompt("you choose") не имеет побочных эффектов, мы должны сделать шаг назад и уточнить значение побочных эффектов. - person Holger; 05.03.2019
comment
@Holger Хорошо, это немного натянуто, возможно, даже больше, чем Math.random(), но, учитывая обычную модель вычислений JavaScript, это не меняет наблюдаемую среду - это не нарушает определение OP для побочных эффектов (т.е. только разрешены изменения в локальной области). Но вы правы, я изменю его на что-то менее спорное. - person Bergi; 05.03.2019
comment
@Bergi Извините, я забыл вопросительный знак в конце моего комментария выше. Верно ли, что побочные эффекты только включают запись во внешние переменные, а не чтение указанных переменных? - person Magnus; 05.03.2019
comment
@Magnus Да, именно это и означает effect. Постараюсь уточнить и в своем ответе, я не ожидал такого большого внимания и хочу сделать ответ достойным десятков голосов :-) - person Bergi; 05.03.2019
comment
Или чтение с жесткого диска, или просмотр веб-сайта, или чтение данных с датчика... - person Acccumulation; 05.03.2019
comment
Не уверен, что согласен с терминологией о побочных эффектах. Из уроков CS я всегда думал, что преднамеренно широкий термин «побочные эффекты» использовался специально для того, чтобы включить такие случаи, как во втором пункте. Запись в глобальную переменную является явной, поэтому это не побочный эффект (а прямой). Влияние на что-то вроде позиции чтения потока, кажется, идеально подходит для побочного эффекта для меня. Это не конкретное предполагаемое последствие, но тем не менее происходит. - person StayOnTarget; 06.03.2019
comment
@DaveInCaz Я уверен, что этот термин имеет довольно специфическое значение при обсуждении ссылочной прозрачности в функциональном программировании. Но есть и более общее использование со значением непреднамеренных или скрытых эффектов, о которых вы, кажется, узнали. - person Bergi; 06.03.2019
comment
Ну, насколько вы знаете, Math.random() возвращает термодиод. На самом деле не указано использовать плохой ГСЧ. - person Joshua; 06.03.2019
comment
@Joshua Даже это, вероятно, было бы обернуто достаточной абстракцией ОС, в которой чтение изменяет некоторое внутреннее состояние :-) - person Bergi; 06.03.2019
comment
@Bergi: тогда подумайте о чистоте функции, написанной на ассемблере x86, которая использует rdrand машинную инструкцию чтобы получить аппаратную случайность. Внутренняя реализация проходит некоторую обработку, но инструкции x86 не могут получить доступ к этому внутреннему состоянию. На архитектурном уровне инструкция производит истинную случайность каждый раз, когда она выполняется в пользовательском пространстве без вмешательства ОС. Вы не можете с пользой сказать, что вы взяли значение, которое получила бы какая-то другая функция или программа. Но вы не должны оптимизировать несколько вызовов. - person Peter Cordes; 06.03.2019
comment
@PeterCordes Хорошо, я думал о каком-то языке более высокого уровня, чем ассемблер - я не знаю, какую модель использовать для анализа чистоты этого. Вы, наверное, правы. (С другой стороны, описание Флаг переноса указывает, доступно ли случайное значение во время выполнения инструкции. подразумевает наличие некоторого состояния даже в аппаратном генераторе). - person Bergi; 06.03.2019
comment
@Bergi: рассмотрите реализацию в IvyBridge, где rdrand никогда не дает сбоев, если только HW не обнаружит, что истинный RNG не работает. API оставляет дверь открытой для реализаций, которые возвращают ошибку вместо того, чтобы останавливаться, на случай, если они не могут справиться с возможным количеством ядер, вытягивающих случайность, но IvB может не отставать, согласно ответу SO от архитектора Intel, который разработал это: Каковы характеристики исчерпания RDRAND на Ivy Bridge?. Или рассмотрим гипотетический истинный ГСЧ (со скрытой обусловленностью, если таковая имеется). - person Peter Cordes; 07.03.2019
comment
Я предполагаю, что с точки зрения чистоты это эквивалентно входу любого внешнего датчика, измеряющего любую физическую величину. (В отличие от чтения входного потока, такого как клавиатура или файл, потому что доступно бесконечное количество данных, ограниченное только тем, как часто вы их сэмплируете.) - person Peter Cordes; 07.03.2019
comment
и если генератор случайных чисел не очень случайный, у вас будет нечистая функция, которая возвращает один и тот же результат для одного и того же ввода () - person RandomB; 08.03.2019
comment
@Bergi Я нахожу эту тему такой загадочной. 1) этот вопрос задавали бесчисленное количество раз на SO, 2) он не по теме, 3) он совершенно новый, но имеет более 70 голосов. Что такого горячего в этом вопросе на этот раз? - person Mulan; 21.03.2019
comment
@user633183 user633183 Думаю, это было опубликовано в теге JS, что привело к большому количеству зрителей и нескольким отзывам, что привело к тому, что оно появилось в горячих сетевых вопросах, откуда оно получило еще больше зрителей и голосов. Кстати, я не думаю, что это не по теме. И если вы можете найти предыдущие экземпляры, пожалуйста, прокомментируйте сам вопрос - если вы нашли точный дубликат, я проголосую за закрытие. - person Bergi; 21.03.2019
comment
Я слышал, что из двух условий первое называется следствием, а второе называется коэффектом. Оба являются побочными эффектами и нечистыми. f(coeffects, input) -> Effects, Output Коэффекты — это входные данные, возникающие в результате изменений в более широкой среде, а выходные эффекты — это изменения, которые изменяют более широкую среду. Например, Elm и Clojurescrips переформулируют работу с этой моделью. - person ; 26.07.2019
comment
@Dan Спасибо за упоминание этого термина. Однако я не думаю, что коэффекты являются побочными эффектами в большинстве определений. - person Bergi; 26.07.2019

«Нормальный» способ сформулировать, что такое чистая функция, заключается в терминах прозрачности ссылок. Функция называется чистой, если она прозрачна по ссылкам.

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

Так, например, если бы printf языка C был ссылочно прозрачным, эти две программы должны иметь одно и то же значение:

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. Однако мы также можем посмотреть на функцию и увидеть, что она является ссылочно прозрачной и, следовательно, чистой. Иногда это называется внешняя чистота, то есть функция, которая кажется чистой для внешнего мира, но реализуется нечистой внутри.

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

person Jörg W Mittag    schedule 05.03.2019
comment
Эта примесь влияет на всю программу, когда у вас есть параллелизм. - person R.. GitHub STOP HELPING ICE; 05.03.2019
comment
@R .. Можете ли вы придумать, как параллелизм может сделать описанную функцию Фибоначчи внешне нечистой? Я не могу. Запись в memo[n] является идемпотентной, и невозможность чтения из него просто тратит впустую циклы процессора. - person Brilliand; 06.03.2019
comment
Я согласен с вами обоими. Нечистота может привести к проблемам параллелизма, но не в данном конкретном случае. - person Jörg W Mittag; 06.03.2019
comment
@R.. Нетрудно представить версию с поддержкой параллелизма. - person user253751; 06.03.2019
comment
@Brilliand Например, memo[n] = ... может сначала создать запись в словаре, а затем сохранить в ней значение. Это оставляет окно, в течение которого другой поток может увидеть неинициализированную запись. - person user253751; 06.03.2019
comment
@immibis И что потом? Другой поток снова выполняет точно такие же вычисления и получает точно такой же результат. Это не имеет никакого значения. Отсутствие внешней примеси. Как я уже говорил: невозможность чтения из него просто тратит впустую циклы процессора. - person Brilliand; 07.03.2019
comment
@Brilliand Нет, другой поток видит, что запись существует, и считывает из нее значение, которое представляет собой случайное число. - person user253751; 07.03.2019
comment
@immibis А, я понимаю. Да, это может сработать, в зависимости от библиотеки словарей. - person Brilliand; 07.03.2019
comment
Хорошо, у вас есть функция, которая считывает текущий год (из системных часов/календаря) и возвращает 1, если он › 0, иначе 0. Очевидно, что эта функция имеет ссылочную прозрачность. Но он нечист и будет жить под монадой IO. - person RandomB; 08.03.2019
comment
@Paul-AG: Нет, эта функция непрозрачна с точки зрения ссылок. Ссылочная прозрачность означает, что вы можете заменить вызов функции результатом этого вызова без изменения смысла программы. Но, каким бы результатом вы ни заменили вызов, 0 или 1, вы измените значение программы как минимум для запусков, а именно, когда вы замените его на 1, вы измените значение для запусков программы с часами, установленными на ДО Н.Э. - person Jörg W Mittag; 12.08.2019

Мне кажется, что второе условие, которое вы описали, является более слабым ограничением, чем первое.

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

function addOneAndLog(x) {
  console.log(x);
  return x + 1;
}

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

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

Предположим, у нас есть функция, которая просто добавляет:

function addOne(x) {
  return x + 1;
}

Мы можем заменить addOne(5) на 6 в любом месте нашей программы, и ничего не изменится.

Напротив, мы не можем заменить addOneAndLog(x) значением 6 в любом месте нашей программы без изменения поведения, потому что первое выражение приводит к тому, что что-то записывается в консоль, а второе - нет.

Мы рассматриваем любое из этих дополнительных действий, которые выполняет addOneAndLog(x) помимо возврата вывода, как побочный эффект.

person TheInnerLight    schedule 04.03.2019
comment
Мне кажется, что второе условие, которое вы описали, является более слабым ограничением, чем первое. Нет, эти два условия логически независимы. - person sleske; 05.03.2019
comment
@sleske ты ошибаешься. Я дал четкие определения терминам «чистый» и «побочный эффект». В рамках этих ограничений нет ничего, что функция не имеет побочных эффектов, кроме того, что она возвращает один и тот же результат для заданного ввода. Однако я привел примеры, когда второе условие может быть выполнено без первого. Фундаментальной концепцией для понимания понятия чистоты является референтная прозрачность. - person TheInnerLight; 05.03.2019
comment
Небольшая опечатка: функция без побочных эффектов не может сделать ничего, кроме возврата одного и того же вывода для заданного ввода. - person TheInnerLight; 05.03.2019
comment
Как насчет возврата текущего времени? Это не имеет побочных эффектов, но возвращает другой результат для одного и того же ввода. Или, вообще говоря, любая функция, результат которой зависит не только от входных параметров, но и от (изменяемой) глобальной переменной. - person sleske; 05.03.2019
comment
@sleske Возврат текущего времени - абсолютно побочный эффект. Вы не можете заменить 'Date.now()' своим результатом в любом месте вашей программы без изменения поведения, поэтому он не является ссылочно прозрачным и, следовательно, не чистым. - person TheInnerLight; 05.03.2019
comment
Кажется, вы используете другое определение побочного эффекта, чем то, которое обычно используется. Побочный эффект обычно определяется как наблюдаемый эффект помимо возврата значения или наблюдаемого изменения состояния - см., например. Википедия , этот пост на softwareengineering.SE. Вы совершенно правы в том, что Date.now() не является чистым/референтно прозрачным, но не потому, что у него есть побочные эффекты, а потому, что его результат зависит не только от его ввода. - person sleske; 05.03.2019

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

Во всяком случае, все, что я могу придумать.

person user3340459    schedule 04.03.2019
comment
По моему мнению, эти случайности из-за пределов системы являются формой побочного эффекта. Функции с таким поведением не являются чистыми. - person Joseph M. Dion; 05.03.2019

Проблема с определениями FP заключается в том, что они очень искусственны. Каждая оценка/вычисление имеет побочные эффекты для оценщика. Это теоретически верно. Отрицание этого показывает лишь то, что апологеты ФП игнорируют философию и логику: под «оценкой» понимается изменение состояния некоторой разумной среды (машины, мозга и т.п.). Такова природа процесса оценки. Нет изменений - нет "исчислений". Эффект может быть очень заметным: нагрев процессора или его выход из строя, отключение материнской платы при перегреве и так далее.

Говоря о ссылочной прозрачности, следует понимать, что информация о такой прозрачности доступна человеку как создателю всей системы и носителю семантической информации и может быть недоступна для компилятора. Например, функция может прочитать какой-то внешний ресурс, и у нее в сигнатуре будет монада IO, но она все время будет возвращать одно и то же значение (например, результат current_year > 0). Компилятор не знает, что функция всегда будет возвращать один и тот же результат, поэтому функция нечиста, но обладает свойством ссылочной прозрачности и может быть заменена константой True.

Итак, чтобы избежать такой неточности, мы должны различать математические функции и «функции» в языках программирования. Функции в Haskell всегда нечисты, и определение чистоты, относящееся к ним, всегда очень условно: они работают на реальном оборудовании с реальными побочными эффектами и физическими свойствами, что неверно для математических функций. Это означает, что пример с функцией "printf" совершенно некорректен.

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

person RandomB    schedule 08.03.2019

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

Да

Рассмотрим простой фрагмент кода ниже

public int Sum(int a, int b) {
    Random rnd = new Random();
    return rnd.Next(1, 10);
}

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

Общий эффект обоих пунктов №1 и №2, которые вы упомянули, при объединении означает: В любой момент времени, если функция Sum с тем же i/p заменяется ее результатом в программе, общий смысл программы не меняется. изменить. Это не что иное, как прозрачность ссылок.

person rahulaga_dev    schedule 05.03.2019
comment
Но в данном случае не проверяется первое условие: запись в консоль считается побочным эффектом, так как она изменяет состояние самой машины. - person Right leg; 05.03.2019
comment
@Rightleg спасибо за указание на это. Как-то я неправильно понял ОП совершенно по-другому. исправлен ответ. - person rahulaga_dev; 05.03.2019
comment
Разве это не меняет состояние генератора случайных чисел? - person Eric Duminil; 05.03.2019
comment
@EricDuminil прав. rnd.Next() вызывает побочный эффект в объекте rnd. docs.microsoft.com/en- нас/dotnet/api/. Поэтому пример не чистый - person JDurstberger; 05.03.2019
comment
Генерация случайного числа сама по себе является побочным эффектом, если только состояние генератора случайных чисел не указано явно, что заставило бы функцию удовлетворять условию 2. - person TheInnerLight; 05.03.2019
comment
rnd не выходит из функции, поэтому тот факт, что его состояние изменяется, не имеет значения для чистоты функции, но тот факт, что конструктор Random использует текущее время в качестве начального значения, означает, что есть входные данные, отличные от a и b. - person Sneftel; 05.03.2019
comment
Я предположил, что речь идет об наблюдаемых побочных эффектах. Если нет, то есть длинный список побочных эффектов. - person rahulaga_dev; 06.03.2019