这是一个开源funx库:Elixir的可组合函数式编程模式,以及LLM的使用规则。Funx 就是给 Elixir 打造的一套函数式编程工具箱 + 教材 + 练习环境 + 社区,既方便人类学,也方便 LLM 理解和生成代码。
Funx - Elixir 里的函数式编程套路(持续集成发布在 Hex.pm 上)
注意:Funx 还在测试阶段,功能和接口可能会改动,等到 1.0 版才会稳定。作者欢迎大家提意见和一起贡献。
它的生态圈主要包括:
- 《高级函数式编程与 Elixir》:这是一本书,帮你从理论到实战系统学会函数式编程。
- 交互式 Livebooks:对应书中每一章的练习本,你可以直接在浏览器里跑,也可以用 Docker 运行。
- Funx 库:把各种通用的函数式编程模式搬到 Elixir 里,还贴心附带了“给人和 LLM 用的说明书”。
- Learn Funx:一个练习环境,你可以把 LLM 当老师,边学边练。
- Discord 社区:讨论书里的内容、Funx 库的用法、以及 Learn Funx 的学习体验。
每一个模块(比如 Eq、Ord、Maybe、Monoid、Predicate 等等)在设计时,都不仅仅是冷冰冰的代码,而是附带了一套“使用说明”,这套说明既是写给人类程序员的,也是写给未来可能帮你写代码的 LLM 模型的。
主要包括三类内容:
- 什么时候该用它(When to use)
- 举例:
- Eq 适合用在“判断两个值是否相等”的场景。
- Ord 适合用在“需要排序或比较大小”的场景。
- Maybe 适合用在“数据可能缺失”的场景,比如数据库查不到用户。
- 举例:
- 你可以先用 Eq 比较名字,再用 Ord 排序年龄。
- Maybe 可以和 Writer 组合:既表示数据可能为空,又能顺手记录日志。
- Monoid 可以叠加 Predicate:比如“必须是成年人 AND 必须有驾照”。
- 举例:
- 用户账号系统:Eq 定义“用户是否相同”,可能只看 user_id。
- 电商排序:Ord 定义“商品排序”,可以先按价格,再按销量。
- 配置加载:Maybe 表示“这个配置有可能没填”,避免崩溃。
- 日志累积:Writer 在处理交易流水时,顺带把操作过程记下来。
Equality(相等)
就是定义“两个东西算不算一样”。
* 你可以自己决定啥叫一样:比如比身份证号、比名字、比手机号。
* 也能组合多个条件:比如必须身份证号和手机号都一样,或者只要名字一样就行。
* 不光能用在普通数字、字符串上,还能用在你自己定义的结构体。
Ordering(排序)
就是定义“谁大谁小,谁先谁后”。
* 你可以规定规则:比如按身高排、按年龄排、按优先级排。
* 还可以设定备用方案:先按身高,如果身高一样就按体重。
* 不局限于数字,任何类型都能排。
Monads(单子)
听起来玄乎,其实就是一种把计算过程打包起来,方便串联的工具。好处是它能自动帮你处理一些麻烦事(比如可能没值、可能失败、需要依赖、要延迟执行)。
常见的几种:
* Identity(身份单子):就是普通值包装一下,没啥特别,但方便统一处理。
* Maybe(可能单子):有值就是 Just
,没值就是 Nothing
。不用担心空指针。
* Either(二选一单子):要么是 Left
(错误),要么是 Right
(正确结果)。
* Effect(效果单子):包装带延迟或可能出错的操作,比如异步任务。
* Reader(读取单子):传递全局配置或环境变量,不用到处传参。
* Writer(写入单子):一边算结果,一边自动记录日志或附加说明。
Monoids(幺半群)
名字很数学,其实就是一类可以合并的东西,并且有个“不会影响结果的初始值”。
* Sum:加法,初始值是 0
。
* Product:乘法,初始值是 1
。
* Max/Min:取最大或最小值。
* ListConcat:拼接列表,初始值是空列表 []
。
* StringConcat:拼接字符串,初始值是 ""
。
* Eq.All / Eq.Any:所有比较都相等才算相等 / 只要有一个相等就行。
* Predicate.All / Predicate.Any:所有条件都满足 / 满足一个就行。
一句话:就是“可以反复合并”的东西。
Predicates(条件函数)
就是返回 true/false 的小函数。比如“年龄大于18”“名字以A开头”。
* p_and
:两个条件都满足才行。
* p_or
:有一个满足就行。
* p_not
:取反。
* p_all
:一组条件全满足。
* p_any
:至少满足一个。
* p_none
:一个都不满足。
Folding(折叠/归约)
就是把一堆东西压缩成一个结果。
* fold_l
:从左边开始算。
* fold_r
:从右边开始算。
比如:把 [1,2,3]
加起来得到 6
,或者拼成 "123"
。
Filtering(过滤)
就是只保留你想要的值。
* guard
:满足条件就留下,否则丢掉。
* filter
:像列表里的筛选,保留符合条件的。
* filter_map
:先转换,再过滤掉不合格的。
Sequencing(顺序执行)
就是按顺序跑一堆操作,把结果合并起来。
* concat
:把一堆结果拼起来,丢掉空的。
* concat_map
:对每个元素做操作,再拼结果。
* sequence
:把一堆单子变成一个单子,里面装一堆结果。遇到失败会立刻停下。
* traverse
:边处理边组合结果。
* _a
版本:类似,但更偏向 Applicative 风格,能一次性收集结果。
Lifting(提升)
就是把普通函数提升到单子世界里用。
* lift_predicate
:把普通条件变成单子里的条件。
* lift_eq
:把相等比较适配到单子里。
* lift_ord
:把排序适配到单子里。
Interop(互操作)
和 Elixir 里的常见 {:ok, value}
/ {:error, reason}
打通。
* from_result
:把 result 转成单子。
* to_result
:再把单子转回 result。
* from_try
:捕捉函数执行的异常,转成单子。
* to_try!
:强制取值,失败就报错。
这些概念就是函数式编程的“工具箱”,帮你更清晰地定义相等、排序、组合计算、处理错误和数据流。用白话说,就是一套规范化的“算账方式”,让程序写起来既稳又可控。