核心模块介绍:
Option
用于处理可能为空的值
主要类型:Some(值存在), None(值不存在)
常用函数:
some() - 创建Some实例
none - 创建None实例
isSome() - 检查是否为Some
isNone() - 检查是否为None
import { pipe } from 'fp-ts/function';
import { some, none } from 'fp-ts/Option';
import { fold, map, getOrElse } from 'fp-ts/Option';
console.log(
pipe(
some(5),
map<number, number>(n => n * 2) // Some(10)
)
);
console.log(
pipe(
none,
getOrElse(() => 0) // 返回 0
)
);
console.log(
// fold: 处理 Some 和 None 两种情况
pipe(
some(5),
fold(
() => 'nothing', // None 的情况
value => `got ${value}` // Some 的情况
)
)
);
// 3. 实际应用示例
const findUser = (users: User[], id: number): Option<User> => {
const user = users.find(u => u.id === id)
return user ? some(user) : none
}
Either
用于处理可能出错的计算
主要类型:Right(成功), Left(失败)
常用函数:
right() - 创建Right实例
left() - 创建Left实例
isRight() - 检查是否为Right
isLeft() - 检查是否为Left
import { pipe } from 'fp-ts/function';
import { Either, left, right } from 'fp-ts/Either';
import { fold, map, getOrElse } from 'fp-ts/Either';
// 1. 创建 Either
const success = right('success'); // Right('success')
const failure = left('error'); // Left('error')
// map: 转换 Right 中的值
pipe(
right(5),
map(n => n * 2) // Right(10)
);
// getOrElse: 提供默认值
pipe(
left('error'),
getOrElse(() => 0) // 返回 0
);
// fold: 处理 Both Left 和 Right
pipe(
right(5),
fold(
error => `Error: ${error}`, // Left 的情况
value => `Success: ${value}` // Right 的情况
)
);
// 3. 实际应用示例
interface User {
id: number;
name: string;
}
const validateUser = (input: unknown): Either<string, User> => {
if (typeof input !== 'object' || input === null) {
return left('Invalid input');
}
const user = input as any;
if (typeof user.id !== 'number' || typeof user.name !== 'string') {
return left('Invalid user format');
}
return right({ id: user.id, name: user.name });
};
// 使用示例
const result = validateUser({ id: 1, name: 'John' });
pipe(
result,
fold(
error => console.error(error),
user => console.log(`Valid user: ${user.name}`)
)
);
主要区别:
Option 用于处理"有值/无值"的情况
Either 用于处理"成功/失败"的情况,且可以携带错误信息
实践建议:
当只需要处理值是否存在时,使用 Option
当需要处理错误并携带错误信息时,使用 Either
总是使用 pipe 操作来链式处理这些类型
善用 fold 来处理所有可能的情况
Array
数组相关的函数式操作
常用函数:
map - 映射数组元素
filter - 过滤数组元素
fold - 折叠数组
import { pipe } from 'fp-ts/function';
import * as A from 'fp-ts/Array';
// 基础数据
const numbers = [1, 2, 3, 4, 5];
const users = [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 },
{ id: 3, name: 'Charlie', age: 35 },
];
// 1. 基本操作
// map - 映射数组元素
console.log(
pipe(
numbers,
A.map(n => n * 2)
)
); // [2, 4, 6, 8, 10]
// filter - 过滤数组元素
console.log(
pipe(
numbers,
A.filter(n => n % 2 === 0)
)
); // [2, 4]
// reduce/fold - 折叠数组
console.log(
pipe(
numbers,
A.reduce(0, (acc, n) => acc + n)
)
); // 15
// 2. 数组转换
// flatten - 展平嵌套数组
console.log(
pipe(
[
[1, 2],
[3, 4],
],
A.flatten
)
); // [1, 2, 3, 4]
// chain (flatMap) - 映射并展平
console.log(
pipe(
numbers,
A.chain(n => [n, n * 2])
)
); // [1, 2, 2, 4, 3, 6, 4, 8, 5, 10]
// 3. 查找元素
// findFirst - 查找第一个匹配的元素
console.log(
pipe(
users,
A.findFirst(user => user.age > 30)
)
); // Option<User>
// findLast - 查找最后一个匹配的元素
console.log(
pipe(
users,
A.findLast(user => user.age > 30)
)
); // Option<User>
// 4. 数组验证
// every - 检查是否所有元素都满足条件
console.log(
pipe(
numbers,
A.every(n => n > 0)
)
); // true
// some - 检查是否存在元素满足条件
console.log(
pipe(
numbers,
A.some(n => n > 4)
)
); // true
// 5. 排序
// sort - 按指定比较函数排序
console.log(
'排序',
pipe(
users,
A.sort<(typeof users)[0]>({
compare: (a, b) => (a.age < b.age ? -1 : a.age > b.age ? 1 : 0),
equals: (a, b) => a.age === b.age,
})
)
);
// sortBy - 按多个条件排序
console.log(
pipe(
users,
A.sortBy<(typeof users)[0]>([
{
compare: (a, b) => (a.age < b.age ? -1 : a.age > b.age ? 1 : 0),
equals: (a, b) => a.age === b.age,
},
{
compare: (a, b) => (a.name.localeCompare(b.name) > 0 ? -1 : 1),
equals: (a, b) => a.name === b.name,
},
])
)
);
// partition - 分割数组
console.log(
pipe(
users,
A.partition(user => user.age > 30)
)
); // 返回 [满足条件的数组, 不满足条件的数组]
// 7. 组合操作示例
// 查找年龄大于30的用户,获取他们的名字,并按字母顺序排序
console.log(
'组合示例',
pipe(
users,
A.filter(user => user.age > 30),
A.map(user => user.name),
A.sort<string>({
compare: (a, b) => (a.localeCompare(b) > 0 ? -1 : 1),
equals: (a, b) => a === b,
})
)
);
// 8. 实用函数
// uniq - 去重
console.log(pipe([1, 1, 2, 2, 3, 3], A.uniq({ equals: (a, b) => a === b }))); // [1, 2, 3]
// zip - 将两个数组配对
console.log(pipe([1, 2, 3], A.zip(['a', 'b', 'c']))); // [[1, 'a'], [2, 'b'], [3, 'c']]
// chunk - 将数组分成固定大小的块
console.log(pipe([1, 2, 3, 4, 5], A.chunksOf(2))); // [[1, 2], [3, 4], [5]]
Task
处理异步操作
表示一个可能异步执行的计算
常用于处理Promise
主要使用场景:
API 调用
文件操作
数据库操作
任何异步操作
实践建议:
对于可能失败的异步操作,优先使用 TaskEither
使用 pipe 来组合多个操作
考虑使用并行操作来优化性能
实现错误重试机制来提高可靠性
始终处理错误情况
import { pipe } from 'fp-ts/function';
import * as T from 'fp-ts/Task';
import * as TE from 'fp-ts/TaskEither';
// 1. 基本创建和使用
// Task 类型实际上是一个返回 Promise 的函数:() => Promise<A>
// 从普通值创建 Task
const task1 = T.of(1); // Task<number>
// 从异步函数创建 Task
const fetchUser =
(id: number): T.Task<User> =>
() =>
fetch(`/api/users/${id}`).then(res => res.json());
// 2. 基本操作
// map - 转换值
const doubled = pipe(
task1,
T.map(n => n * 2)
);
doubled().then(v => console.log('doubled', v)); // Promise<2>
// chain - 链接 Tasks
const getUserAndPosts = (userId: number) =>
pipe(
fetchUser(userId),
T.chain(user =>
pipe(
fetchUserPosts(user.id),
T.map(posts => ({ user, posts }))
)
)
);
getUserAndPosts(1)().then(v => console.log('getUserAndPosts ', v)); // Promise<{ user: User, posts: Post[] }>
// 3. 错误处理 (使用 TaskEither)
// TaskEither 组合了 Task 和 Either,用于处理可能失败的异步操作
const fetchUserSafely = (id: number): TE.TaskEither<Error, User> =>
TE.tryCatch(
() => fetch(`/api/users/${id}`).then(res => res.json()),
error => new Error(`Failed to fetch user: ${error}`)
);
// 使用 TaskEither
const program = pipe(
fetchUserSafely(1),
TE.map(user => user.name),
TE.fold(
error => T.of(`Error: ${error.message}`),
name => T.of(`Success: ${name}`)
)
);
program().then(v => console.log('program', v)); // Promise<"Success: John">
// 4. 实际应用示例
// 定义接口
interface User {
id: number;
name: string;
}
interface Post {
id: number;
title: string;
userId: number;
}
// API 调用函数
const fetchUserById = (id: number): TE.TaskEither<Error, User> =>
TE.tryCatch(
() => fetch(`/api/users/${id}`).then(res => res.json()),
error => new Error(`Failed to fetch user: ${error}`)
);
const fetchUserPosts = (userId: number): TE.TaskEither<Error, Post[]> =>
TE.tryCatch(
() => fetch(`/api/posts?userId=${userId}`).then(res => res.json()),
error => new Error(`Failed to fetch posts: ${error}`)
);
// 组合多个操作
const getUserWithPosts = (userId: number) =>
pipe(
fetchUserById(userId),
TE.chain(user =>
pipe(
fetchUserPosts(user.id),
TE.map(posts => ({ user, posts }))
)
),
TE.fold(
error => T.of(`Error: ${error.message}`),
name => T.of(`Success: ${name}`)
)
);
getUserWithPosts(1)(); // Promise<{ user: User, posts: Post[] }>
// 5. 并行操作
// 并行执行多个 Task
const parallel = T.sequenceArray([fetchUser(1), fetchUser(2), fetchUser(3)]);
console.log('parallel', parallel()); // Promise<[User, User, User]>
// 并行执行多个 TaskEither
const parallelTE = TE.sequenceArray([fetchUserSafely(1), fetchUserSafely(2), fetchUserSafely(3)]);
console.log('parallelTE', parallelTE()); // Promise<Either<Error, [User, User, User]>>
// 6. 实用工具函数
// delay - 延迟执行
const delay =
(ms: number): T.Task<void> =>
() =>
new Promise(resolve => setTimeout(resolve, ms));
// 带延迟的操作
const fetchUserWithDelay = (id: number) =>
pipe(
delay(1000),
T.chain(() => fetchUser(id))
);
console.log('fetchUserWithDelay', fetchUserWithDelay(1)()); // Promise<User>
// 7. 错误重试
const withRetry = <E, A>(task: TE.TaskEither<E, A>, maxAttempts: number): TE.TaskEither<E, A> => {
const attempt = (currentAttempt: number): TE.TaskEither<E, A> =>
pipe(
task,
TE.orElse(error =>
currentAttempt < maxAttempts
? pipe(
delay(1000),
T.chain(() => attempt(currentAttempt + 1))
)
: TE.left(error)
)
);
return attempt(1);
};
// 使用示例
const fetchWithRetry = pipe(fetchUserSafely(1), task => withRetry(task, 3));
console.log('fetchWithRetry', fetchWithRetry()); // Promise<Either<Error, User>>
// 8. 完整的应用示例
// const userProgram = (userId: number) =>
// pipe(
// getUserWithPosts(userId),
// TE.fold(
// error =>
// T.of({
// success: false as const,
// error: error.message,
// }),
// result =>
// T.of({
// success: true as const,
// data: result,
// })
// )
// );
// 执行
// userProgram(1)().then(result => {
// if (result.success) {
// console.log('User and posts:', result.data);
// } else {
// console.error('Error:', result.error);
// }
// });
IO
处理同步的副作用
用于封装有副作用的操作
主要使用场景:
DOM 操作
本地存储操作
控制台输入输出
随机数生成
日期/时间操作
全局状态管理
实践建议:
IO 用于封装所有带有副作用的同步操作
对于可能失败的操作,使用 IOEither
使用 pipe 组合多个操作
将复杂的操作分解成小的、可组合的单元
始终考虑错误处理
保持函数纯净,将副作用限制在 IO 中
设计模式:
Program = Pure core + Impure shell
将核心业务逻辑保持纯函数
使用 IO 在外层处理副作用
依赖注入
通过参数传入 IO 操作,提高可测试性
错误处理
使用 IOEither 处理可能的错误
实现重试机制
import { pipe } from 'fp-ts/function';
import * as IO from 'fp-ts/IO';
import * as IOE from 'fp-ts/IOEither';
import * as E from 'fp-ts/Either';
// 1. 基本概念
// IO<A> 本质上是一个返回类型 A 的函数:() => A
// 2. 基本创建和使用
// 创建简单的 IO
const getTimestamp: IO.IO<number> = () => Date.now();
const getRandomNumber: IO.IO<number> = () => Math.random();
// 创建控制台操作
const log =
(message: string): IO.IO<void> =>
() =>
console.log(message);
const readLine: IO.IO<string> = () => prompt('Enter something:') || '';
// 3. 基本操作
// map - 转换值
const doubledTimestamp = pipe(
getTimestamp,
IO.map(time => time * 2)
);
// chain - 链接多个 IO
const logTimestamp = pipe(
getTimestamp,
IO.chain(time => log(`Current time: ${time}`))
);
// 4. IOEither - 处理可能失败的同步操作
// 解析 JSON
const parseJSON = (str: string): IOE.IOEither<Error, unknown> =>
IOE.tryCatch(
() => JSON.parse(str),
error => new Error(`Parse failed: ${error}`)
);
// 5. 实际应用示例
// DOM 操作
interface DOMOperations {
getElementById: (id: string) => IO.IO<HTMLElement | null>;
setValue: (element: HTMLElement, value: string) => IO.IO<void>;
getValue: (element: HTMLElement) => IO.IO<string>;
}
const dom: DOMOperations = {
getElementById: id => () => document.getElementById(id),
setValue: (element, value) => () => {
if ('value' in element) {
(element as HTMLInputElement).value = value;
}
},
getValue: element => () => ('value' in element ? (element as HTMLInputElement).value : ''),
};
console.log('dom :>> ', dom);
// 本地存储操作
const storage = {
getItem:
(key: string): IO.IO<string | null> =>
() =>
localStorage.getItem(key),
setItem:
(key: string, value: string): IO.IO<void> =>
() =>
localStorage.setItem(key, value),
};
// 6. 复杂示例:表单处理
interface FormData {
name: string;
email: string;
}
const validateForm = (data: FormData): IOE.IOEither<string, FormData> =>
IOE.tryCatch(
() => {
if (!data.name) throw new Error('Name is required');
if (!data.email) throw new Error('Email is required');
if (!data.email.includes('@')) throw new Error('Invalid email');
return data;
},
error => `Validation failed: ${error}`
);
const saveForm = (data: FormData): IO.IO<void> =>
pipe(
storage.setItem('formData', JSON.stringify(data)),
IO.chain(() => log('Form saved successfully'))
);
// 类型守卫函数
const isFormData = (json: unknown): json is FormData => {
// 实现你的类型检查逻辑
return true; // 这里需要根据实际 FormData 结构进行验证
};
const loadForm = (): IOE.IOEither<string, FormData> =>
pipe(
storage.getItem('formData'),
IO.chain(data =>
data
? pipe(
parseJSON(data),
IOE.mapLeft(error => error.message), // 将 Error 转换为 string
IOE.chainEitherK(json =>
// 验证并转换为 FormData
isFormData(json) ? E.right(json as FormData) : E.left('Invalid form data format')
)
)
: IO.of(E.left('No saved form data'))
)
);
console.log('loadForm :>> ', loadForm()());
// 7. 组合多个操作
const formProgram = (data: FormData) =>
pipe(
validateForm(data),
IOE.chain(validData =>
IOE.tryCatch(
() => saveForm(validData)(),
error => `Save failed: ${error}`
)
)
);
// 8. 实用工具函数
// 重试机制
const withRetry = <A>(io: IO.IO<A>, maxAttempts: number): IO.IO<A> => {
const attempt =
(currentAttempt: number): IO.IO<A> =>
() => {
try {
return io();
} catch (error) {
if (currentAttempt < maxAttempts) {
return attempt(currentAttempt + 1)();
}
throw error;
}
};
return attempt(1);
};
// 9. 完整的应用示例
const userFormProgram = () => {
const getUserInput = (): IO.IO<FormData> => () => ({
name: (document.getElementById('name') as HTMLInputElement)?.value || '',
email: (document.getElementById('email') as HTMLInputElement)?.value || '',
});
return pipe(
getUserInput(),
IO.chain(data =>
pipe(
formProgram(data),
IOE.fold(
error => log(`Error: ${error}`),
() => log('Success!')
)
)
)
);
};
// 执行示例
const main = userFormProgram();
main(); // 执行所有操作
一些理解:
函数式编程的要求是纯函数,而非一定是最简函数,那可能是最佳实践,在实际的使用过程中,先学会用,再去重构到最佳实践
函数式编程就是关于组合
处理复杂转换时,将操作分解为多个步骤
面向对象是分门别类,永远分不完。而函数式编程是制定规则,定义世界,你是人类还是动物,对于创造世界的神来说都是一样的。就比如说吃饭,吃了就有能量,不吃就没有,动物和人都一样会饿死,这是一条规则!