mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 19:03:29 +08:00
- 将所有小写的命名空间导入更正为首字母大写格式 - 统一 GFramework 框架的命名空间引用规范 - 修复 core、ecs、godot 等模块的命名空间导入错误 - 标准化文档示例代码中的 using 语句格式 - 确保所有文档中的命名空间引用保持一致性 - 更新 global using 语句以匹配正确的命名空间格式
15 KiB
15 KiB
函数式编程指南
概述
GFramework.Core 提供了一套完整的函数式编程工具,帮助开发者编写更安全、更简洁、更易维护的代码。函数式编程强调不可变性、纯函数和声明式编程风格,能够有效减少副作用,提高代码的可测试性和可组合性。
本模块提供以下核心功能:
- Option 类型:安全处理可能不存在的值,替代 null 引用
- Result 类型:优雅处理操作结果和错误,避免异常传播
- 管道操作:构建流式的函数调用链
- 函数组合:组合多个函数形成新函数
- 控制流扩展:函数式风格的条件执行和重试机制
- 异步函数式编程:支持异步操作的函数式封装
核心概念
Option 类型
Option<T> 表示可能存在或不存在的值,用于替代 null 引用。它有两种状态:
- Some:包含一个值
- None:不包含值
使用 Option 可以在编译时强制处理"无值"的情况,避免空引用异常。
Result 类型
Result<T> 表示操作的结果,可能是成功值或失败异常。它有三种状态:
- Success:操作成功,包含返回值
- Faulted:操作失败,包含异常信息
- Bottom:未初始化状态
Result 类型将错误处理显式化,避免使用异常进行流程控制。
管道操作
管道操作允许将值通过一系列函数进行转换,形成流式的调用链。这种风格使代码更易读,逻辑更清晰。
函数组合
函数组合是将多个简单函数组合成复杂函数的技术。通过组合,可以构建可复用的函数库,提高代码的模块化程度。
基本用法
Option 基础
创建 Option
using GFramework.Core.Functional;
// 创建包含值的 Option
var someValue = Option<int>.Some(42);
// 创建空 Option
var noneValue = Option<int>.None;
// 隐式转换
Option<string> name = "Alice"; // Some("Alice")
Option<string> empty = null; // None
获取值
// 使用默认值
var value1 = someValue.GetOrElse(0); // 42
var value2 = noneValue.GetOrElse(0); // 0
// 使用工厂函数(延迟计算)
var value3 = noneValue.GetOrElse(() => ExpensiveDefault());
转换值
// Map:映射值到新类型
var option = Option<int>.Some(42);
var mapped = option.Map(x => x.ToString()); // Option<string>.Some("42")
// Bind:链式转换(单子绑定)
var result = Option<string>.Some("42")
.Bind(s => int.TryParse(s, out var i)
? Option<int>.Some(i)
: Option<int>.None);
过滤值
var option = Option<int>.Some(42);
var filtered = option.Filter(x => x > 0); // Some(42)
var filtered2 = option.Filter(x => x < 0); // None
模式匹配
// 返回值的模式匹配
var message = option.Match(
some: value => $"Value: {value}",
none: () => "No value"
);
// 副作用的模式匹配
option.Match(
some: value => Console.WriteLine($"Value: {value}"),
none: () => Console.WriteLine("No value")
);
Result 基础
创建 Result
using GFramework.Core.Functional;
// 创建成功结果
var success = Result<int>.Succeed(42);
var success2 = Result<int>.Success(42); // 别名
// 创建失败结果
var failure = Result<int>.Fail(new Exception("Error"));
var failure2 = Result<int>.Failure("Error message");
// 隐式转换
Result<int> result = 42; // Success(42)
安全执行
// 自动捕获异常
var result = Result<int>.Try(() => int.Parse("42"));
// 异步安全执行
var asyncResult = await ResultExtensions.TryAsync(async () =>
await GetDataAsync());
获取值
// 失败时返回默认值
var value1 = result.IfFail(0);
// 失败时通过函数处理
var value2 = result.IfFail(ex =>
{
Console.WriteLine($"Error: {ex.Message}");
return -1;
});
转换值
// Map:映射成功值
var mapped = result.Map(x => x * 2);
// Bind:链式转换
var bound = result.Bind(x => x > 0
? Result<string>.Succeed(x.ToString())
: Result<string>.Fail(new ArgumentException("Must be positive")));
// 异步映射
var asyncMapped = await result.MapAsync(async x =>
await ProcessAsync(x));
模式匹配
// 返回值的模式匹配
var message = result.Match(
succ: value => $"Success: {value}",
fail: ex => $"Error: {ex.Message}"
);
// 副作用的模式匹配
result.Match(
succ: value => Console.WriteLine($"Success: {value}"),
fail: ex => Console.WriteLine($"Error: {ex.Message}")
);
管道操作
Pipe:管道转换
using GFramework.Core.Functional.pipe;
var result = 42
.Pipe(x => x * 2) // 84
.Pipe(x => x.ToString()) // "84"
.Pipe(s => $"Result: {s}"); // "Result: 84"
Tap:副作用操作
var result = GetUser()
.Tap(user => Console.WriteLine($"User: {user.Name}"))
.Tap(user => _logger.LogInfo($"Processing user {user.Id}"))
.Pipe(user => new UserDto { Id = user.Id, Name = user.Name });
Let:作用域转换
var dto = GetUser().Let(user => new UserDto
{
Id = user.Id,
Name = user.Name,
Email = user.Email
});
PipeIf:条件管道
var result = 42.PipeIf(
predicate: x => x > 0,
ifTrue: x => $"Positive: {x}",
ifFalse: x => $"Non-positive: {x}"
);
函数组合
Compose:函数组合
using GFramework.Core.Functional.functions;
Func<int, int> addOne = x => x + 1;
Func<int, int> multiplyTwo = x => x * 2;
// f(g(x)) = (x + 1) * 2
var composed = multiplyTwo.Compose(addOne);
var result = composed(5); // (5 + 1) * 2 = 12
AndThen:链式组合
// g(f(x)) = (x + 1) * 2
var chained = addOne.AndThen(multiplyTwo);
var result = chained(5); // (5 + 1) * 2 = 12
Curry:柯里化
Func<int, int, int> add = (x, y) => x + y;
var curriedAdd = add.Curry();
var add5 = curriedAdd(5);
var result = add5(3); // 8
高级用法
Result 扩展操作
链式副作用
using GFramework.Core.Functional.result;
Result<int>.Succeed(42)
.OnSuccess(x => Console.WriteLine($"Value: {x}"))
.OnFailure(ex => Console.WriteLine($"Error: {ex.Message}"))
.Map(x => x * 2);
验证约束
var result = Result<int>.Succeed(42)
.Ensure(x => x > 0, "Value must be positive")
.Ensure(x => x < 100, "Value must be less than 100");
聚合多个结果
var results = new[]
{
Result<int>.Succeed(1),
Result<int>.Succeed(2),
Result<int>.Succeed(3)
};
var combined = results.Combine(); // Result<List<int>>
控制流扩展
TakeIf:条件返回
using GFramework.Core.Functional.control;
var user = GetUser().TakeIf(u => u.IsActive); // 活跃用户或 null
// 值类型版本
var number = 42.TakeIfValue(x => x > 0); // 42 或 null
When:条件执行
var result = 42
.When(x => x > 0, x => Console.WriteLine($"Positive: {x}"))
.When(x => x % 2 == 0, x => Console.WriteLine("Even"));
RepeatUntil:重复执行
var result = 1.RepeatUntil(
func: x => x * 2,
predicate: x => x >= 100,
maxIterations: 10
); // 128
Retry:同步重试
var result = ControlExtensions.Retry(
func: () => UnstableOperation(),
maxRetries: 3,
delayMilliseconds: 100
);
异步函数式编程
异步重试
using GFramework.Core.Functional.async;
var result = await (() => UnreliableOperationAsync())
.WithRetryAsync(
maxRetries: 3,
delay: TimeSpan.FromSeconds(1),
shouldRetry: ex => ex is TimeoutException
);
异步安全执行
var result = await (() => RiskyOperationAsync()).TryAsync();
result.Match(
value => Console.WriteLine($"Success: {value}"),
error => Console.WriteLine($"Failed: {error.Message}")
);
异步绑定
var result = Result<int>.Succeed(42);
var bound = await result.BindAsync(async x =>
await GetUserAsync(x) is User user
? Result<User>.Succeed(user)
: Result<User>.Fail(new Exception("User not found"))
);
高级函数操作
Partial:偏函数应用
Func<int, int, int> add = (x, y) => x + y;
var add5 = add.Partial(5);
var result = add5(3); // 8
Repeat:重复应用
var result = 2.Repeat(3, x => x * 2); // 2 * 2 * 2 * 2 = 16
Once:单次执行
var counter = 0;
var once = (() => ++counter).Once();
var result1 = once(); // 1
var result2 = once(); // 1(不会再次执行)
Defer:延迟执行
var lazy = (() => ExpensiveComputation()).Defer();
// 此时尚未执行
var result = lazy.Value; // 首次访问时才执行
Memoize:结果缓存
Func<int, int> expensive = x =>
{
Thread.Sleep(1000);
return x * x;
};
var memoized = expensive.MemoizeUnbounded();
var result1 = memoized(5); // 耗时 1 秒
var result2 = memoized(5); // 立即返回(从缓存)
最佳实践
何时使用 Option
- 替代 null 引用:当函数可能返回空值时
- 配置参数:表示可选的配置项
- 查找操作:字典查找、数据库查询等可能失败的操作
- 链式操作:需要安全地链式调用多个可能返回空值的方法
// 不推荐:使用 null
public User? FindUser(int id)
{
return _users.ContainsKey(id) ? _users[id] : null;
}
// 推荐:使用 Option
public Option<User> FindUser(int id)
{
return _users.TryGetValue(id, out var user)
? Option<User>.Some(user)
: Option<User>.None;
}
何时使用 Result
- 错误处理:需要显式处理错误的操作
- 验证逻辑:输入验证、业务规则检查
- 外部调用:网络请求、文件操作等可能失败的 I/O 操作
- 避免异常:不希望使用异常进行流程控制的场景
// 不推荐:使用异常
public int ParseNumber(string input)
{
if (!int.TryParse(input, out var number))
throw new ArgumentException("Invalid number");
return number;
}
// 推荐:使用 Result
public Result<int> ParseNumber(string input)
{
return int.TryParse(input, out var number)
? Result<int>.Succeed(number)
: Result<int>.Failure("Invalid number");
}
何时使用管道操作
- 数据转换:需要对数据进行多步转换
- 流式处理:构建数据处理管道
- 副作用隔离:使用 Tap 隔离副作用操作
- 提高可读性:使复杂的嵌套调用变得线性
// 不推荐:嵌套调用
var result = FormatResult(
ValidateInput(
ParseInput(
GetInput()
)
)
);
// 推荐:管道操作
var result = GetInput()
.Pipe(ParseInput)
.Pipe(ValidateInput)
.Tap(x => _logger.LogInfo($"Validated: {x}"))
.Pipe(FormatResult);
错误处理模式
模式 1:早期返回
public Result<User> CreateUser(string name, string email)
{
return ValidateName(name)
.Bind(_ => ValidateEmail(email))
.Bind(_ => CheckDuplicate(email))
.Bind(_ => SaveUser(name, email));
}
模式 2:聚合验证
public Result<User> CreateUser(UserDto dto)
{
var validations = new[]
{
ValidateName(dto.Name),
ValidateEmail(dto.Email),
ValidateAge(dto.Age)
};
return validations.Combine()
.Bind(_ => SaveUser(dto));
}
模式 3:错误恢复
public Result<Data> GetData(int id)
{
return GetFromCache(id)
.Match(
succ: data => Result<Data>.Succeed(data),
fail: _ => GetFromDatabase(id)
);
}
组合模式
模式 1:Option + Result
public Result<User> GetActiveUser(int id)
{
return FindUser(id) // Option<User>
.ToResult("User not found") // Result<User>
.Ensure(u => u.IsActive, "User is not active");
}
模式 2:Result + 管道
public Result<UserDto> ProcessUser(int id)
{
return Result<int>.Succeed(id)
.Bind(GetUser)
.Map(user => user.Tap(u => _logger.LogInfo($"Processing {u.Name}")))
.Map(user => new UserDto { Id = user.Id, Name = user.Name });
}
模式 3:异步组合
public async Task<Result<Response>> ProcessRequestAsync(Request request)
{
return await Result<Request>.Succeed(request)
.Ensure(r => r.IsValid, "Invalid request")
.BindAsync(async r => await ValidateAsync(r))
.BindAsync(async r => await ProcessAsync(r))
.MapAsync(async r => await FormatResponseAsync(r));
}
常见问题
Option vs Nullable
Q: Option 和 Nullable<T> 有什么区别?
A:
Nullable<T>只能用于值类型,Option<T>可用于任何类型Option<T>提供丰富的函数式操作(Map、Bind、Filter 等)Option<T>强制显式处理"无值"情况,更安全Option<T>可以与 Result 等其他函数式类型组合
Result vs Exception
Q: 什么时候应该使用 Result 而不是异常?
A:
- 使用 Result:预期的错误情况(验证失败、资源不存在等)
- 使用 Exception:意外的错误情况(系统错误、编程错误等)
- Result 使错误处理显式化,提高代码可读性
- Result 避免异常的性能开销
性能考虑
Q: 函数式编程会影响性能吗?
A:
- Option 和 Result 是值类型(struct),性能开销很小
- 管道操作本质是方法调用,JIT 会进行内联优化
- Memoize 等缓存机制可以提高性能
- 对于性能敏感的代码,可以选择性使用函数式特性
与 LINQ 的关系
Q: 函数式扩展与 LINQ 有什么区别?
A:
- LINQ 主要用于集合操作,函数式扩展用于单值操作
- 两者可以很好地组合使用
- Option 和 Result 可以转换为 IEnumerable 与 LINQ 集成
// Option 转 LINQ
var options = new[]
{
Option<int>.Some(1),
Option<int>.None,
Option<int>.Some(3)
};
var values = options
.SelectMany(o => o.ToEnumerable())
.ToList(); // [1, 3]
学习曲线
Q: 函数式编程难学吗?
A:
- 从简单的 Option 和 Result 开始
- 逐步引入管道操作和函数组合
- 不需要一次性掌握所有特性
- 在实际项目中逐步应用,积累经验
参考资源
相关文档
外部资源
示例代码
完整的示例代码可以在测试项目中找到:
GFramework.Core.Tests/functional/OptionTests.csGFramework.Core.Tests/functional/ResultTests.csGFramework.Core.Tests/functional/pipe/PipeExtensionsTests.csGFramework.Core.Tests/functional/functions/FunctionExtensionsTests.csGFramework.Core.Tests/functional/control/ControlExtensionsTests.cs