Как вы называете функцию, когда один и тот же вход всегда будет возвращать один и тот же выход, но также имеет побочные эффекты?

43

Скажем, у нас есть нормальная чистая функция, такая как

function add(a, b) {
  return a + b
}

И тогда мы изменим его так, что он имеет побочный эффект

function add(a, b) {
  writeToDatabase(Math.random())
  return a + b;
}

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

Есть ли другое название для этого типа функции, оно безымянное или все еще на самом деле чистое, и я ошибаюсь в определении чистоты?

m0meni
источник
91
«Не чистая функция».
Росс Паттерсон
2
@RossPatterson я тоже так думал, но, спросив, я узнал о прозрачности ссылок, так что я рад, что не оставил это при себе.
m0meni
9
Если происходит writeToDatabaseсбой, это может вызвать исключение, в результате чего ваша вторая addфункция иногда создает исключение, даже если она вызывается с теми же аргументами, что раньше не имели проблем ... в большинстве случаев наличие побочных эффектов приводит к возникновению такого рода связанных с ошибкой условий, «чистота ввода-вывода».
Бакуриу
25
То, что всегда дает один и тот же вывод для данного ввода, называется детерминированным .
njzk2
2
@ njzk2: правда, но это также без гражданства . С сохранением состояния детерминированной функции может не дать тот же выход для каждого входа. Пример: F(x)определяется для возврата, trueесли он вызывается с тем же аргументом, что и предыдущий вызов. Очевидно, что с последовательностью {1,2,2} => {undefined, false, true}это является детерминированным, но это дает разные результаты для F(2).
MSalters

Ответы:

85

Я не уверен в универсальных определениях чистоты, но с точки зрения Haskell (языка, где программисты, как правило, заботятся о таких вещах, как чистота и ссылочная прозрачность), только первая из ваших функций является «чистой». Вторая версия addне чиста . Так что в ответ на ваш вопрос я бы назвал это "нечистым";)

Согласно этому определению, чистая функция - это функция, которая:

  1. Только зависит от его ввода. То есть при одинаковом вводе он всегда будет возвращать одинаковый вывод.
  2. Референтно прозрачен: функция может быть свободно заменена на ее значение, и «поведение» программы не изменится.

С этим определением ясно, что ваша вторая функция не может считаться чистой, поскольку она нарушает правило 2. То есть следующие две программы НЕ эквивалентны:

function f(a, b) { 
    return add(a, b) + add(a, b);
}

а также

function g(a, b) {
    c = add(a, b);
    return c + c;
}

Это потому, что, хотя обе функции будут возвращать одно и то же значение, функция fбудет писать в базу данных дважды, но gбудет писать один раз! Весьма вероятно, что записи в базу данных являются частью наблюдаемого поведения вашей программы, и в этом случае я показал, что ваша вторая версия addне "чистая".

Если записи в базу данных не являются наблюдаемой частью поведения вашей программы, то обе версии addможно считать эквивалентными и чистыми. Но я не могу придумать сценарий, когда запись в базу данных не имеет значения. Даже ведение журнала имеет значение!

Андрес Ф.
источник
1
Разве «зависит только от его входных данных» избыточно, учитывая ссылочную прозрачность? Что подразумевает, что RT является синонимом чистоты? (Я запутываюсь из-за этого, чем больше источников я ищу)
Ixrec,
Я согласен, это сбивает с толку. Я могу думать только о надуманных примерах. Скажем, f(x)зависит не только от x, но и от некоторой внешней глобальной переменной y. Затем, если fимеет свойство RT, вы можете свободно поменять местами все его вхождения с возвращаемым значением, пока вы не коснетесь y . Да, мой пример сомнителен. Но важная вещь: если fзапись в базу данных (или запись в журнал) теряет свойство RT: теперь не имеет значения, оставляете ли вы глобальный без изменений y, вы знаете, что значение вашей программы меняется в зависимости от того, действительно ли вы позвоните fили просто используйте его возвращаемое значение.
Андрес Ф.
Гм. Допустим, у нас есть такая функция, которая является чистой, за исключением побочных эффектов, и гарантированно будет иметь такое поведение, когда ваши две выборки эквивалентны. (На самом деле это дело было выдвинуто, поэтому оно не является гипотетическим.) Думаю, мы еще не закончили.
Джошуа
2
Я бы сказал, что вторая функция может нарушить правило № 1. В зависимости от языка и практики обработки ошибок используемого API базы данных, функция может вообще ничего не возвращать, если база данных недоступна или запись по БД по какой-то причине не удалась.
Зак Липтон
1
Поскольку Haskell был упомянут: в Haskell добавление подобного побочного эффекта требует изменения сигнатуры функции . (подумайте об этом, как о предоставлении исходной базы данных в качестве дополнительного ввода и получении модифицированной базы данных в качестве дополнительного возвращаемого значения функции). Таким образом, вполне возможно смоделировать побочные эффекты в системе типов таким элегантным способом, просто сегодняшние основные языки недостаточно заботятся о побочных эффектах и ​​чистоте, чтобы сделать это.
ComicSansMS
19

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

Такая функция называется

детерминистический

Алгоритм, поведение которого может быть полностью предсказано на основе входных данных.

termwiki.com

По поводу состояния:

В зависимости от того, чье определение функции вы используете, функция не имеет состояния. Если вы пришли из объектно-ориентированного мира, помните, что x.f(y)это метод. Как функция это будет выглядеть так f(x,y). И если вы находитесь в замыканиях с закрытой лексической областью, помните, что неизменное состояние может также быть частью выражения функций. Это только изменчивое состояние, которое будет влиять на функции детерминированного характера. Таким образом, f (x) = x + 1 является детерминированным, пока 1 не изменяется. Не имеет значения, где хранится 1.

Ваши функции оба детерминированы. Ваша первая тоже чистая функция. Ваш второй не чист.

Чистая функция

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

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

wikipedia.org

Точка 1 означает детерминированность . Пункт 2 означает ссылочную прозрачность . Вместе они означают, что чистая функция позволяет изменять только свои аргументы и возвращаемое значение. Ничто другое не вызывает изменений. Больше ничего не изменилось.

candied_orange
источник
-1. Запись в базу данных зависит от внешнего состояния, которое обычно не может быть определено по входам. База данных может быть недоступна по ряду причин, и то, будет ли операция успешной, не предсказуемо. Это не детерминированное поведение.
Frax
@Frax Системная память может быть недоступна. Процессор может быть недоступен. Быть детерминированным не гарантирует успеха. Это гарантирует, что успешное поведение предсказуемо.
candied_orange
OOMing не является специфическим для какой-либо функции, это другая категория проблем. Теперь давайте просто посмотрим на пункт 1. вашего определения «чистой функции» (что на самом деле означает «детерминированный»): «Значение результата функции не может зависеть от какой-либо скрытой информации или состояния, которое может измениться во время выполнения программы или между различными выполнениями программа , и при этом это не может зависеть от любого внешнего входа от устройств ввода-вывода ". База данных - это такое состояние, поэтому функция OP явно не соответствует этому условию - она ​​не является детерминированной.
Frax
@candied_orange Я бы согласился, если бы запись в БД зависела только от входных данных. Но это так Math.random(). Так что нет, если только мы не примем PRNG (вместо физического RNG) И не посчитаем, что PRNG указывают часть ввода (а это не так, ссылка жестко закодирована), это не детерминистично.
Марстато
1
@candied_orange вашу цитату из детерминированных состояний «алгоритм, поведение которого можно полностью предсказать из входных данных». Писать в IO, для меня, безусловно, поведение, а не результат.
Марстато
9

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

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

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

Комбинации идемпотентных функций могут быть или не быть идемпотентными в целом.

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

Джон Ханна
источник
3
Термин «ссылочно-прозрачный» определен более строго, чем «все равно». даже если не принимать во внимание вопросы ввода - вывода , такие как проблемы с подключением, недостающие строки подключения, времени ожидания и т.д., то это по - прежнему легко показать , что программа , которая заменяет (f x, f x)с let y = f x in (y, y)будет работать в свободное дисковое пространство-исключений в два раза быстрее , можно утверждать , что эти крайние случаи вас не волнуют, но с таким нечетким определением мы могли бы также назвать new Random().Next()референциально прозрачным, потому что, черт возьми, мне все равно, какое число я получу в любом случае.
Сара
@kai: в зависимости от контекста, вы можете игнорировать побочные эффекты. С другой стороны, возвращаемое значение функции, такой как random, не является побочным эффектом: это его основной эффект.
Джорджио
@Giorgio Random.Nextв .NET действительно имеет побочные эффекты. Даже очень. Если вы можете Nextназначить ее переменной, а затем Nextснова вызвать и назначить другую переменную, скорее всего, они не будут одинаковыми. Зачем? Потому что вызов Nextизменяет скрытое внутреннее состояние Randomобъекта. Это полярная противоположность ссылочной прозрачности. Я не понимаю вашего заявления о том, что «основные эффекты» не могут быть побочными эффектами. В императивном коде чаще всего встречается побочный эффект, потому что императивные программы имеют состояние с учетом состояния.
Сара
3

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

user470365
источник
1
Согласовано! Это (неофициально) математическое определение «функции», но, как вы говорите, к сожалению, «функция» означает что-то другое в языках программирования, где оно ближе к «пошаговой процедуре, необходимой для получения значения».
Андрес Ф.
2

Это в основном зависит от того, заботишься ты о нечистоте или нет. Если семантика этой таблицы такова, что вам все равно, сколько записей, то она чистая. Иначе, это не чисто.

Или, другими словами, это нормально, если оптимизация, основанная на чистоте, не нарушает семантику программы.

Более реалистичный пример был бы, если вы пытались отладить эту функцию и добавили операторы регистрации. Технически, регистрация - побочный эффект. Логи делают это нечистым? Нет.

DeadMG
источник
Смотря как. Может быть, журналы делают его нечистым, например, если вы заботитесь о том, сколько раз и в какое время в вашем журнале появляется «INFO f () named». Что вы часто делаете :)
Андрес Ф.
8
-1 Логи имеют значение. На большинстве платформ любой вывод неявно синхронизирует выполнение потока. Поведение вашей программы становится зависимым от записи других потоков, от внешних записей журнала, иногда от чтения журнала, от состояния файловых дескрипторов. Это так же чисто, как ведро грязи.
Basilevs
@AndresF. Ну, вы, вероятно, не заботитесь о буквальном числе раз. Вы, вероятно, заботитесь только о том, чтобы оно регистрировалось столько раз, сколько была вызвана функция.
DeadMG
@Basilevs Поведение функции не зависит от них вообще. Если запись в журнал не удалась, вы просто продолжаете.
DeadMG
2
Вопрос в том, выберете ли вы регистратор как часть среды выполнения или нет. В другом примере, моя чистая функция остается чистой, если я присоединяю к процессу отладчик и устанавливаю для него точку останова? Из POV человека, использующего отладчик, очевидно, что функция имеет побочные эффекты, но обычно мы анализируем программу с соглашением, что это «не считается». То же самое можно (но не обязательно) использовать для ведения журнала, используемого для отладки, и я предполагаю, что трассировка скрывает его нечистоту. Ведение журнала критически важных задач, например, для аудита, конечно, является существенным побочным эффектом.
Стив Джессоп
1

Я бы сказал, что лучше всего спросить не о том, как мы это называем, а о том, как мы будем анализировать такой кусок кода. И мой первый ключевой вопрос в таком анализе:

  • Зависит ли побочный эффект от аргумента функции или результата от побочного эффекта?
    • Нет . «Эффективная функция» может быть преобразована в чистую функцию, эффективное действие и механизм их объединения.
    • Да: «эффективная функция» - это функция, которая дает монадический результат.

Это легко проиллюстрировать в Haskell (и это предложение - только половина шутки). Пример случая «нет» будет примерно таким:

double :: Num a => a -> IO a
double x = do
  putStrLn "I'm doubling some number"
  return (x*2)

В этом примере действие, которое мы предпринимаем (выводим строку "I'm doubling some number"), не влияет на отношения xи результат. Это означает, что мы можем реорганизовать его таким образом (используя Applicativeкласс и его *>оператор), что показывает, что функция и эффект фактически ортогональны:

double :: Num a => a -> IO a
double x = action *> pure (function x)
  where
    -- The pure function 
    function x = x*2  
    -- The side effect
    action = putStrLn "I'm doubling some number"

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

Пример типа «да», где чистая и эффективная части не ортогональны:

double :: Num a => a -> IO a
double x = do
  putStrLn ("I'm doubling the number " ++ show x)
  return (x*2)

Теперь строка, которую вы печатаете, зависит от значения x. Функция часть (умножить xна два), однако, не зависит от влияния на всех, так что мы можем по- прежнему учитывать это:

logged :: (a -> b) -> (a -> IO x) -> IO b
logged function logger a = do
  logger a
  return (function a)

double x = logged function logger
  where function = (*2) 
        logger x putStrLn ("I'm doubling the number " ++ show x)

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

Это одна из причин, по которой Haskell Monadтак широко использует свой класс. Монады являются (помимо прочего) инструментом для проведения такого анализа и рефакторинга.

sacundim
источник
-2

Функции, которые предназначены для побочных эффектов, часто называют эффективными . Пример https://slpopejoy.github.io/posts/Effectful01.html

Бен Хатчисон
источник
Только ответ, чтобы упомянуть общепризнанный термин «эффективный», и за него проголосовали… Полагаю, невежество - это блаженство. ..
Бен Хатчисон
«эффективный» - это слово, которое автор этого поста выбрал для обозначения «имеющий побочные эффекты». Он сам так говорит.
Роберт Харви
Погуглить effectful функции показывает множество доказательств его широко используемый термин. Сообщение в блоге было дано как один из многих примеров, а не как определение. В кругах функционального программирования, где чистые функции являются значениями по умолчанию, необходим положительный термин для описания намеренно побочных эффектов. Т.е. больше, чем просто отсутствие чистоты . Эффект этот термин. Теперь считайте себя образованным.
Бен Хатчисон
Мех.
Роберт Харви