一、传统手艺:Partial Application
在函数式编程中,Partial Application 是传递依赖的常用方式。例如:
1 | let foo bar baz request = ... |
优点:
- 无需框架或反射,直接通过函数参数传递依赖。
- 符合函数式编程的纯函数理念。
缺点:
- 参数爆炸:当功能扩展时,参数数量激增(如日志、数据库、加密等)。
- 维护困难:新增依赖需修改所有调用点的参数传递。
- 隐式依赖:难以从函数签名直接区分核心参数与辅助依赖。
二、结构化方法:单一环境参数(env
)
为解决参数爆炸问题,可将依赖封装为单一环境对象env
,并通过接口约束访问权限:
1 | type ILog = abstract Logger: ILogger |
优点:
- 显式依赖声明:函数签名仅需
env
参数,编译器验证接口实现。 - 模块化隔离:各模块仅声明所需接口(如
ILog
、IDb
),避免全局依赖。 - 易于测试:通过模拟
env
实现单元测试,无需依赖具体实现。
应用场景:
1 | let changePass env req = task { |
三、Reader Monad
为消除显式的env
传递,可引入 Reader Monad,将环境隐式注入计算流程:
1 | type Effect<'env, 'out> = Effect of ('env -> 'out) |
然后:
1 | let changePass req = effect { |
优点:
- 隐式依赖管理:通过
effect
计算表达式自动传递env
,减少样板代码。 - 组合性:支持与其他计算表达式(如
async
/task
)结合,处理异步操作。
缺点:
- 性能开销:频繁的闭包创建和间接调用可能导致性能下降。
- 生态兼容性:需自定义计算表达式,与现有异步框架集成复杂。
Refs.
- Spring的构造器注入
- Blazor的DI实现