在 fp-ts 中分支和合并
Branch and merge in fp-ts
我有以下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
and/or do notation
的东西,比如这个 但不太确定。
那么如何将流分支和合并,而不是总是在管道中 运行?
更新
命令式编程中的等价物如下:
const products = getProducts()
const productsWithTax = getProductsWithTax(products)
const productsWithDelivery = getProductsWithDelivery(products)
const productsWithTaxAndDelivery = getWithTaxAndDelivery(productsWithTax, productsWithDelivery)
重点是我不想要 true
管道。
这里绝对可以使用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 }))
);
}
诚然,这里有一些不必要的 return 类型重叠,我们不得不在最后笨拙地合并对象。您可以 return tax
和 delivery
作为独立结果,而不是在函数之间 returning 扩展对象,并在最后合并所有内容:
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
或类似的东西,您将无法获得并发。
我有以下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
and/or do notation
的东西,比如这个
那么如何将流分支和合并,而不是总是在管道中 运行?
更新 命令式编程中的等价物如下:
const products = getProducts()
const productsWithTax = getProductsWithTax(products)
const productsWithDelivery = getProductsWithDelivery(products)
const productsWithTaxAndDelivery = getWithTaxAndDelivery(productsWithTax, productsWithDelivery)
重点是我不想要 true
管道。
这里绝对可以使用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 }))
);
}
诚然,这里有一些不必要的 return 类型重叠,我们不得不在最后笨拙地合并对象。您可以 return tax
和 delivery
作为独立结果,而不是在函数之间 returning 扩展对象,并在最后合并所有内容:
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
或类似的东西,您将无法获得并发。