fp-ts Pipe and Flow Composition by whatiskadudoing/fp-ts-skills
npx skills add https://github.com/whatiskadudoing/fp-ts-skills --skill 'fp-ts Pipe and Flow Composition'函数组合是函数式编程的核心。fp-ts 提供了两个强大的工具来组合函数:pipe 和 flow。本指南涵盖了构建优雅、类型安全的管道所需的一切知识。
import { pipe, flow, identity } from 'fp-ts/function'
pipe 接受一个值,并将其传递给一系列函数,立即执行。
// pipe(value, fn1, fn2, fn3) === fn3(fn2(fn1(value)))
const result = pipe(
5,
n => n * 2, // 10
n => n + 1, // 11
n => `Result: ${n}` // "Result: 11"
)
在以下情况使用 pipe:
flow 将函数组合成一个新函数,但不执行它们。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
// flow(fn1, fn2, fn3) === (x) => fn3(fn2(fn1(x)))
const processNumber = flow(
(n: number) => n * 2,
n => n + 1,
n => `Result: ${n}`
)
processNumber(5) // "Result: 11"
processNumber(10) // "Result: 21"
在以下情况使用 flow:
| 方面 | pipe | flow |
|---|---|---|
| 执行 | 立即 | 延迟 |
| 第一个参数 | 值 | 函数 |
| 返回 | 转换后的值 | 新函数 |
| 使用场景 | 立即转换数据 | 创建可重用的转换 |
// 这些是等价的:
const result1 = pipe(5, double, increment, toString)
const result2 = flow(double, increment, toString)(5)
大多数 fp-ts 操作返回一元函数,使它们非常适合组合。
import { pipe } from 'fp-ts/function'
import * as A from 'fp-ts/Array'
const numbers = [1, 2, 3, 4, 5]
const result = pipe(
numbers,
A.filter(n => n % 2 === 0),
A.map(n => n * 10)
)
// [20, 40]
当需要使用多参数函数时,使用柯里化或部分应用。
// 方法 1:内联箭头函数
const add = (a: number, b: number) => a + b
pipe(
5,
n => add(n, 10), // 包装在箭头函数中
n => n * 2
)
// 方法 2:柯里化版本
const addCurried = (b: number) => (a: number) => a + b
pipe(
5,
addCurried(10), // 简洁的组合
n => n * 2
)
// 方法 3:使用 fp-ts 柯里化工具
import { curry2 } from 'fp-ts-std/Function'
const addC = curry2(add)
pipe(
5,
addC(10),
n => n * 2
)
import { pipe } from 'fp-ts/function'
import * as A from 'fp-ts/Array'
import * as NEA from 'fp-ts/NonEmptyArray'
interface User {
id: number
name: string
age: number
active: boolean
}
const users: User[] = [
{ id: 1, name: 'Alice', age: 30, active: true },
{ id: 2, name: 'Bob', age: 25, active: false },
{ id: 3, name: 'Charlie', age: 35, active: true },
]
// 复杂的转换管道
const activeUserNames = pipe(
users,
A.filter(u => u.active),
A.map(u => u.name),
A.sort(S.Ord)
)
// ['Alice', 'Charlie']
// 带分组
import * as R from 'fp-ts/Record'
import * as S from 'fp-ts/string'
const usersByActiveStatus = pipe(
users,
A.groupBy(u => u.active ? 'active' : 'inactive')
)
// { active: [...], inactive: [...] }
import { pipe } from 'fp-ts/function'
import * as R from 'fp-ts/Record'
const scores: Record<string, number> = {
alice: 85,
bob: 92,
charlie: 78
}
const adjustedScores = pipe(
scores,
R.map(score => score * 1.1),
R.filter(score => score >= 85)
)
import { pipe, flow } from 'fp-ts/function'
import * as S from 'fp-ts/string'
const normalizeString = flow(
S.trim,
S.toLowerCase,
s => s.replace(/\s+/g, '-')
)
const slug = normalizeString(' Hello World ') // 'hello-world'
// 带验证
import * as O from 'fp-ts/Option'
const safeSlug = flow(
O.fromPredicate((s: string) => s.length > 0),
O.map(normalizeString)
)
import { pipe } from 'fp-ts/function'
import * as O from 'fp-ts/Option'
interface Config {
database?: {
host?: string
port?: number
}
}
const getConnectionString = (config: Config): O.Option<string> =>
pipe(
O.fromNullable(config.database),
O.flatMap(db => O.fromNullable(db.host)),
O.map(host => `postgresql://${host}`)
)
// 链式多个 Option 操作
const findUser = (id: number): O.Option<User> => { /* ... */ }
const getUserEmail = (user: User): O.Option<string> => { /* ... */ }
const validateEmail = (email: string): O.Option<string> => { /* ... */ }
const getValidatedEmail = (userId: number): O.Option<string> =>
pipe(
findUser(userId),
O.flatMap(getUserEmail),
O.flatMap(validateEmail)
)
import { pipe } from 'fp-ts/function'
import * as E from 'fp-ts/Either'
type ValidationError = { type: 'validation'; message: string }
type NetworkError = { type: 'network'; message: string }
type AppError = ValidationError | NetworkError
const validateAge = (age: number): E.Either<ValidationError, number> =>
age >= 0 && age <= 150
? E.right(age)
: E.left({ type: 'validation', message: 'Invalid age' })
const validateName = (name: string): E.Either<ValidationError, string> =>
name.length >= 2
? E.right(name)
: E.left({ type: 'validation', message: 'Name too short' })
// 顺序验证(在第一个错误处失败)
const validateUser = (name: string, age: number) =>
pipe(
E.Do,
E.bind('name', () => validateName(name)),
E.bind('age', () => validateAge(age)),
E.map(({ name, age }) => ({ name, age, createdAt: new Date() }))
)
// 使用 Validation 累积错误
import * as A from 'fp-ts/Apply'
import * as NEA from 'fp-ts/NonEmptyArray'
type ValidationErrors = NEA.NonEmptyArray<string>
type Validation<A> = E.Either<ValidationErrors, A>
const applicativeValidation = E.getApplicativeValidation(NEA.getSemigroup<string>())
const validateUserAll = (name: string, age: number) =>
pipe(
A.sequenceS(applicativeValidation)({
name: validateName(name),
age: validateAge(age)
}),
E.map(({ name, age }) => ({ name, age }))
)
import { pipe } from 'fp-ts/function'
import * as T from 'fp-ts/Task'
import * as TE from 'fp-ts/TaskEither'
// 组合异步操作
const fetchUser = (id: number): TE.TaskEither<Error, User> =>
TE.tryCatch(
() => fetch(`/api/users/${id}`).then(r => r.json()),
(error) => new Error(String(error))
)
const fetchUserPosts = (userId: number): TE.TaskEither<Error, Post[]> =>
TE.tryCatch(
() => fetch(`/api/users/${userId}/posts`).then(r => r.json()),
(error) => new Error(String(error))
)
const getUserWithPosts = (id: number): TE.TaskEither<Error, UserWithPosts> =>
pipe(
fetchUser(id),
TE.flatMap(user =>
pipe(
fetchUserPosts(user.id),
TE.map(posts => ({ ...user, posts }))
)
)
)
// 并行执行
import * as A from 'fp-ts/Array'
const fetchAllUsers = (ids: number[]): TE.TaskEither<Error, User[]> =>
pipe(
ids,
A.map(fetchUser),
A.sequence(TE.ApplicativePar) // 并行执行
)
import { pipe } from 'fp-ts/function'
import * as RTE from 'fp-ts/ReaderTaskEither'
interface Dependencies {
userRepo: UserRepository
emailService: EmailService
logger: Logger
}
const getUser = (id: number): RTE.ReaderTaskEither<Dependencies, Error, User> =>
pipe(
RTE.ask<Dependencies>(),
RTE.flatMapTaskEither(deps => deps.userRepo.findById(id))
)
const sendWelcomeEmail = (user: User): RTE.ReaderTaskEither<Dependencies, Error, void> =>
pipe(
RTE.ask<Dependencies>(),
RTE.flatMapTaskEither(deps => deps.emailService.send(user.email, 'Welcome!'))
)
const onboardUser = (id: number): RTE.ReaderTaskEither<Dependencies, Error, User> =>
pipe(
getUser(id),
RTE.tap(sendWelcomeEmail),
RTE.tap(user =>
RTE.fromTask(deps => deps.logger.info(`Onboarded: ${user.name}`))
)
)
// 好:每个步骤做一件事
const processOrder = pipe(
order,
validateOrder,
calculateTotals,
applyDiscounts,
formatForDisplay
)
// 避免:大型内联函数
const processOrder = pipe(
order,
o => {
// 50 行验证、计算和格式化代码
}
)
// 好:命名函数解释意图
const isAdult = (user: User) => user.age >= 18
const formatName = (user: User) => `${user.firstName} ${user.lastName}`
const adultNames = pipe(
users,
A.filter(isAdult),
A.map(formatName)
)
// 不够清晰:内联匿名函数
const adultNames = pipe(
users,
A.filter(u => u.age >= 18),
A.map(u => `${u.firstName} ${u.lastName}`)
)
// 定义可重用的管道
const normalizeEmail = flow(
S.trim,
S.toLowerCase
)
const validateEmailFormat = flow(
O.fromPredicate((s: string) => s.includes('@')),
O.filter(s => s.length >= 5)
)
// 组合它们
const processEmail = flow(
normalizeEmail,
validateEmailFormat
)
// 好:带注释的逻辑分组
const processUsers = pipe(
users,
// 过滤
A.filter(isActive),
A.filter(isVerified),
// 转换
A.map(enrichWithMetadata),
A.map(formatForAPI),
// 排序
A.sort(byCreatedAt)
)
// 好:在可以有意义地响应的地方处理错误
const getUserSafely = (id: number) =>
pipe(
fetchUser(id),
TE.mapLeft(toAppError),
TE.orElse(error =>
error.type === 'not_found'
? TE.right(defaultUser)
: TE.left(error)
)
)
// 好:当步骤依赖于先前结果时很清晰
const createOrder = pipe(
E.Do,
E.bind('user', () => validateUser(userData)),
E.bind('items', () => validateItems(itemsData)),
E.bind('shipping', ({ user }) => calculateShipping(user.address)),
E.bind('total', ({ items, shipping }) => calculateTotal(items, shipping)),
E.map(({ user, items, total }) => ({ user, items, total }))
)
const processInput = flow(
S.trim,
O.fromPredicate(s => s.length > 0),
O.map(S.toLowerCase),
O.filter(isValidFormat)
)
const syncUser = (id: number) =>
pipe(
fetchExternalUser(id),
TE.map(transformToInternalUser),
TE.flatMap(saveUser),
TE.map(user => ({ success: true, user }))
)
const fetchDashboardData = pipe(
TE.Do,
TE.apS('users', fetchUsers()),
TE.apS('orders', fetchOrders()),
TE.apS('metrics', fetchMetrics()),
TE.map(({ users, orders, metrics }) =>
buildDashboard(users, orders, metrics)
)
)
const processPayment = (paymentData: PaymentData) =>
pipe(
validatePayment(paymentData),
TE.flatMap(checkFunds),
TE.flatMap(reserveFunds),
TE.flatMap(processTransaction),
TE.flatMap(sendConfirmation)
)
const getConfig = pipe(
getEnvConfig(),
O.alt(() => getFileConfig()),
O.alt(() => getDefaultConfig()),
O.getOrElse(() => hardcodedDefaults)
)
const processUser = (user: User) =>
pipe(
user,
O.fromPredicate(isAdmin),
O.match(
() => processRegularUser(user),
() => processAdminUser(user)
)
)
const processAll = (items: Item[]) =>
pipe(
items,
A.map(processItem),
A.separate, // 分割为 { left: errors[], right: successes[] }
({ left: errors, right: successes }) => ({
successes,
errors,
successRate: successes.length / items.length
})
)
// 好:类型贯穿始终
const result = pipe(
users,
A.filter(u => u.active), // TypeScript 知道 u 是 User
A.map(u => u.name) // TypeScript 知道结果是 string[]
)
// 仅在必要时添加注解
const processNumber = flow(
(n: number) => n * 2, // 第一个函数需要注解
n => n + 1, // 其余部分可以推断
String
)
// 好:公共函数的清晰契约
const processOrder: (order: Order) => E.Either<OrderError, ProcessedOrder> =
flow(
validateOrder,
E.flatMap(calculateTotals),
E.map(formatOrder)
)
pipe 开销最小 - 本质上只是函数调用flow 创建一个新函数,创建时有轻微开销,但执行时没有pipe 稍快一些flow 避免了重新创建// 效率较低:多次数组迭代
const result = pipe(
largeArray,
A.filter(predicate1),
A.filter(predicate2),
A.map(transform)
)
// 更高效:组合谓词
const result = pipe(
largeArray,
A.filter(x => predicate1(x) && predicate2(x)),
A.map(transform)
)
// 或者使用 filterMap 进行过滤 + 映射
const result = pipe(
largeArray,
A.filterMap(x =>
predicate1(x) && predicate2(x)
? O.some(transform(x))
: O.none
)
)
import { tap } from 'fp-ts/function'
const debugPipeline = pipe(
data,
tap(x => console.log('Step 1:', x)),
transform1,
tap(x => console.log('Step 2:', x)),
transform2
)
const trace = <A>(label: string) => (a: A): A => {
console.log(label, a)
return a
}
const result = pipe(
data,
trace('input'),
transform1,
trace('after transform1'),
transform2,
trace('final')
)
| 使用场景 | 使用 pipe | 使用 flow |
|---|---|---|
| 立即转换一个值 | 是 | 否 |
| 创建可重用函数 | 否 | 是 |
| 作为回调传递 | 否 | 是 |
| 一次性转换 | 是 | 否 |
| 构建实用工具库 | 否 | 是 |
| fp-ts 操作链 | 是 | 两者皆可 |
记住:
pipe(value, f, g) 立即执行flow(f, g) 返回一个函数供稍后使用每周安装次数
–
代码仓库
GitHub 星标数
4
首次出现时间
–
安全审计
Function composition is the heart of functional programming. fp-ts provides two powerful utilities for composing functions: pipe and flow. This guide covers everything you need to build elegant, type-safe pipelines.
import { pipe, flow, identity } from 'fp-ts/function'
pipe takes a value and passes it through a series of functions, executing immediately.
// pipe(value, fn1, fn2, fn3) === fn3(fn2(fn1(value)))
const result = pipe(
5,
n => n * 2, // 10
n => n + 1, // 11
n => `Result: ${n}` // "Result: 11"
)
Use pipe when:
flow composes functions into a new function without executing them.
// flow(fn1, fn2, fn3) === (x) => fn3(fn2(fn1(x)))
const processNumber = flow(
(n: number) => n * 2,
n => n + 1,
n => `Result: ${n}`
)
processNumber(5) // "Result: 11"
processNumber(10) // "Result: 21"
Use flow when:
| Aspect | pipe | flow |
|---|---|---|
| Execution | Immediate | Deferred |
| First argument | Value | Function |
| Returns | Transformed value | New function |
| Use case | Transform data now | Create reusable transform |
// These are equivalent:
const result1 = pipe(5, double, increment, toString)
const result2 = flow(double, increment, toString)(5)
Most fp-ts operations return unary functions, making them ideal for composition.
import { pipe } from 'fp-ts/function'
import * as A from 'fp-ts/Array'
const numbers = [1, 2, 3, 4, 5]
const result = pipe(
numbers,
A.filter(n => n % 2 === 0),
A.map(n => n * 10)
)
// [20, 40]
When you need to use functions with multiple arguments, use currying or partial application.
// Method 1: Inline arrow function
const add = (a: number, b: number) => a + b
pipe(
5,
n => add(n, 10), // Wrap in arrow function
n => n * 2
)
// Method 2: Curried version
const addCurried = (b: number) => (a: number) => a + b
pipe(
5,
addCurried(10), // Clean composition
n => n * 2
)
// Method 3: Using fp-ts curry utilities
import { curry2 } from 'fp-ts-std/Function'
const addC = curry2(add)
pipe(
5,
addC(10),
n => n * 2
)
import { pipe } from 'fp-ts/function'
import * as A from 'fp-ts/Array'
import * as NEA from 'fp-ts/NonEmptyArray'
interface User {
id: number
name: string
age: number
active: boolean
}
const users: User[] = [
{ id: 1, name: 'Alice', age: 30, active: true },
{ id: 2, name: 'Bob', age: 25, active: false },
{ id: 3, name: 'Charlie', age: 35, active: true },
]
// Complex transformation pipeline
const activeUserNames = pipe(
users,
A.filter(u => u.active),
A.map(u => u.name),
A.sort(S.Ord)
)
// ['Alice', 'Charlie']
// With grouping
import * as R from 'fp-ts/Record'
import * as S from 'fp-ts/string'
const usersByActiveStatus = pipe(
users,
A.groupBy(u => u.active ? 'active' : 'inactive')
)
// { active: [...], inactive: [...] }
import { pipe } from 'fp-ts/function'
import * as R from 'fp-ts/Record'
const scores: Record<string, number> = {
alice: 85,
bob: 92,
charlie: 78
}
const adjustedScores = pipe(
scores,
R.map(score => score * 1.1),
R.filter(score => score >= 85)
)
import { pipe, flow } from 'fp-ts/function'
import * as S from 'fp-ts/string'
const normalizeString = flow(
S.trim,
S.toLowerCase,
s => s.replace(/\s+/g, '-')
)
const slug = normalizeString(' Hello World ') // 'hello-world'
// With validation
import * as O from 'fp-ts/Option'
const safeSlug = flow(
O.fromPredicate((s: string) => s.length > 0),
O.map(normalizeString)
)
import { pipe } from 'fp-ts/function'
import * as O from 'fp-ts/Option'
interface Config {
database?: {
host?: string
port?: number
}
}
const getConnectionString = (config: Config): O.Option<string> =>
pipe(
O.fromNullable(config.database),
O.flatMap(db => O.fromNullable(db.host)),
O.map(host => `postgresql://${host}`)
)
// Chaining multiple Option operations
const findUser = (id: number): O.Option<User> => { /* ... */ }
const getUserEmail = (user: User): O.Option<string> => { /* ... */ }
const validateEmail = (email: string): O.Option<string> => { /* ... */ }
const getValidatedEmail = (userId: number): O.Option<string> =>
pipe(
findUser(userId),
O.flatMap(getUserEmail),
O.flatMap(validateEmail)
)
import { pipe } from 'fp-ts/function'
import * as E from 'fp-ts/Either'
type ValidationError = { type: 'validation'; message: string }
type NetworkError = { type: 'network'; message: string }
type AppError = ValidationError | NetworkError
const validateAge = (age: number): E.Either<ValidationError, number> =>
age >= 0 && age <= 150
? E.right(age)
: E.left({ type: 'validation', message: 'Invalid age' })
const validateName = (name: string): E.Either<ValidationError, string> =>
name.length >= 2
? E.right(name)
: E.left({ type: 'validation', message: 'Name too short' })
// Sequential validation (fail on first error)
const validateUser = (name: string, age: number) =>
pipe(
E.Do,
E.bind('name', () => validateName(name)),
E.bind('age', () => validateAge(age)),
E.map(({ name, age }) => ({ name, age, createdAt: new Date() }))
)
// Accumulating errors with Validation
import * as A from 'fp-ts/Apply'
import * as NEA from 'fp-ts/NonEmptyArray'
type ValidationErrors = NEA.NonEmptyArray<string>
type Validation<A> = E.Either<ValidationErrors, A>
const applicativeValidation = E.getApplicativeValidation(NEA.getSemigroup<string>())
const validateUserAll = (name: string, age: number) =>
pipe(
A.sequenceS(applicativeValidation)({
name: validateName(name),
age: validateAge(age)
}),
E.map(({ name, age }) => ({ name, age }))
)
import { pipe } from 'fp-ts/function'
import * as T from 'fp-ts/Task'
import * as TE from 'fp-ts/TaskEither'
// Composing async operations
const fetchUser = (id: number): TE.TaskEither<Error, User> =>
TE.tryCatch(
() => fetch(`/api/users/${id}`).then(r => r.json()),
(error) => new Error(String(error))
)
const fetchUserPosts = (userId: number): TE.TaskEither<Error, Post[]> =>
TE.tryCatch(
() => fetch(`/api/users/${userId}/posts`).then(r => r.json()),
(error) => new Error(String(error))
)
const getUserWithPosts = (id: number): TE.TaskEither<Error, UserWithPosts> =>
pipe(
fetchUser(id),
TE.flatMap(user =>
pipe(
fetchUserPosts(user.id),
TE.map(posts => ({ ...user, posts }))
)
)
)
// Parallel execution
import * as A from 'fp-ts/Array'
const fetchAllUsers = (ids: number[]): TE.TaskEither<Error, User[]> =>
pipe(
ids,
A.map(fetchUser),
A.sequence(TE.ApplicativePar) // Parallel execution
)
import { pipe } from 'fp-ts/function'
import * as RTE from 'fp-ts/ReaderTaskEither'
interface Dependencies {
userRepo: UserRepository
emailService: EmailService
logger: Logger
}
const getUser = (id: number): RTE.ReaderTaskEither<Dependencies, Error, User> =>
pipe(
RTE.ask<Dependencies>(),
RTE.flatMapTaskEither(deps => deps.userRepo.findById(id))
)
const sendWelcomeEmail = (user: User): RTE.ReaderTaskEither<Dependencies, Error, void> =>
pipe(
RTE.ask<Dependencies>(),
RTE.flatMapTaskEither(deps => deps.emailService.send(user.email, 'Welcome!'))
)
const onboardUser = (id: number): RTE.ReaderTaskEither<Dependencies, Error, User> =>
pipe(
getUser(id),
RTE.tap(sendWelcomeEmail),
RTE.tap(user =>
RTE.fromTask(deps => deps.logger.info(`Onboarded: ${user.name}`))
)
)
// Good: Each step does one thing
const processOrder = pipe(
order,
validateOrder,
calculateTotals,
applyDiscounts,
formatForDisplay
)
// Avoid: Large inline functions
const processOrder = pipe(
order,
o => {
// 50 lines of validation, calculation, and formatting
}
)
// Good: Named functions explain intent
const isAdult = (user: User) => user.age >= 18
const formatName = (user: User) => `${user.firstName} ${user.lastName}`
const adultNames = pipe(
users,
A.filter(isAdult),
A.map(formatName)
)
// Less clear: Anonymous functions inline
const adultNames = pipe(
users,
A.filter(u => u.age >= 18),
A.map(u => `${u.firstName} ${u.lastName}`)
)
// Define reusable pipelines
const normalizeEmail = flow(
S.trim,
S.toLowerCase
)
const validateEmailFormat = flow(
O.fromPredicate((s: string) => s.includes('@')),
O.filter(s => s.length >= 5)
)
// Compose them
const processEmail = flow(
normalizeEmail,
validateEmailFormat
)
// Good: Logical grouping with comments
const processUsers = pipe(
users,
// Filter
A.filter(isActive),
A.filter(isVerified),
// Transform
A.map(enrichWithMetadata),
A.map(formatForAPI),
// Sort
A.sort(byCreatedAt)
)
// Good: Handle errors where you can meaningfully respond
const getUserSafely = (id: number) =>
pipe(
fetchUser(id),
TE.mapLeft(toAppError),
TE.orElse(error =>
error.type === 'not_found'
? TE.right(defaultUser)
: TE.left(error)
)
)
// Good: Clear when steps depend on previous results
const createOrder = pipe(
E.Do,
E.bind('user', () => validateUser(userData)),
E.bind('items', () => validateItems(itemsData)),
E.bind('shipping', ({ user }) => calculateShipping(user.address)),
E.bind('total', ({ items, shipping }) => calculateTotal(items, shipping)),
E.map(({ user, items, total }) => ({ user, items, total }))
)
const processInput = flow(
S.trim,
O.fromPredicate(s => s.length > 0),
O.map(S.toLowerCase),
O.filter(isValidFormat)
)
const syncUser = (id: number) =>
pipe(
fetchExternalUser(id),
TE.map(transformToInternalUser),
TE.flatMap(saveUser),
TE.map(user => ({ success: true, user }))
)
const fetchDashboardData = pipe(
TE.Do,
TE.apS('users', fetchUsers()),
TE.apS('orders', fetchOrders()),
TE.apS('metrics', fetchMetrics()),
TE.map(({ users, orders, metrics }) =>
buildDashboard(users, orders, metrics)
)
)
const processPayment = (paymentData: PaymentData) =>
pipe(
validatePayment(paymentData),
TE.flatMap(checkFunds),
TE.flatMap(reserveFunds),
TE.flatMap(processTransaction),
TE.flatMap(sendConfirmation)
)
const getConfig = pipe(
getEnvConfig(),
O.alt(() => getFileConfig()),
O.alt(() => getDefaultConfig()),
O.getOrElse(() => hardcodedDefaults)
)
const processUser = (user: User) =>
pipe(
user,
O.fromPredicate(isAdmin),
O.match(
() => processRegularUser(user),
() => processAdminUser(user)
)
)
const processAll = (items: Item[]) =>
pipe(
items,
A.map(processItem),
A.separate, // Split into { left: errors[], right: successes[] }
({ left: errors, right: successes }) => ({
successes,
errors,
successRate: successes.length / items.length
})
)
// Good: Types flow through
const result = pipe(
users,
A.filter(u => u.active), // TypeScript knows u is User
A.map(u => u.name) // TypeScript knows result is string[]
)
// Only annotate when necessary
const processNumber = flow(
(n: number) => n * 2, // First function needs annotation
n => n + 1, // Rest are inferred
String
)
// Good: Clear contract for public function
const processOrder: (order: Order) => E.Either<OrderError, ProcessedOrder> =
flow(
validateOrder,
E.flatMap(calculateTotals),
E.map(formatOrder)
)
pipe has minimal overhead - essentially just function callsflow creates a new function, slight overhead on creation but not on executionpipe is marginally fasterflow avoids recreation// Less efficient: Multiple array iterations
const result = pipe(
largeArray,
A.filter(predicate1),
A.filter(predicate2),
A.map(transform)
)
// More efficient: Combine predicates
const result = pipe(
largeArray,
A.filter(x => predicate1(x) && predicate2(x)),
A.map(transform)
)
// Or use filterMap for filter + map
const result = pipe(
largeArray,
A.filterMap(x =>
predicate1(x) && predicate2(x)
? O.some(transform(x))
: O.none
)
)
import { tap } from 'fp-ts/function'
const debugPipeline = pipe(
data,
tap(x => console.log('Step 1:', x)),
transform1,
tap(x => console.log('Step 2:', x)),
transform2
)
const trace = <A>(label: string) => (a: A): A => {
console.log(label, a)
return a
}
const result = pipe(
data,
trace('input'),
transform1,
trace('after transform1'),
transform2,
trace('final')
)
| Use Case | Use pipe | Use flow |
|---|---|---|
| Transform a value now | Yes | No |
| Create reusable function | No | Yes |
| Pass as callback | No | Yes |
| One-off transformation | Yes | No |
| Build utility library | No | Yes |
| fp-ts operations chain | Yes | Either |
Remember:
pipe(value, f, g) executes immediatelyflow(f, g) returns a function for laterWeekly Installs
–
Repository
GitHub Stars
4
First Seen
–
Security Audits
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
109,600 周安装