【问题标题】:Branch and merge in fp-ts在 fp-ts 中分支和合并
【发布时间】:2021-12-04 01:41:13
【问题描述】:

我有以下pipe


pipe(
  getProduct(), //step 1
  chain((it) => E.right(Object.assign({}, it, { tax: 0.1 }))), //step 2
  chain((it) => E.right(Object.assign({}, it, { delivery: 0.15 }))), //step 3
  //chain((it) => E.left('ERROR')), //step 4
  E.fold( //final step
    (e) => {
      console.log(`error: ${e}`)
    },
    (it) => {
      console.log(
        `ok ${it.count} ${it.pricePerItem} ${it.tax} ${it.delivery}`
      )
    }
  )
)

在哪里

getProduct = () => E.right({ count: 10, pricePerItem: 5 })

第 1 步产生输出 { count: 10, pricePerItem: 5 }

步骤 2 将步骤 1 的输出作为输入并产生输出{ count: 10, pricePerItem: 5, tax: 0.1 }

步骤 3 获取步骤 2 的输出 { count: 10, pricePerItem: 5, tax: 0.1 } 并产生输出 { count: 10, pricePerItem: 5, tax: 0.1, delivery: 0.15 }

第 4 步只是一个占位符,它可能会生成 left 以指示错误情况。我只是忽略了它。

它在pipe 中按预期工作。但我不想那样。

我希望第 2 步接受输入 { count: 10, pricePerItem: 5 } 并将 tax 添加到其中。同时,我希望第 3 步采用相同的输入 { count: 10, pricePerItem: 5 } 并将 delivery 添加到其中。

然后我希望第 4 步获取第 2 步和第 3 步的输出并将它们合并回来。

我在bind 和/或do notation 中看到了一些相关内容,例如在此answer 中,但不太确定。

那么与始终在管道中运行相比,流如何分支和合并?

更新 命令式编程中的等价物如下:

const products = getProducts() 
const productsWithTax = getProductsWithTax(products)
const productsWithDelivery = getProductsWithDelivery(products)

const productsWithTaxAndDelivery = getWithTaxAndDelivery(productsWithTax, productsWithDelivery)

重点是我不想要true 管道。

【问题讨论】:

  • 而您期望在单线程环境中并行执行...如何?
  • 不,不是并行执行。请参阅我的更新以获取命令式编程中的等效项

标签: functional-programming fp-ts


【解决方案1】:

您绝对可以在这里使用do 表示法。这是一个符合您所寻求的解决方案:

import * as E from 'fp-ts/Either';
import { Either } from 'fp-ts/Either'
import { pipe } from 'fp-ts/function'

// Your base product type
interface Product {
  count: number
  pricePerItem: number
}

// Something that has a tax added
interface Tax {
  tax: number
}

// Something that has a delivery added
interface Delivery {
  delivery: number
}

declare function getProduct(): Either<Error, Product>

declare function getTax(p: Product): Either<Error, Product & Tax>

declare function getDelivery(p: Product): Either<Error, Product & Delivery>

function solution1(): Either<Error, Product & Tax & Delivery> {
  return pipe(
    // begin the `do` notation
    E.Do,
    // (1)
    // bind your product to a variable `p`
    E.bind('p', getProduct),
    // (2)
    // use your product to get your product & tax, and bind that to `pTax`
    E.bind('pTax', ({ p }) => getTax(p)),
    // (3)
    // use your product to get your product & delivery, and bind that to `pDelivery`
    E.bind('pDelivery', ({ p }) => getDelivery(p)),
    // (4)
    // merge your original product, product w/tax, and product w/delivery
    E.map(({ p, pTax, pDelivery }) => ({ ...p, ...pTax, ...pDelivery }))
  );
}

诚然,这里有一些不必要的返回类型重叠,我们不得不在最后笨拙地合并对象。您可以将 taxdelivery 作为独立结果返回并在最后合并所有内容,而不是在函数之间返回扩展对象:

declare function getProduct(): Either<Error, Product>

declare function getTax(p: Product): Either<Error, number> // changed

declare function getDelivery(p: Product): Either<Error, number> // changed

function solution2(): Either<Error, Product & Tax & Delivery> {
  return pipe(
    E.Do,
    // get product
    E.bind('p', getProduct),
    // use product to get tax
    E.bind('tax', ({ p }) => getTax(p)),
    // use product to get delivery
    E.bind('delivery', ({ p }) => getDelivery(p)),
    // merge tax and delivery into product
    E.map(({ p, tax, delivery }) => ({ ...p, tax, delivery })) //
  );
}

请注意,即使我们使用do,也没有转义pipe。此外,使用 Either 将一直是同步的。如果不切换到 TaskEither 或类似的东西,您将无法获得并发。

【讨论】:

    猜你喜欢
    • 2022-01-07
    • 2021-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-10
    • 1970-01-01
    • 2023-02-01
    相关资源
    最近更新 更多