15 分钟了解 Monad(下)

简介: 15 分钟了解 Monad

为了解决这个问题, 我们像之前一样, 引入两个辅助函数:

def unit(x):
    return (x, [x])
def bind(t, f):
    res = f(t[0])
    return (res, t[1] + [res])

现在, 我们又可以链式调用了:

print( bind(bind(bind(unit(x), f1), f2), f3) )

下面的图表展示了当 x=0 的时候, v1, v2, v3 分别代表了中间变量.640 (1).png

3 Nulls/Nones

下面让我们来引入类和对象. 假设我们有一个类 Employee:

class Employee:
    def get_boss(self):
        """Retrun the employee's boss"""
    def get_wage(self):
        """Compute the wage"""

每个 Employee 实例都有一个 boss, 也就是老板, 并且也是 Employee 类型的, 还有一个工资属性. 我们可以通过两个方法来访问他们. 每一个方法都有可能返回 None (也就是说工资不知道, 或者是 没有 boss). 在这个例子中, 我们要开发一个程序, 给定一个 Employee, 比如说 john, 返回他的老 板的工资, 如果不能确定工资的话, 或者 john 是 None, 那么我们应该返回 None.

理想情况下, 我们只要这样写就好了:

print(john.get_boss().get_wage())

然而, 因为每个方法都可能返回 None, 我们得这么写:

result = None
if john is not None and john.get_boss() is not None and john.get_boss().get_wage() is not None: result = johs.get_boss().get_wage()
print(result)

然而, 在这个方案中, 我们调用了好多次 getboss 和 getwage 方法. 如果这两个方法调用起来代 价很大的话(比如说需要查询数据库), 那么显然是不合适的. 所以方案应该是:

result = None
if john is not None:
    boss =  john.get_boss()
    if boss is not None:
        wage = boss.get_wage()
        if wage is not None:
            result = wage
print(result)

这个方案显然不太好看, 三层 if 语句看起来太臃肿了. 为了解决这个问题, 我们使用和刚刚一样的 方法: 定义下面的辅助函数

def unit(e):
    return e
def bind(e, f):
    return None if e is None, else f(e)

现在我们可以直接链式调用了:

print(bind(bind(unit(john), Employee.get_boss), Employee.get_wage))

你可能已经注意到了, 我们实际上并不需要调用 unit(john), 因为他就是返回自身而已. 我们这样 做的原因是为了和之前的模式保持一致, 这样我们就能推广泛化到更通用的模式. 另外需要注意的是 , 在 Python 中, 方法也只是普通的函数, john.getboss() 和 Employee.getboss(john) 是完全 一样的意思.

下面的图表显示了在 john 没有 boss 的情况下的计算过程.640 (2).png

泛化 - Monads

如果我们想要组合函数 f1, f2, ... fn. 如果所有的参数都和返回类型对的上, 那么我们可以直接 调用 fn(...f2(f1(x))...). 下面的图说明了隐含的计算过程. v1, v2...vn 标识了其中的中间变量 .640 (3).png然而, 这种情况往往是不存在的. 比如说在我们之前的日志例子中, 输入类型和输出类型是不能匹配 的, 在第二个和第三个例子中, 函数是可以组合的, 但是我们想要在其中"注入"我们额外的逻辑. 在 第二个例子中, 我们想要记录中间值, 而在第三个例子中, 我们想要加入 Null/None 检测.

命令式解法

在上面的例子中, 我们首先使用了直观的命令式解法. 如下图所示:640 (4).png在调用 f1 之前, 我们首先执行一些初始化代码. 比如, 在例子1 和例子2 中, 我们初始化了存储日 志和中间值的变量. 在之后我们调用 f1, f2...fn 等函数的时候, 我们添加了一些胶水代码. 在例 子1 和例子2 中, 胶水代码分别负责聚合日志和中间值. 在例子3 中, 胶水代码负责检查中间值是否 是空的, 也就是 Null/None.

引入 Monad

正如我们在上面的例子中看到的一样, 直接的方法会有一些让人不悦的副作用 -- 丑陋的胶水代码, 多次检查 Null/None 等等. 为了实现更优雅的方案, 在上面的例子中, 我们使用了一种设计模式, 包含了 unit 和 bind 两种函数. 这种设计模式就叫做 Monad. 本质上来说, bind 函数实现了 胶水代码, 而 unit 实现了初始化代码. 这就让我们可以在一行之内解决问题:

bind(bind(...bind(bind(unit(x), f1), f2)...fn-1), fn)

下面的图表说明了计算过程:640 (5).pngunit(x) 产生了初始值 v1, 然后 bind(v1, f1) 生成了新的中间值 v2, 然后在被用到了 bind(v2, f2) 中, 整个过程一直持续到最终结果产生. 使用这个模式, 配合上不同的 unit 和 bind 函数, 我们可以实 现多种不同的函数组合. 标准的 Monad 库提供了几种预定义好的常用 monad(也就是 unit 和 bind 函数), 可以直接拿来用.

为了组合 bind 和 unit 函数, unit 和 bind 的返回值, 和 bind 的第一个参数必须是匹配的. 这 叫做 Monadic 类型. 在上面的 Monad 计算过程中, 所有的中间值的类型都是 Monadic.

最后, 重复调用bind显然也是丑陋的, 我们可以定义一个函数来辅助操作.

dep ipeline(e, *fns):
    for fn in fns:
        e = bind(e, fn)
    return e

下面的代码:

bind(bind(bind(bind(unit(x), f1), f2), f3), f4)

就可以改成:

pipeline(unit(x), f1, f2, f3, f4)

结论

Monad 是函数组合的一种简单又强大的设计模式. 在声明式的语言中, 他被用来实现命令式语言中的 日志和 IO 操作. 在命令式的语言中, 他可以用来减少和隔离冗余的胶水代码. 本文只是简单地介绍 了 Monad 的一些只管解释, 还可以查看下面这些资料:

  1. Monad on Wikipedia
  2. Monads in Python
  3. List of Monad tutorials
相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
目录
相关文章
|
机器学习/深度学习 算法 算法框架/工具
模型训练实战:选择合适的优化算法
【7月更文第17天】在模型训练这场智慧与计算力的较量中,优化算法就像是一位精明的向导,引领着我们穿越复杂的损失函数地形,寻找那最低点的“宝藏”——最优解。今天,我们就来一场模型训练的实战之旅,探讨两位明星级的优化算法:梯度下降和Adam,看看它们在不同战场上的英姿。
583 5
|
运维 测试技术 文件存储
【阿里云新品发布·周刊】第21期:小程序一云多端解决方案,助开发者实现一云多端的业务战略
点击订阅新品发布会! 新产品、新版本、新技术、新功能、价格调整,评论在下方,下期更新!关注更多内容,了解更多 最新发布 小程序一云多端解决方案 2019年8月14日15时,小程序一云多端解决方案分享会正式开启。
3053 0
【阿里云新品发布·周刊】第21期:小程序一云多端解决方案,助开发者实现一云多端的业务战略
|
9天前
|
人工智能 开发工具 iOS开发
Claude Code 新手完全上手指南:安装、国产模型配置与常用命令全解
Claude Code 是一款运行在终端环境中的 AI 编程助手,能够直接在命令行中完成代码生成、项目分析、文件修改、命令执行、Git 管理等开发全流程工作。它最大的特点是**任务驱动、终端原生、轻量高效、多模型兼容**,无需图形界面、不依赖 IDE 插件,能够深度融入开发者日常工作流。
3127 8
|
12天前
|
Shell API 开发工具
Claude Code 快速上手指南(新手友好版)
AI编程工具卷疯啦!Claude Code凭借任务驱动+终端原生的特性,成了开发者的效率搭子。本文从安装、登录、切换国产模型到常用命令,手把手带新手快速上手,全程避坑,30分钟独立用起来。
3194 20
|
5天前
|
人工智能 Linux BI
国内用 Claude Code 终于不用翻墙了:一行命令搞定,自动接 DeepSeek
JeecgBoot AI专题研究 一键脚本:Claude Code + JeecgBoot Skills + DeepSeek 全平台接入 一行命令装好 Claude Code + JeecgBoot Skills + DeepSeek 接入,无需翻墙使用 Claude Code,支持 Wind
2118 3
国内用 Claude Code 终于不用翻墙了:一行命令搞定,自动接 DeepSeek
|
24天前
|
人工智能 JSON 供应链
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
LucianaiB分享零成本畅用JVS Claw教程(学生认证享7个月使用权),并开源GeoMind项目——将JVS改造为科研与产业地理情报可视化AI助手,支持飞书文档解析、地理编码与腾讯地图可视化,助力产业关系图谱构建。
23589 15
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
|
1天前
|
人工智能 自然语言处理 文字识别
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
Qwen3.7-Max是阿里云百炼面向智能体时代推出的新一代旗舰模型,对标GPT-5.5、Claude Opus 4.7等闭源旗舰。该模型支持百万级token上下文窗口,具备顶级推理能力、多模态搜索与视觉理解增强、流式输出低延迟响应等核心优势,覆盖编程、办公、长周期自主执行等复杂场景。同时支持OpenAI接口兼容,便于系统快速迁移。用户可通过Token Plan团队版、Coding Plan或节省计划等订阅方式灵活调用,适合企业级高要求场景使用。
|
11天前
|
人工智能 JSON BI
DeepSeek V4-Pro 接入 Claude Code 完全实战:体验、测试与关键避坑指南
Claude Code 作为当前主流的 AI 编程辅助工具,凭借强大的代码理解、工程执行与自动化能力深受开发者喜爱,但原生模型的使用成本相对较高。为了在保持能力的同时进一步降低开销,不少开发者开始寻找兼容度高、价格更友好的替代模型。DeepSeek V4 系列的发布带来了新的选择,该系列包含 V4-Pro 与 V4-Flash 两款模型,并提供了与 Anthropic 完全兼容的 API 接口,理论上只需简单修改配置,即可让 Claude Code 无缝切换为 DeepSeek 引擎。
2632 3