FP-TS中Task、Lift与复杂Monad问题及代码优化咨询
Hey there! Let's tackle your fp-ts issues one by one, with clear fixes and explanations:
1. Why savePerson isn't executing
The main issue here is that when you use fold on your Either, the right branch returns a Task<void> but you never trigger its execution. Plus, using map on the outer Task leaves you with a Task that wraps either void (from the error log) or another Task (the save operation) — that inner save Task just sits idle.
To fix this, we need to use task.chain instead of map when handling the Either result. Since Task is a monad, chain lets us flatten nested Tasks and ensure the save operation actually runs when we call .run() on the final Task.
2. Replacing deprecated liftA2 with sequenceT
liftA2 was deprecated in favor of sequenceT for combining multiple applicatives into a tuple. Here's how to adapt your code:
sequenceT(task)(task1, task2)takes two Tasks and returns a single Task that resolves to a tuple of their results (e.g.,Task<[string, number]>for your name and age calls).- We then map over that tuple to apply your curried
buildPersonfunction — sincebuildPersonis curried, we can spread the tuple[name, age]intobuildPerson(name)(age).
3. Cleaner function composition with pipe
Your original code has nested map calls which can get hard to follow. Using fp-ts's pipe function lets us chain operations in a linear, readable flow. We can string together all steps: combining repo calls, building the person, validating, and handling success/failure — all without nesting.
Full Optimized Code
import { Task, task } from "fp-ts/lib/Task" import { Either, left, right, fold } from "fp-ts/lib/Either" import { curry, pipe } from "fp-ts/lib/function" import { sequenceT } from "fp-ts/lib/Apply" import { Repo } from "./repo" // Explicit type instead of `any` for better type safety type Person = { name: string; age: number } const buildPerson = curry((name: string, age: number): Either<Error, Person> => { if (name !== undefined && age !== undefined) { return right({ name, age }) } else { return left(Error("Missing parameter")) } }) const validatePerson = (person: Person): Either<Error, Person> => { if (person.age < 18) { return left(Error("Too Young")) } else { return right(person) } } // Simplified Task definition: fp-ts Task is just () => Promise<A> const getNameFromRepo = (repo: Repo): Task<string> => () => repo.getName() const getAgeFromRepo = (repo: Repo): Task<number> => () => repo.getAge() const savePerson = curry((person: Person, repo: Repo): Task<void> => () => { console.log(person) return repo.setPerson(person) }) const hello = async () => { const repo = new Repo() await pipe( // Combine two repo Tasks into a single Task returning [name, age] sequenceT(task)(getNameFromRepo(repo), getAgeFromRepo(repo)), // Apply buildPerson to the tuple result task.map(([name, age]) => buildPerson(name)(age)), // Chain validation onto the Either result task.map(eitherPerson => eitherPerson.chain(validatePerson)), // Convert Either to Task<void> using fold + chain task.chain(fold( // Handle error: log and return a resolved Task (err) => task.of(console.log(err.message)), // Handle success: run the save Task (validPerson) => savePerson(validPerson)(repo) )) ).run() } hello()
Key improvements in this code:
- Simplified Task definitions (no need for
new Task()— just return the promise-producing function) - Explicit type for
Personinstead ofanyto enforce type safety - Linear flow using
pipefor readability - Proper use of
task.chainto ensuresavePersonexecutes - Clear separation of concerns for each step in the workflow
内容的提问来源于stack exchange,提问作者Ezequiel Lewin

