Funx:Elixir函数式编程套路及LLM的使用规则


这是一个开源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 模型的。

主要包括三类内容:

  1. 什么时候该用它(When to use)
    • 举例:
      • Eq 适合用在“判断两个值是否相等”的场景。
      • Ord 适合用在“需要排序或比较大小”的场景。
      • Maybe 适合用在“数据可能缺失”的场景,比如数据库查不到用户。
  • 怎么把它们拼接组合起来(How to compose)
    • 举例:
      • 你可以先用 Eq 比较名字,再用 Ord 排序年龄。
      • Maybe 可以和 Writer 组合:既表示数据可能为空,又能顺手记录日志。
      • Monoid 可以叠加 Predicate:比如“必须是成年人 AND 必须有驾照”。
  • 在领域建模里的实际例子(Domain modeling examples)
    • 举例:
      • 用户账号系统: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!:强制取值,失败就报错。


    这些概念就是函数式编程的“工具箱”,帮你更清晰地定义相等、排序、组合计算、处理错误和数据流。用白话说,就是一套规范化的“算账方式”,让程序写起来既稳又可控