Это чистая функция? - PullRequest
       5

Это чистая функция?

66 голосов
/ 07 ноября 2019

Большинство источников определяют чистую функцию как имеющую следующие два свойства:

  1. Ее возвращаемое значение одинаково для тех же аргументов.
  2. Ее оценкане имеет побочных эффектов.

Это первое условие, которое касается меня. В большинстве случаев это легко судить. Рассмотрим следующие функции JavaScript (как показано в этой статье )

Pure:

const add = (x, y) => x + y;

add(2, 4); // 6

Impure:

let x = 2;

const add = (y) => {
  return x += y;
};

add(4); // x === 6 (the first time)
add(4); // x === 10 (the second time)

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

Эта часть, которую я получаю.


Теперь, на мой вопрос, рассмотрим эту функцию, которая преобразует определенную сумму в долларах в евро:

(РЕДАКТИРОВАТЬ - Используя 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. Однако это может дать мне другой результат завтра. Я не уверен, нарушает ли это первое условие или нет.

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

Можем ли мы называть такие функции чистыми функциями. Если ответ НЕТ, как мы можем изменить его на один?

Ответы [ 10 ]

84 голосов
/ 07 ноября 2019

Возвращаемое значение dollarToEuro зависит от внешней переменной, которая не является аргументом, поэтому она неочевидна.

В ответе НЕТ, как тогда мы можем изменить его на единицу?

Один из вариантов - передать exchangeRate. Таким образом, каждый раз, когда аргументы (something, somethingElse), выходные данные гарантированно будут точно something * somethingElse:

const exchangeRate =  fetchFromDatabase(); // evaluates to say 0.9 for today;

const dollarToEuro = (x, exchangeRate) => {
  return x * exchangeRate;
};

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

59 голосов
/ 07 ноября 2019

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

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

const fib = (() => {
    const memo = [0, 1];

    return n => {
      if (n >= memo.length) memo[n] = fib(n - 1) + fib(n - 2);
      return memo[n];
    };
})();

console.log(fib(100));

Мы не заботимся о внутренних органах, потому что мы используем методологию черного ящика для проверки на чистоту. Точно так же нас не волнует, что весь код в конечном итоге преобразуется в нечистые машинные инструкции, потому что мы думаем о чистоте, используя методологию черного ящика. Внутренние органы не важны.

Теперь рассмотрим следующую функцию.

const greet = name => {
    console.log("Hello %s!", name);
};

greet("World");
greet("Snowman");

Является ли функция greet чистой или нечистой? Согласно нашей методологии черного ящика, если мы даем ему одинаковый ввод (например, World), то он всегда выводит один и тот же вывод на экран (т.е. Hello World!). В этом смысле разве это не чисто? Нет, это не так. Причина, по которой это не чисто, в том, что мы считаем печать чего-то на экране побочным эффектом. Если наш черный ящик вызывает побочные эффекты, то он не является чистым.

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

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

console.log("Hello %s!", "World");
console.log("Hello %s!", "Snowman");

Здесь мы ввели определение greet, и оно не изменило семантику программы.

Теперь рассмотрим следующую программу.

undefined;
undefined;

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

Теперь давайте рассмотрим другой пример. Рассмотрим следующую программу.

const main = async () => {
    const response = await fetch("https://time.akamai.com/");
    const serverTime = 1000 * await response.json();
    const timeDiff = time => time - serverTime;
    console.log("%d ms", timeDiff(Date.now()));
};

main();

Очевидно, что функция main нечиста. Однако функция timeDiff чистая или нечистая? Хотя это зависит от serverTime, который исходит от нечистого сетевого вызова, он все еще прозрачен по ссылкам, потому что он возвращает те же выходные данные для тех же самых входов и потому что у него нет никаких побочных эффектов.

zerkms , вероятно, не согласится со мной по этому вопросу. В своем ответе он сказал, что функция dollarToEuro в следующем примере нечиста, потому что «она транзитивно зависит от IO».

const exchangeRate =  fetchFromDatabase(); // evaluates to say 0.9 for today;

const dollarToEuro = (x, exchangeRate) => {
  return x * exchangeRate;
};

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

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

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

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

Однако давайте рассмотрим ваш оригинальный пример с другой семантикой.

let exchangeRate =  fetchFromDatabase(); // evaluates to say 0.9 for today;

const dollarToEuro = (x) => {
  return x * exchangeRate;
};

dollarToEuro(100) //90 today

dollarToEuro(100) //something else tomorrow

ЗдесьЯ предполагаю, что, поскольку exchangeRate не определен как const, он будет изменен во время работы программы. Если это так, то dollarToEuro определенно является нечистой функцией, потому что при изменении exchangeRate она нарушит прозрачность ссылок.

Однако, если переменная exchangeRate не будет изменена и никогда не будетизменено в будущем (т. е. если это постоянное значение), то даже если оно определено как let, оно не нарушит ссылочную прозрачность. В этом случае dollarToEuro действительно является чистой функцией.

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

Например, если вы выполните мой пример timeDiff несколько раз, вы получите разные значения для serverTime и, следовательно, разные результаты. Однако, поскольку значение serverTime никогда не изменяется во время работы программы, функция timeDiff является чистой.

16 голосов
/ 07 ноября 2019

Ответ ме-пуриста (где «я» буквально «я», так как я думаю, что у этого вопроса нет единственного формального «правильного» ответа):

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

Демонстрация:

const add = (x, y) => x + y;

function myNumber(n) { this.n = n; };
myNumber.prototype.valueOf = function() {
    console.log('impure'); return this.n;
};

const n = new myNumber(42);

add(n, 1); // this call produces a side effect

Ответ ме-прагматика:

Из самого определения из Википедии

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

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

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

Теперь к вашей функции:

const exchangeRate =  fetchFromDatabase(); // evaluates to say 0.9 for today;

const dollarToEuro = (x, exchangeRate) => {
  return x * exchangeRate;
};

Это нечисто, потому что это неквалифицировать требование 2: это зависит от IO транзитивно.

Я согласен, что приведенное выше утверждение неверно, подробности см. в другом ответе: https://stackoverflow.com/a/58749249/251311

Другие соответствующие ресурсы:

10 голосов
/ 08 ноября 2019

Как и в других ответах, способ, которым вы реализовали dollarToEuro,

let exchangeRate = fetchFromDatabase(); // evaluates to say 0.9 for today;

const dollarToEuro = (x) => { return x * exchangeRate; }; 

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

. Ключевым моментом здесь является то, что есть несколько параметров, которые необходимы для расчета конвертации валюты, и что действительно чистыйверсия общего dollarToEuro будет поставлять все из них. Самыми прямыми параметрами являются количество долларов США для пересчета и обменный курс. Однако, поскольку вы хотите получить свой обменный курс на основе опубликованной информации, теперь у вас есть три параметра для предоставления:

  • Сумма, подлежащая обмену
  • Исторический авторитет, который можно проконсультировать для обменаставки
  • Дата совершения транзакции (для индексации исторического авторитета)

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

function dollarToEuro(x, authority, date) {
    const exchangeRate = authority(date);
    return x * exchangeRate;
}

dollarToEuro(100, fetchFromDatabase, Date.now());

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

Если вы хотите иметь чистую версию dollarToEuro, которая все еще может получить наиболее актуальное значение, вы все равно можете привязать исторический авторитет, но оставить параметр даты свободным и спроситьдля даты от звонящего в качестве аргумента, заканчивающегося чем-то вроде этого:

function dollarToEuro(x, date) {
    const exchangeRate = fetchFromDatabase(date);
    return x * exchangeRate;
}

dollarToEuro(100, Date.now());
5 голосов
/ 08 ноября 2019

Я бы хотел немного отступить от конкретных деталей JS и абстракции формальных определений, а также поговорить о том, какие условия необходимо соблюдать, чтобы включить конкретные оптимизации. Обычно это главное, о чем мы заботимся при написании кода (хотя это также помогает доказать правильность). Функциональное программирование не является ни руководством к последней моде, ни монашеским обетом самоотречения. Это инструмент для решения проблем.

Если у вас есть такой код:

let exchangeRate =  fetchFromDatabase(); // evaluates to say 0.9 for today;

const dollarToEuro = (x) => {
  return x * exchangeRate;
};

dollarToEuro(100) //90 today

dollarToEuro(100) //something else tomorrow

Если exchangeRate никогда не может быть изменен между двумя вызовами dollarToEuro(100), этоможно запомнить результат первого вызова на dollarToEuro(100) и оптимизировать второй вызов. Результат будет таким же, поэтому мы можем просто вспомнить значение, которое было до этого.

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

Если fetchFromDatabase() сам по себе являетсяЧистая функция, вычисляющая до константы, и exchangeRate является неизменной, мы могли бы сложить эту константу на всем протяжении вычисления. Компилятор, который знает, что это так, может сделать тот же вывод, который вы сделали в комментарии, который dollarToEuro(100) оценивает до 90.0, и заменить все выражение константой 90.0.

Однако, если fetchFromDatabase()не выполняет ввод-вывод, что считается побочным эффектом, его название нарушает принцип наименьшего удивления.

4 голосов
/ 08 ноября 2019

Эта функция не является чистой, она опирается на внешнюю переменную, которая почти наверняка изменится.

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

Чтобы сделать эту функцию "чистой", передайте exchangeRate в качестве аргумента.

Это будет удовлетворять обоим условиям.

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

Пример кода:

const dollarToEuro = (x, exchangeRate) => {
  return x * exchangeRate;
};

dollarToEuro(100, fetchFromDatabase())
4 голосов
/ 08 ноября 2019

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

Два указанных вами свойства являются последствиями ссылочной прозрачности. Например, следующая функция f1 является нечистой, поскольку она не дает каждый раз один и тот же результат (свойство, которое вы пронумеровали 1):

function f1(x, y) {
  if (Math.random() > 0.5) { return x; }
  return y;
}

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

Допустим, мы пишем код f1("hello", "world"), мы запускаем его и получаем возвращаемое значение "hello". Если мы выполняем поиск / замену каждого вызова f1("hello", "world") и заменяем их на "hello", мы изменим семантику программы (теперь все вызовы будут заменены на "hello", но первоначально около половины из них будутоценили до "world"). Следовательно, вызовы f1 не являются ссылочно прозрачными, следовательно, f1 нечист.

Другой способ, которым вызов функции может иметь различную семантику со значением, - выполнение операторов. Например:

function f2(x) {
  console.log("foo");
  return x;
}

Возвращаемое значение f2("bar") всегда будет "bar", но семантика значения "bar" отличается от вызова f2("bar"), так как последний также будет регистрироваться вконсоль. Замена одной на другую изменит семантику программы, поэтому она не будет ссылочно прозрачной, и, следовательно, f2 будет нечистой.

Будет ли ваша функция dollarToEuro ссылочно прозрачной (и, следовательно, чистой), зависит от двухвещи:

  • «Область» того, что мы считаем референтно прозрачным
  • Изменится ли когда-либо exchangeRate в этой «области»

Тамнет «лучшей» области применения;обычно мы думаем об одном прогоне программы или времени жизни проекта. В качестве аналогии, представьте, что возвращаемые значения каждой функции кэшируются (например, таблица memo в примере, представленном @ aadit-m-shah): когда нам нужно очистить кеш, чтобы гарантировать, что устаревшие значения не будут мешать нашимсемантика?

Если бы exchangeRate использовал var, то он мог бы меняться между вызовами на dollarToEuro;нам нужно будет очищать любые кэшированные результаты между каждым вызовом, чтобы не было упоминания ссылочной прозрачности.

Используя const, мы расширяем область действия для запуска программы:было бы безопасно кешировать возвращаемые значения dollarToEuro до завершения программы. Мы можем представить себе использование макроса (на языке, подобном Lisp) для замены вызовов функций их возвращаемыми значениями. Эта степень чистоты является общей для таких вещей, как значения конфигурации, параметры командной строки или уникальные идентификаторы. Если мы ограничимся размышлениями об одном прогоне программы, тогда мы получим большинство преимуществ чистоты, но мы должны быть осторожны между прогонами (например, сохранение данных в файл, затем загрузка их в другой прогон). Я бы не назвал такие функции «чистыми» в абстрактном смысле (например, если бы я писал словарное определение), но у меня не было бы проблем с обработкой их как чистых в контексте .

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

2 голосов
/ 08 ноября 2019

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

1 голос
/ 14 ноября 2019

Можем ли мы называть такие функции чистыми функциями. Если ответ НЕТ, как мы можем изменить его на один?

Как вы должным образом заметили, "это может дать мне другой результат завтра" . Если это так, ответом будет громкое "нет" . Это особенно верно, если ваше предполагаемое поведение dollarToEuro было правильно истолковано как:

const dollarToEuro = (x) => {
  const exchangeRate =  fetchFromDatabase(); // evaluates to say 0.9 for today;
  return x * exchangeRate;
};

Однако существует иная интерпретация, где она будет считаться чистой:

const dollarToEuro = ( () => {
    const exchangeRate =  fetchFromDatabase();

    return ( x ) => x * exchangeRate;
} )();

dollarToEuro прямо выше - чисто.


С точки зрения разработки программного обеспечения важно объявить зависимость dollarToEuro от функции fetchFromDatabase. Поэтому рефакторируем определение dollarToEuro следующим образом:

const dollarToEuro = ( x, fetchFromDatabase ) => {
  return x * fetchFromDatabase();
};

С этим результатом, учитывая предпосылку, что fetchFromDatabase функционирует удовлетворительно, тогда мы можем сделать вывод, что проекция fetchFromDatabase на dollarToEuroдолжен быть удовлетворительным. Или утверждение «fetchFromDatabase чисто» подразумевает, что dollarToEuro чисто (поскольку fetchFromDatabase является базисом для dollarToEuro по скалярному коэффициенту x.

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

fetchFromDatabase = (timestamp) => {/ * здесь идет реализация * /};

В конечном счете, я бы реорганизовал эту функцию следующим образом:

const fetchFromDatabase = ( timestamp ) => { /* here goes the implementation */ };

// Do a partial application of `fetchFromDatabase` 
const exchangeRate = fetchFromDatabase.bind( null, Date.now() );

const dollarToEuro = ( dollarAmount, exchangeRate ) => dollarAmount * exchangeRate();

Следовательно, dollarToEuro может бытьпроверенный модулем, просто доказав, что он правильно вызывает fetchFromDatabase (или его производную exchangeRate).

0 голосов
/ 08 ноября 2019

Как уже говорили другие, чтение изменчивой переменной обычно считается нечистым. Если бы вы объявили его const, то (предполагая, что это просто number и не имеет изменяемой внутренней структуры), это было бы чисто.

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

Так что для очень легкого примера класса, который описывает неизменяемые программы и некоторые вещи, которые вы можете с ними сделать,

export class Program<x> {
   // wrapped function value
   constructor(public run: () => Promise<x>) {}
   // promotion of any value into a program which makes that value
   static of<v>(value: v): Program<v> {
     return new Program(() => Promise.resolve(value));
   }
   // applying any pure function to a program which makes its input
   map<y>(fn: (x: x) => y): Program<y> {
     return new Program(() => this.run().then(fn));
   }
   // sequencing two programs together
   chain<y>(after: (x: x) => Program<y>): Program<y> {
    return new Program(() => this.run().then(x => after(x).run()));
   }
}

Ключ в том, что если у вас есть Program<x>, то нетпобочные эффекты произошли, и это полностью функционально чистые объекты. Отображение функции на программу не имеет побочных эффектов, если только функция не была чистой;последовательность двух программ не имеет побочных эффектов;и т. д.

Так, например, мы бы написали несколько программ для получения пользователей по ID и для изменения базы данных и выборки данных JSON, например,

// assuming a database library in knex, say
function getUserById(id: number): Program<{ id: number, name: string, supervisor_id: number }> {
    return new Program(() => knex.select('*').from('users').where({ id }));
}
function notifyUserById(id: number, message: string): Program<void> {
    return new Program(() => knex('messages').insert({ user_id: id, type: 'notification', message }));
}
function fetchJSON(url: string): Program<any> {
  return new Program(() => fetch(url).then(response => response.json()));
}

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

const action =
  fetchJSON('http://myapi.example.com/employee-of-the-month')
    .chain(eotmInfo => getUserById(eotmInfo.id))
    .chain(employee => 
        getUserById(employee.supervisor_id)
          .chain(supervisor => notifyUserById(
            supervisor.id,
            'Your subordinate ' + employee.name + ' is employee of the month!'
          ))
    );

Дело в том, что каждая функция здесь является полностью чистой функцией;на самом деле ничего не произошло, пока я на самом деле action.run() не запустил его в движение.

Точно так же мы могли бы иметь

declare const exchangeRate: Program<number>;

function dollarsToEuros(dollars: number): Program<number> {
  return exchangeRate.map(rate => dollars * rate);
}

и exchangeRate могла бы быть программой, которая просматривает изменяемое значение,

let privateExchangeRate: number = 0;
export function setExchangeRate(value: number): Program<void> {
  return new Program(() => { privateExchangeRate = value; return Promise.resolve(undefined); });
}
export const exchangeRate: Program<number> = new Program(() => {
  return Promise.resolve(privateExchangeRate); 
});

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...