Большинство источников определяют чистую функцию как имеющую следующие два свойства:
- Его возвращаемое значение одинаково для тех же аргументов.
- Его оценка не имеет побочных эффектов.
Это первое условие, которое касается меня. В большинстве случаев это легко судить. Рассмотрим следующие функции JavaScript (как показано в этой статье )
Pure:
const add = (x, y) => x + y;
add(2, 4); // 6
Нечистая:
let x = 2;
const add = (y) => {
return x += y;
};
add(4); // x === 6 (the first time)
add(4); // x === 10 (the second time)
Легко видеть, что 2-я функция будет выдавать разные выходы для последующих вызовов, тем самым нарушая первое условие. И, следовательно, это нечисто.
Эту часть я получаю.
Теперь, на мой вопрос, рассмотрим эту функцию, которая конвертирует заданную сумму в долларах в евро:
(РЕДАКТИРОВАТЬ - Использование const
в первой строке. Использовалось let
ранее непреднамеренно.)
const exchangeRate = fetchFromDatabase(); // evaluates to say 0.9 for today;
const dollarToEuro = (x) => {
return x * exchangeRate;
};
dollarToEuro(100) //90 today
dollarToEuro(100) //something else tomorrow
Предположим, мы выбираем обменный курс из базы данных, и он меняется каждый день.
Теперь, независимо от того, сколько раз я вызываю эту функцию сегодня , она выдаст мне один и тот же вывод для ввода 100
. Однако это может дать мне другой результат завтра. Я не уверен, нарушает ли это первое условие или нет.
Таким образом, сама функция не содержит никакой логики для изменения входных данных, но использует внешнюю константу, которая может измениться в будущем. В этом случае он абсолютно уверен, что он будет меняться ежедневно. В других случаях это может произойти; это не может быть.
Можем ли мы назвать такие функции чистыми функциями. Если ответ НЕТ, как мы можем изменить его на один?
источник
function myNumber(n) { this.n = n; }; myNumber.prototype.valueOf = function() { console.log('impure'); return this.n; }; const n = new myNumber(42); add(n, 1);
(x) => {return x * 0.9;}
. Завтра у вас будет другая функция, которая также может быть чистой(x) => {return x * 0.89;}
. Обратите внимание, что каждый раз, когда вы запускаете,(x) => {return x * exchangeRate;}
он создает новую функцию, и эта функция чистая, потому чтоexchangeRate
не может измениться.const dollarToEuro = (x, exchangeRate) => { return x * exchangeRate; };
для чистой функции, котораяIts return value is the same for the same arguments.
должна храниться всегда, 1 секунда, 1 декада ... позже, несмотря ни на чтоОтветы:
В
dollarToEuro
«S возвращаемое значение зависит от внешней переменной, не аргумент; следовательно, функция нечиста.Один из вариантов - пройти
exchangeRate
. Таким образом, каждый раз, когда аргументы есть(something, somethingElse)
, выходные данные гарантированно будутsomething * somethingElse
:Обратите внимание, что для функционального программирования следует избегать
let
- всегда использовать,const
чтобы избежать переназначения.источник
const add = x => y => x + y; const one = add(42);
здесь и такadd
иone
есть чистые функции.const foo = 42; const add42 = x => x + foo;
<- это еще одна чистая функция, которая снова использует свободные переменные.dollarToEuro
функция в примере в вашем ответе нечиста, потому что она зависит от свободной переменнойexchangeRate
. Это абсурдно. Как указал zerkms, чистота функции не имеет ничего общего с тем, есть ли у нее свободные переменные. Однако, zerkms также неверен, потому что он считает, чтоdollarToEuro
функция нечиста, потому что она зависит от того,exchangeRate
что исходит из базы данных. Он говорит, что это нечисто, потому что «это зависит от IO транзитивно».dollarToEuro
это нечисто, потому чтоexchangeRate
это свободная переменная. Это говорит о том, что если быexchangeRate
не было свободной переменной, т.е. если бы это был аргумент, тоdollarToEuro
было бы чисто. Следовательно, это говорит о том, чтоdollarToEuro(100)
это нечисто, ноdollarToEuro(100, exchangeRate)
чисто. Это явно абсурдно, потому что в обоих случаях вы зависитеexchangeRate
от базы данных. Разница лишь в том, является лиexchangeRate
свободная переменная внутриdollarToEuro
функции.Технически, любая программа, которую вы выполняете на компьютере, является нечистой, потому что она в конечном итоге компилируется в инструкции типа «переместить это значение в
eax
» и «добавить это значение в содержимоеeax
», которые являются нечистыми. Это не очень полезно.Вместо этого мы думаем о чистоте, используя черные ящики . Если какой-то код всегда выдает одни и те же выходные данные при одинаковых входных данных, он считается чистым. По этому определению следующая функция также является чистой, хотя внутренне она использует нечистую таблицу заметок.
Мы не заботимся о внутренних органах, потому что мы используем методологию черного ящика для проверки на чистоту. Точно так же нас не волнует, что весь код в конечном итоге преобразуется в нечистые машинные инструкции, потому что мы думаем о чистоте, используя методологию черного ящика. Внутренние органы не важны.
Теперь рассмотрим следующую функцию.
Является ли
greet
функция чистым или нечистым? Согласно нашей методологии черного ящика, если мы даем ему одинаковый ввод (напримерWorld
), то он всегда выводит один и тот же вывод на экран (т.е.Hello World!
). В этом смысле разве это не чисто? Нет, это не так. Причина, по которой это не чисто, в том, что мы считаем печать чего-то на экране побочным эффектом. Если наш черный ящик вызывает побочные эффекты, то он не чистый.Что такое побочный эффект? Здесь полезна концепция ссылочной прозрачности . Если функция является ссылочно прозрачной, то мы всегда можем заменить приложения этой функции их результатами. Обратите внимание, что это не то же самое, что встроенная функция .
При вставке функции мы заменяем приложения функции телом функции, не изменяя семантику программы. Однако ссылочно-прозрачную функцию всегда можно заменить ее возвращаемым значением, не изменяя семантику программы. Рассмотрим следующий пример.
Здесь мы ввели определение,
greet
и оно не изменило семантику программы.Теперь рассмотрим следующую программу.
Здесь мы заменили приложения
greet
функции их возвращаемыми значениями, и это изменило семантику программы. Мы больше не печатаем приветствия на экране. Вот почему печать считается побочным эффектом, и поэтому этаgreet
функция нечиста. Это не ссылочно прозрачно.Теперь давайте рассмотрим другой пример. Рассмотрим следующую программу.
Понятно, что эта
main
функция нечиста. ОднакоtimeDiff
функция чистая или нечистая? Хотя это зависит от того,serverTime
что исходит от нечистого сетевого вызова, он все еще прозрачен по ссылкам, потому что он возвращает те же выходные данные для тех же самых входов и потому что у него нет никаких побочных эффектов.zerkms , вероятно, не согласится со мной по этому вопросу. В своем ответе он сказал, что
dollarToEuro
функция в следующем примере нечиста, потому что «она транзитивно зависит от IO».Я должен не согласиться с ним, потому что тот факт, что он
exchangeRate
поступил из базы данных, не имеет значения. Это внутренняя деталь, и наша методология черного ящика для определения чистоты функции не заботится о внутренних деталях.В чисто функциональных языках, таких как Haskell, у нас есть аварийный люк для выполнения произвольных эффектов ввода-вывода. Он называется
unsafePerformIO
, и, как следует из названия, если вы не используете его правильно, это небезопасно, потому что это может нарушить ссылочную прозрачность. Однако, если вы знаете, что делаете, то использовать его совершенно безопасно.Обычно он используется для загрузки данных из конфигурационных файлов в начале программы. Загрузка данных из файлов конфигурации является нечистой операцией ввода-вывода. Однако мы не хотим обременяться передачей данных в качестве входных данных для каждой функции. Следовательно, если мы используем их,
unsafePerformIO
мы можем загрузить данные на верхнем уровне, и все наши чистые функции могут зависеть от неизменных глобальных данных конфигурации.Обратите внимание, что то, что функция зависит от данных, загруженных из файла конфигурации, базы данных или сетевого вызова, не означает, что эта функция нечиста.
Однако давайте рассмотрим ваш оригинальный пример с другой семантикой.
Здесь я предполагаю, что поскольку
exchangeRate
он не определен какconst
, он будет изменен во время работы программы. Если это так, тоdollarToEuro
это определенно нечистая функция, потому что приexchangeRate
изменении она нарушит ссылочную прозрачность.Однако, если
exchangeRate
переменная не изменена и никогда не будет изменена в будущем (т. Е. Если это постоянное значение), то даже если она определена какlet
, она не нарушит ссылочную прозрачность. В таком случаеdollarToEuro
это действительно чистая функция.Обратите внимание, что значение
exchangeRate
может меняться каждый раз, когда вы снова запускаете программу, и это не нарушит прозрачность ссылок. Он нарушает прозрачность ссылок только в том случае, если он изменяется во время работы программы.Например, если вы запустите мой
timeDiff
пример несколько раз, вы получите разные значенияserverTime
и, следовательно, разные результаты. Однако, поскольку значениеserverTime
никогда не изменяется во время работы программы,timeDiff
функция является чистой.источник
const
в своем примере.const
тоdollarToEuro
функция действительно чистая. Единственный способexchangeRate
изменить значение - это если вы снова запустите программу. В этом случае старый процесс и новый процесс различны. Следовательно, это не нарушает ссылочную прозрачность. Это как дважды вызвать функцию с разными аргументами. Аргументы могут быть разными, но внутри функции значение аргументов остается постоянным.eax
очищено - с помощью загрузки или очистки - код остается детерминированным независимо от того, что еще происходит и, следовательно, чисто. В противном случае, очень полный ответ.Ответ ме-пуриста (где «я» буквально «я», так как я думаю, что у этого вопроса нет ни одного формального «правильного» ответа):
В таком динамическом языке, как JS, с таким большим количеством возможностей, чтобы обезопасить базовые типы, или создавать пользовательские типы, используя такие функции, как
Object.prototype.valueOf
невозможно определить, является ли функция чистой, просто взглянув на нее, так как вызывающий может решить, хотят ли они производить побочные эффекты.Демо:
Ответ меня-прагматика:
Из самого определения из Википедии
Другими словами, имеет значение только то, как ведет себя функция, а не то, как она реализована. И до тех пор, пока конкретная функция содержит эти 2 свойства - она чиста независимо от того, как именно она была реализована.
Теперь к вашей функции:
Это нечисто, потому что не соответствует требованию 2: оно транзитивно зависит от IO.Я согласен с тем, что приведенное выше утверждение неверно, подробности см. В другом ответе: https://stackoverflow.com/a/58749249/251311.
Другие соответствующие ресурсы:
источник
me
как zerkms, который дает ответ.add42
и моимaddX
чисто в том, что моеx
может быть изменено, а вашеft
не может быть изменено (и, следовательно,add42
возвращаемое значение не зависит отft
)?dollarToEuro
функция в вашем примере нечиста. Я объяснил, почему я не согласен в своем ответе. stackoverflow.com/a/58749249/783743Как и в других ответах, как вы реализовали
dollarToEuro
,действительно чисто, потому что обменный курс не обновляется во время работы программы. Концептуально, однако,
dollarToEuro
кажется, что это должна быть нечистая функция, поскольку она использует любой самый актуальный обменный курс. Самый простой способ объяснить это несоответствие - это то, что вы не реализовали,dollarToEuro
ноdollarToEuroAtInstantOfProgramStart
.Ключевым моментом здесь является то, что есть несколько параметров, которые требуются для расчета конвертации валюты, и что действительно чистая версия генерала
dollarToEuro
предоставит все из них. Самыми прямыми параметрами являются количество долларов США для конвертации и обменный курс. Однако, поскольку вы хотите получить свой обменный курс на основе опубликованной информации, теперь у вас есть три параметра для предоставления:Историческим авторитетом здесь является ваша база данных, и, предполагая, что база данных не взломана, всегда будет возвращать один и тот же результат для обменного курса в определенный день. Следовательно, с помощью комбинации этих трех параметров вы можете написать полностью чистую, самодостаточную версию генерала
dollarToEuro
, которая может выглядеть примерно так:Ваша реализация захватывает постоянные значения как для исторического авторитета, так и для даты транзакции в момент создания функции - исторический авторитет - это ваша база данных, а захваченная дата - это дата запуска программы - все, что осталось, это сумма в долларах , который обеспечивает вызывающая сторона. Нечистая версия,
dollarToEuro
которая всегда получает самое актуальное значение, по сути, неявно принимает параметр даты, устанавливая его на момент вызова функции, который не является чистым просто потому, что вы никогда не сможете вызывать функцию с одинаковыми параметрами дважды.Если вы хотите иметь чистую версию,
dollarToEuro
которая все еще может получить наиболее актуальное значение, вы все равно можете привязать исторический авторитет, но оставить параметр даты свободным и запросить дату у вызывающей стороны в качестве аргумента, в результате чего с чем-то вроде этого:источник
Я хотел бы немного отступить от конкретных деталей JS и абстракции формальных определений, а также поговорить о том, какие условия необходимо соблюдать, чтобы включить конкретные оптимизации. Обычно это главное, о чем мы заботимся при написании кода (хотя это также помогает доказать правильность). Функциональное программирование не является ни руководством к последней моде, ни монашеским обетом самоотречения. Это инструмент для решения проблем.
Когда у вас есть такой код:
Если
exchangeRate
между двумя вызовами невозможно изменить ихdollarToEuro(100)
, можно запомнить результат первого вызоваdollarToEuro(100)
и оптимизировать второй вызов. Результат будет таким же, поэтому мы можем просто запомнить значение из ранее.Значение
exchangeRate
может быть установлено один раз перед вызовом любой функции, которая ищет его, и никогда не изменяется. Менее ограниченно, вы можете иметь код, который ищетexchangeRate
один раз для определенной функции или блока кода и использует один и тот же обменный курс последовательно в этой области. Или, если только этот поток может изменять базу данных, вы будете иметь право предполагать, что, если вы не обновили обменный курс, никто другой не изменил его для вас.Если
fetchFromDatabase()
сама по себе является чистой функцией,exchangeRate
вычисляющей константу, и является неизменной, мы можем сложить эту константу на всем протяжении вычисления. Компилятор, который знает, что это так, может сделать тот же вывод, который вы сделали в комментарии, которыйdollarToEuro(100)
оценивается как 90.0, и заменить все выражение константой 90.0.Однако, если
fetchFromDatabase()
не выполняется ввод-вывод, что считается побочным эффектом, его название нарушает принцип наименьшего удивления.источник
Эта функция не является чистой, она зависит от внешней переменной, которая почти наверняка изменится.
Таким образом, функция не выполняет первую сделанную вами точку зрения, она не возвращает одинаковое значение при одинаковых аргументах.
Чтобы сделать эту функцию «чистой», передайте
exchangeRate
в качестве аргумента.Это будет тогда удовлетворять обоим условиям.
Пример кода:
источник
const
.Чтобы расширить те моменты, которые другие высказывают о ссылочной прозрачности: мы можем определить чистоту как просто ссылочную прозрачность вызовов функций (т. Е. Каждый вызов функции может быть заменен возвращаемым значением без изменения семантики программы).
Два указанных вами свойства являются следствием ссылочной прозрачности. Например, следующая функция
f1
является нечистой, так как она не дает один и тот же результат каждый раз (свойство, которое вы пронумеровали 1):Почему важно каждый раз получать один и тот же результат? Потому что получение разных результатов - это один из способов для вызова функции иметь другую семантику от значения и, следовательно, нарушать прозрачность ссылок.
Допустим, мы пишем код
f1("hello", "world")
, запускаем его и получаем возвращаемое значение"hello"
. Если мы сделаем поиск / замену каждого вызоваf1("hello", "world")
и заменим их на,"hello"
мы изменим семантику программы (теперь все вызовы будут заменены на"hello"
, но первоначально около половины из них были бы оценены"world"
). Следовательно, призывыf1
не являются ссылочно прозрачными, а значитf1
нечистыми.Другой способ, которым у вызова функции может быть другая семантика к значению, заключается в выполнении операторов. Например:
Возвращаемое значение
f2("bar")
всегда будет"bar"
, но семантика значения"bar"
отличается от вызова,f2("bar")
так как последний также войдет в консоль. Замена одного другим изменила бы семантику программы, поэтому она не является прозрачной по ссылкам и, следовательноf2
, нечистой.Является ли ваша
dollarToEuro
функция ссылочно прозрачной (и, следовательно, чистой), зависит от двух вещей:exchangeRate
когда-либо в пределах этого «объема»Не существует «лучшей» области применения; обычно мы думаем об одном прогоне программы или времени жизни проекта. Как аналогия, представьте, что возвращаемые значения каждой функции кэшируются (как таблица memo в примере, приведенном @ aadit-m-shah): когда нам нужно очистить кеш, чтобы гарантировать, что устаревшие значения не будут мешать нашим семантика?
Если
exchangeRate
использовать,var
то он может меняться между каждым вызовомdollarToEuro
; нам нужно будет очищать любые кэшированные результаты между каждым вызовом, чтобы не было ссылочной прозрачности, о которой можно было бы говорить.При использовании
const
мы расширяем область действия до запуска программы: было бы безопасно кешировать возвращаемые значенияdollarToEuro
до завершения программы. Мы можем представить себе использование макроса (на языке, подобном Lisp) для замены вызовов функций их возвращаемыми значениями. Эта степень чистоты является общей для таких вещей, как значения конфигурации, параметры командной строки или уникальные идентификаторы. Если мы ограничимся размышлениями об одном прогоне программы, тогда мы получим большинство преимуществ чистоты, но мы должны быть осторожны при каждом прогоне (например, сохранение данных в файл, а затем загрузка его в другой прогон). Я бы не назвал такие функции «чистыми» в абстрактном смысле (например, если бы я писал словарное определение), но у меня не было бы проблем с обработкой их как чистых в контексте .Если мы рассматриваем время существования проекта как нашу «область видимости», то мы являемся «наиболее прозрачными» и, следовательно, «наиболее чистыми», даже в абстрактном смысле. Нам никогда не нужно будет очищать наш гипотетический кеш. Мы могли бы даже сделать это «кэширование», напрямую переписав исходный код на диске, чтобы заменить вызовы их возвращаемыми значениями. Это могло бы работать даже в разных проектах, например, мы могли бы представить онлайновую базу данных функций и их возвращаемых значений, где любой может найти вызов функции и (если он находится в БД) использовать возвращаемое значение, предоставленное кем-то на другой стороне мир, который использовал идентичную функцию несколько лет назад в другом проекте.
источник
Как написано, это чистая функция. Это не производит никаких побочных эффектов. Функция имеет один формальный параметр, но имеет два входа и всегда будет выводить одно и то же значение для любых двух входов.
источник
Как вы должным образом заметили, «это может дать мне другой результат завтра» . Если это так, ответом будет громкое «нет» . Это особенно
dollarToEuro
верно, если ваше предполагаемое поведение было правильно интерпретировано как:Однако существует иная интерпретация, в которой она будет считаться чистой:
dollarToEuro
прямо выше чисто.С точки зрения разработки программного обеспечения важно объявить зависимость от
dollarToEuro
функцииfetchFromDatabase
. Поэтому рефакторим определениеdollarToEuro
следующим образом:С этим результатом, учитывая предпосылку , что
fetchFromDatabase
функции удовлетворительно, то мы можем заключить , что проекцияfetchFromDatabase
ONdollarToEuro
должна быть удовлетворительной. Или утверждение «fetchFromDatabase
чисто» подразумеваетdollarToEuro
чисто (посколькуfetchFromDatabase
является основой дляdollarToEuro
скалярного множителяx
.Из исходного поста я могу понять, что
fetchFromDatabase
это функция времени. Давайте улучшим работу по рефакторингу, чтобы сделать это понимание прозрачным, и, следовательно, четко определеннымfetchFromDatabase
как чистая функция:fetchFromDatabase = (timestamp) => {/ * здесь идет реализация * /};
В конечном счете, я бы реорганизовал эту функцию следующим образом:
Следовательно,
dollarToEuro
может быть проверен модулем, просто доказав, что он правильно вызываетfetchFromDatabase
(или его производнаяexchangeRate
).источник
dollarToEuro
; Я упомянул это в OP, что могут быть другие варианты использования. Я выбрал dollarToEuro, потому что он мгновенно вызывает то, что я пытаюсь сделать, но может быть что-то менее тонкое, что зависит от свободной переменной, которая может измениться, но не обязательно как функция времени. Имея это в виду, я считаю, что рефактор с верхним голосованием является более доступным и может помочь другим в аналогичных случаях использования. Спасибо за вашу помощь независимо.Я - двуязычный язык Haskell / JS, и Haskell - один из языков, который имеет большое значение для чистоты функций, поэтому я подумал, что дам вам точку зрения с точки зрения Haskell.
Как уже говорили другие, в Хаскеле чтение изменяемой переменной обычно считается нечистым. Существует разница между переменными и определениями в том, что переменные могут изменяться позже, определения остаются неизменными навсегда. Так что, если вы уже объявили его
const
тогда (при условии , что это простоnumber
и не имеет изменчивую внутреннюю структуру), чтение из этого будет использовать определение, которое является чистым. Но вы хотели смоделировать обменные курсы, изменяющиеся со временем, и это требует некоторой изменчивости, и тогда вы попадаете в нечистоту.Чтобы описать такие виды нечистых вещей (мы можем назвать их «эффектами», а их использование «эффективными», а не «чистыми») в Haskell, мы делаем то, что вы могли бы назвать метапрограммированием . Сегодня метапрограммирование обычно относится к макросам, и это не то, что я имею в виду, а просто идея написать программу для написания другой программы в целом.
В этом случае в Haskell мы пишем чистые вычисления, которые вычисляют эффективную программу, которая затем будет делать то, что мы хотим. Таким образом, весь смысл исходного файла на Haskell (по крайней мере, тот, который описывает программу, а не библиотеку) состоит в том, чтобы описать чистые вычисления для вызываемой эффективной программы, которая производит «void»
main
. Затем задача компилятора Haskell состоит в том, чтобы взять этот исходный файл, выполнить эти чистые вычисления и поместить эту эффективную программу в виде двоичного исполняемого файла где-то на жестком диске, чтобы запускать его позже на досуге. Другими словами, существует разрыв между временем выполнения чистых вычислений (когда компилятор создает исполняемый файл) и временем выполнения эффективной программы (всякий раз, когда вы запускаете исполняемый файл).Таким образом, для нас эффективные программы на самом деле являются структурой данных, и они по сути ничего не делают, просто будучи упомянутыми (они не имеют * побочных эффектов в дополнение к своему возвращаемому значению; их возвращаемое значение содержит их эффекты). Для очень легкого примера класса TypeScript, который описывает неизменяемые программы и некоторые вещи, которые вы можете сделать с ними,
Ключ в том, что если у вас есть,
Program<x>
то никаких побочных эффектов не произошло, и это полностью функционально чистые объекты. Отображение функции на программу не имеет побочных эффектов, если только функция не была чистой; последовательность двух программ не имеет побочных эффектов; и т.п.Так, например, как применить это в вашем случае, вы можете написать некоторые чистые функции, которые возвращают программы для получения пользователей по идентификатору и для изменения базы данных и получения данных JSON, например,
а затем вы могли бы описать работу cron, чтобы свернуть URL-адрес, найти какого-нибудь сотрудника и уведомить его руководителя в чисто функциональном виде, как
Дело в том, что каждая функция здесь является полностью чистой функцией; на самом деле ничего не произошло, пока я фактически не привел
action.run()
его в движение. Кроме того, я могу написать такие функции, как,и если бы у JS было обещание отмены, мы могли бы поспорить между двумя программами, взять первый результат и отменить второй. (Я имею в виду, что мы все еще можем, но становится менее понятно, что делать.)
Точно так же в вашем случае мы можем описать изменение обменного курса с
и
exchangeRate
может быть программа, которая смотрит на изменяемое значение,но даже в этом случае эта функция
dollarsToEuros
теперь является чистой функцией от числа до программы, которая производит число, и вы можете рассуждать об этом таким детерминистическим уравнением, которое вы можете рассуждать о любой программе, которая не имеет побочных эффектов.Цена, конечно, заключается в том, что вы должны в конечном итоге назвать это
.run()
где-то , и это будет нечистым. Но вся структура ваших вычислений может быть описана чисто вычислениями, и вы можете отодвинуть примеси на полях вашего кода.источник