Fluture bimap и fold, в чем разница и когда я должен их использовать?
Фон
Я использую Fluture для абстрагирования фьючерсов.
Допустим, у меня есть функция, которая делает запрос GET. Эта функция может быть успешной или неудачной.
После выполнения запроса, в случае успеха, он печатает сообщение, в случае неудачи регистрирует ошибку и выполняет команду.
axios.get(endpoint, { timeout: timeoutMs })
.fold(
err =>
logger.errorAsync( err )
.chain( ( ) => cmd.getAsync("pm2 restart app")),
response => logger.infoAsync( "Great success!" )
);
Исследование
Я читал API, и я обнаружил, что bimap
а также fold
оба применяют функцию к успеху и ошибке:
bimap: сопоставляет левую функцию со значением отклонения или правую функцию со значением разрешения, в зависимости от того, какая присутствует.
сложить: применяет левую функцию к значению отклонения или правую функцию к значению разрешения, в зависимости от того, какое значение присутствует, и разрешается с результатом.
проблема
Если у вас острый взгляд, вы поймете, что мой пример не работает. Мне нужно использовать bimap
но я не понимаю почему.
Вопросы
- Когда я должен использовать
bimap
и когда я должен использоватьfold
? - Каковы основные различия между ними?
2 ответа
Давайте сначала рассмотрим их соответствующие типы подписей:
Future.prototype.bimap :: Future a b ~> (a -> c, b -> d) -> Future c d
Future.prototype.fold :: Future a b ~> (a -> c, b -> c) -> Future x c
Разница довольно тонкая, но заметная. Есть два основных различия:
- Возвращаемое значение второго аргумента отличается: в
bimap
обе функции могут возвращать разные типы. Вfold
обе функции должны возвращать значение одного типа. - Конечное возвращаемое значение отличается: в
bimap
вы возвращаетесь в Future, где отклонение содержит значение типа, возвращаемого левой функцией, а разрешение содержит значение типа, возвращаемого правой функцией. Вfold
сторона отклонения содержит целую переменную нового типа, которая еще должна быть ограничена, а сторона разрешения содержит значение типа, возвращаемого обеими функциями.
Это довольно много, и, возможно, немного трудно разобрать. Я постараюсь визуализировать это в диаграммах.
За bimap
Похоже на следующее. Две ветви не взаимодействуют:
rej(x) res(y)
| |
| |
bimap(f, g): f(x) g(y)
| |
V V
За fold
, ветвь отклонения "останавливается", и ветвь разрешения будет продолжаться с возвращаемым значением из f(x)
или возвращаемое значение из g(y)
:
rej(x) res(y)
| |
| |
fold(f, g): -> f(x)*g(y)
|
V
Ты можешь использовать bimap
всякий раз, когда вы хотите изменить причину отклонения и значение разрешения одновременно. дела .bimap(f, g)
это как делать.mapRej(f).map(g)
,
Ты можешь использовать fold
всякий раз, когда вы хотите переместить отклонение в ветку разрешения. В вашем случае это то, что вы хотите. Причина, по которой ваш пример не работает, состоит в том, что вы в конечном итоге получаете будущее будущего, которое вы должны сгладить:
axios.get(endpoint, { timeout: timeoutMs })
.fold(
err =>
logger.errorAsync( err )
.chain( ( ) => cmd.getAsync("pm2 restart app")),
response => logger.infoAsync( "Great success!" )
)
.chain(inner => inner); //<-- Flatten
Сглаживание монады очень распространено в функциональном программировании и обычно называется join
, который может быть реализован как:
const join = Future.chain(x => x)
Можно использовать bimap
и отклонение карты и разрешение в один шаг, и Future
останется отклоненным или разрешенным с помощью нового вычисления.
С другой стороны, fold
будет обрабатывать как отклонение, так и разрешение, чтобы всегда получать разрешение (вы складываете оба случая в одно разрешение). Один будет использовать fold
либо обернуть оба результата в другой тип (например, Future Either a b
) или рассматривать любую ветку как успешную.
Таким образом, bimap
отличается от fold
потому что первый отображает оба случая, а второй превращает любой случай в разрешение.
Образец: bimap
:
const flag = true
const eventualNumber1 = !flag ? Future.reject (1) : Future.of (2)
const eventualNumber2 = Future.bimap (x => x * 2) (x => x + 1) (eventualNumber1)
// it'll output 3.
Future.fork (console.log) (console.log) (eventualNumber2)
Образец: fold
:
const flag = false
const eventualNumber1 = !flag ? Future.reject (1) : Future.of (2)
const eventualNumber2 = Future.fold (x => x * 2) (x => x + 1) (eventualNumber1)
// It'll output 2 even when the Future represents a rejection
Future.value (console.log) (eventualNumber2)
Обратите внимание, как fold
дает мне полную гарантию того, что eventualNumber2
это разрешение, поэтому я использую Future.value
которые обрабатывают только разрешения!