Kev*_*nle 4 functional-programming fp-ts
当没有一个函数是异步的时,我有以下程序可以正常工作。
interface Product {
count: number
pricePerItem: number
}
interface Tax {
tax: number
}
interface Delivery {
delivery: number
}
interface PTD { //ProductTaxDelivery
p: Product
t: number
d: number
}
function getProduct(): Either<Error, Product> {
return E.right({ count: 10, pricePerItem: 5 })
}
function getTax(p: Product): Either<Error, number> {
return E.right(p.pricePerItem * p.count * 0.085)
}
function getDelivery(p: Product): Either<Error, number> {
return E.right(p.count * 0.05)
//or maybe return E.left(Error('some error in delivery happened'))
}
function run(): Either<Error, PTD> {
return pipe(
E.Do,
E.bind('p', getProduct),
E.bind('tax', ({p}) => getTax(p)),
E.bind('delivery', ({p}) => getDelivery(p)),
E.map(({ p, tax, delivery }) => ({ p, t: tax, d: delivery }))
)
}
function main() {
pipe(
run(),
E.fold(
(e) => {
console.log(`error: ${e}`)
},
(it) => {
console.log(`ok ${it.p.count} ${it.p.pricePerItem} ${it.t} ${it.d}`)
}
)
)
}
main()
Run Code Online (Sandbox Code Playgroud)
我遇到的问题是,例如,如果我的函数之一getDelivery()是异步的,那么我不知道如何解决它。
这是我尝试过的:
function getDelivery(p: Product): TaskEither<Error, number> {
return TE.right(p.count * 0.05)
}
TE.bind('delivery', ({p}) => getDelivery(p)),
Run Code Online (Sandbox Code Playgroud)
以及许多其他变体,但最终都导致编译器错误。
命令式风格的等价物是这样的:
const getDelivery = async (p: Product) => {
return await something()
}
const run = async (): PTD => {
const product = getProduct()
const tax = getTax(product)
const delivery = await getDelivery(product)
return {
p: product, t: tax, d: delivery
}
}
Run Code Online (Sandbox Code Playgroud)
使用 的正确功能方式(我认为涉及 和Either)TaskEither是什么fp-ts?
更新:我还尝试将 Either 替换为 TaskEither,将 E 替换为 TE,但现在的问题是当我尝试fold在main(). 这是替换的代码:
function getProduct(): TaskEither<Error, Product> {
return TE.right({ count: 10, pricePerItem: 5 })
}
function getTax(p: Product): TaskEither<Error, number> {
return TE.right(p.pricePerItem * p.count * 0.085)
}
function getDelivery(p: Product): TaskEither<Error, number> {
return TE.right(p.count * 0.05)
}
function run(): TaskEither<Error, PTD> {
return pipe(
TE.Do,
TE.bind('p', getProduct),
TE.bind('tax', ({ p }) => getTax(p)),
TE.bind('delivery', ({ p }) => getDelivery(p)),
TE.map(({ p, tax, delivery }) => ({ p, t: tax, d: delivery }))
)
}
function main() {
pipe(
run(),
TE.fold(
(e) => {
console.log(`error: ${e}`)
},
(it) => {
console.log(`ok ${it.p.count} ${it.p.pricePerItem} ${it.t} ${it.d}`)
//doNonFunctional()
}
)
)
}
main()
Run Code Online (Sandbox Code Playgroud)
在 line with 上(e) => {,编译器错误显示:
error TS2345: Argument of type '(e: Error) => void' is not assignable to parameter of type '(e: Error) => Task<unknown>'.
Type 'void' is not assignable to type 'Task<unknown>'.
Run Code Online (Sandbox Code Playgroud)
更新2 好的,所以我得到了可以编译的代码,但程序运行时没有输出
const printError = (e: Error): T.Task<unknown> => {
console.log(`error: ${e}`)
return () => Promise.resolve()
}
const printPTD = (ptd: PTD): T.Task<unknown> => {
console.log(`ok ${ptd.p.count} ${ptd.p.pricePerItem} ${ptd.t} ${ptd.d}`)
return () => Promise.resolve()
}
function run(): TaskEither<Error, PTD> {
return pipe(
TE.Do,
TE.bind('p', getProduct),
TE.bind('tax', ({ p }) => getTax(p)),
TE.bind('delivery', ({ p }) => getDelivery(p)),
TE.map(({ p, tax, delivery }) => ({ p, t: tax, d: delivery }))
)
}
function main() {
pipe(
run(),
TE.fold(
(e) => printError(e),
(ptd) => printPTD(ptd)
)
)
}
main()
Run Code Online (Sandbox Code Playgroud)
问题是当您创建Taskin mainwith时pipe,您实际上并没有运行任何东西。
Task定义方式如下:
interface Task<A> {
(): Promise<A>
}
// same as type Task<A> = () => Promise<A>
Run Code Online (Sandbox Code Playgroud)
因为Task是一个thunk,所以你需要调用它来实际执行代码。
async function main(): Promise<void> {
await pipe(
// ...
// vv note the call here
)()
}
main()
Run Code Online (Sandbox Code Playgroud)
但是,我会这样做:
const main: T.Task<void> = pipe(/* ... */)
main()
Run Code Online (Sandbox Code Playgroud)
同样,run不需要是一个函数;有可能const run = pipe(/* ... */)。
此外,还有一个Console模块提供返回IO(副作用操作的类型)的日志函数。
你的代码可以写成
import * as Console from 'fp-ts/Console'
import * as E from 'fp-ts/Either'
import * as T from 'fp-ts/Task'
import * as TE from 'fp-ts/TaskEither'
import {pipe} from 'fp-ts/function'
// <A>(a: A) => Task<void>
const taskLog = T.fromIOK(Console.log)
// You can still keep getProduct and getTask synchronous
function getProduct(): E.Either<Error, Product> { /* ... */ }
function getTax(p: Product): E.Either<Error, number> { /* ... */ }
function getDelivery(p: Product): TE.TaskEither<Error, number> { /* ... */ }
const run: TE.TaskEither<Error, PTD> = pipe(
TE.Do,
// See below for what TE.fromEither(K) does
TE.bind('p', TE.fromEitherK(getProduct)),
TE.bind('tax', ({p}) => TE.fromEither(getTax(p))),
TE.bind('delivery', ({p}) => getDelivery(p)),
TE.map(({p, tax, delivery}) => ({p, t: tax, d: delivery}))
)
const main: T.Task<void> = pipe(
run,
TE.fold(
e => taskLog(`error: ${e}`),
it => taskLog(`ok ${it.p.count} ${it.p.pricePerItem} ${it.t} ${it.d}`)
)
)
main().catch(console.error)
Run Code Online (Sandbox Code Playgroud)
TE.fromEither将 an 转换Either为 a TaskEither:
export declare const fromEither: NaturalTransformation22<'Either', 'TaskEither'>
// same as
export declare const fromEither: <E, A>(fa: Either<E, A>) => TaskEither<E, A>
Run Code Online (Sandbox Code Playgroud)
TE.fromEitherKfromEither与但对于函数相同:
export declare const fromEitherK: <E, A extends readonly unknown[], B>(f: (...a: A) => Either<E, B>) => (...a: A) => TaskEither<E, B>
Run Code Online (Sandbox Code Playgroud)
您现在可能可以猜到T.fromIOK(used for taskLog) 的作用:
export declare const fromIOK: <A, B>(f: (...a: A) => IO<B>) => (...a: A) => Task<B>
Run Code Online (Sandbox Code Playgroud)