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 но я не понимаю почему.

Вопросы

  1. Когда я должен использовать bimap и когда я должен использовать fold?
  2. Каковы основные различия между ними?

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

Разница довольно тонкая, но заметная. Есть два основных различия:

  1. Возвращаемое значение второго аргумента отличается: в bimapобе функции могут возвращать разные типы. В foldобе функции должны возвращать значение одного типа.
  2. Конечное возвращаемое значение отличается: в 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 которые обрабатывают только разрешения!

Другие вопросы по тегам