Fluture bimap 和 fold,有什么区别,我应该什么时候使用它们?

Fluture bimap and fold, what is the difference and when should I use them?

背景

我正在使用 Fluture 抽象 Futures。

假设我有一个发出 GET 请求的函数。这个函数可以成功也可以失败。

发出请求后,如果成功,则打印一条消息,如果失败,则记录错误并执行命令。

axios.get(endpoint, { timeout: timeoutMs })
    .fold(
        err =>
            logger.errorAsync( err )
            .chain( ( ) => cmd.getAsync("pm2 restart app")),
        response => logger.infoAsync( "Great success!" )
    );

研究

我一直在阅读 API,我发现 bimapfold 都对成功和错误应用了一个函数:

bimap:将左函数映射到拒绝值,或将右函数映射到分辨率值,具体取决于哪个存在。

fold:将左函数应用于拒绝值,或将右函数应用于分辨率值,具体取决于哪个存在,并用结果进行解析。

问题

如果你眼尖,你就会知道我的例子有效。我需要使用 bimap,但我不明白为什么。

问题

  1. 什么时候应该使用bimap,什么时候应该使用fold
  2. 它们之间的主要区别是什么?

一个人可能会使用 bimap 在一个步骤中同时映射 rejectionresolution,并且 Future 将保持 rejectedresolved 并进行新的计算。

另一方面,fold将同时处理拒绝解决以始终产生 resolution(您将两种情况合并为 resolution 一种情况)。人们会使用 fold 将两个结果包装成另一种类型(例如 Future Either a b)或将任何分支视为 successful.

因此,bimapfold 不同,因为第一个映射两种情况,第二个将任一情况转换为 分辨率

样本: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

让我们首先检查一下它们各自的类型签名:

bimap :: (a -> c) -> (b -> d) -> Future a b -> Future c d
fold  :: (a -> c) -> (b -> c) -> Future a b -> Future d c

差异非常细微,但很明显。有两个主要区别:

  1. 第二个参数的return值不同:在bimap中,两者 允许函数 return 不同的类型。在 fold 中,两个函数 必须 return 相同类型的值。
  2. 最终的 return 值不同:在 bimap 中,你得到一个 Future,其中 拒绝包含来自左侧函数的 returned 类型的值, 并且分辨率包含从右边开始 returned 类型的值 功能。在fold中,拒绝端包含一个全新的类型变量,它 尚未受到限制,决议方包含的价值 键入 return 由 both 函数编辑。

这太啰嗦了,可能有点难以解析。我将尝试在图表中将其可视化。

对于bimap,它看起来像下面这样。两个分支不交互:

             rej(x)  res(y)
                 |       |
                 |       |
bimap(f)(g):   f(x)    g(y)
                 |       |
                 V       V

对于fold,拒绝分支类型为"stops",resoltion分支将 继续 f(x) 的 return 值 g(y):

的 return 值
             rej(x)  res(y)
                 |       |
                 |       |
fold(f)(g):      ->  f(x)*g(y)
                         |
                         V

您可以随时使用 bimap 更改拒绝原因和 同时分辨率值。做 bimap (f) (g) 就像做 compose (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

扁平化 Monad 在函数式编程中很常见,通常 称为 join,可以像这样实现:

const join = chain(x => x)