Skip to content
📖 Auto 编程语言(主书)本书目录

异步与 ~T ​

在第 15 章中,我们学习了 Auto 的 Actor 模型——如何生成任务、发送消息、 使用 on 接收。Actor 通过消息传递为我们提供了并发能力。但 Auto 中还有第二个同 样重要的并发模型:使用 ~T 类型修饰符的_异步_编程。

在本章中,我们将学习 Auto 的 ~T(读作"波浪-T")如何提供一种干净、可组合 的方式来表达异步操作——无需 async/await 关键字的繁琐或外部运行时。

Auto 中的异步 ​

大多数编程语言使用两个关键字来处理异步操作:async 标记函数,await 等待 结果。JavaScript 有 async/await。Python 有 async/await。C# 有 async/await。 Rust 有 async fn 和 .await。

Auto 采用了不同的方法。它没有添加关键字,而是使用了一个_类型修饰符_:波浪号 ~。当你看到 ~String 时,它表示"一个将来会到达的 String"。当你看到 ~int 时,它表示"一个将来会到达的 int"。~ 是一个_时间蓝图_——对某个计算将在未来 某个时刻完成的描述。

为什么不用 async/await?三个原因:

  1. 关键字太重 —— async/await 需要在语言中引入两个新关键字。~T 不需要 任何新关键字。波浪号是一个类型修饰符,就像 ?T 表示可选、!T 表示结果 一样。

  2. 组合更自然 —— ~T 可以与其他类型修饰符组合。你可以写 ~?String(一个 未来的可选字符串)或 ~!int(一个未来的结果整数)。而使用 async/await, 这些组合要么笨拙,要么不可能。

  3. Actor 和异步统一 —— 在 Auto 中,on 已经可以处理等待消息。~T 类型 让异步函数自然地参与 Actor 系统,不需要单独的运行时或不同的规则集。

这与 Rust 伴读书第 17 章中深入探讨的 ~T 概念相同。在 TAPL 中,我们重点关注 ~T 如何在 Auto 基于 Actor 的并发模型中工作。

~T 蓝图 ​

返回 ~T 的函数就是异步函数。它不会立即返回值——它返回一个值的_蓝图_,该 值将在稍后产生。调用者可以决定何时等待它。

返回时间蓝图的函数

让我们分析一下关键要点:

  1. fn fetch_data() ~String —— 返回类型是 ~String,不是 String。这告诉 编译器(和读者)该函数返回一个时间蓝图。实际的字符串将在稍后产生。

  2. on result String —— 在异步函数内部,on 等待一个触发器。当函数被调用 时,调用者立即获得一个蓝图。函数体异步运行,当它到达 on 时,它等待将 产生结果的触发条件。

  3. send main result —— 当结果准备好时,将其发送回调用者(在这里是 main)。这完成了蓝图。

  4. spawn fetcher —— fetch_data 函数被生成为一个 Actor。因为它返回 ~String,生成操作创建了一个异步 Actor,将在某个时刻产生一个字符串。

  5. on data String —— 调用者使用 on 接收完成的值。这与我们在第 15 章中学到的 on 关键字相同,但在这里它接收的是异步操作的结果。

关键洞察是 ~T 和 Actor 模型使用相同的原语:on 等待,send 传递。没有 单独的异步运行时。异步函数_就是_碰巧返回一个值的 Actor。

在 TypeScript 中,等价物是 Promise<string> —— 一个解析为字符串的 Promise。 在 Rust 中,等价物是 impl Future<Output = String>。Auto 的 ~String 更简短、 更清晰,并且可以与类型修饰符系统组合。

异步函数 ​

现在让我们看一个更完整的异步函数——一个接受参数并产生结果的函数。

带参数的异步函数

输出如下:

Hello, Alice!

有几个注意点:

  1. fn delayed_greeting(name String) ~String —— 函数接受一个 String 参数, 返回 ~String。~String 返回类型告诉 Auto 这是一个异步函数。它将产生一个 字符串,但不是现在。

  2. on _ —— 下划线 _ 表示"等待任何触发器"。在真正的异步函数中,这会是 网络响应、定时器或来自另一个 Actor 的消息。这里模拟等待异步工作完成。

  3. send caller —— 特殊标识符 caller 指向调用此异步函数的地方。这与 第 15 章中 Actor 的 caller 相同,但在异步上下文中它表示"持有蓝图的 地方"。

  4. var result = delayed_greeting("Alice") —— 调用立即返回一个蓝图。变量 result 持有一个 ~String,而不是 String。实际的问候还没有产生。

  5. on msg String —— 主 Actor 等待蓝图完成。当问候准备好时,msg 接收 实际的 String 值。

流程是:调用返回蓝图 -> 异步函数在后台运行 -> 结果被发送回来 -> 调用者用 on 接收。这与 Actor 消息传递是相同的模式,但类型系统追踪了异步契约。

在 Rust 中,这需要 async fn 和 .await。在 Python 中,async def 和 await。在 TypeScript 中,Promise 和 .then()。在 Auto 中,只需 ~T 和 on。

on 用于异步处理 ​

on 关键字在 Auto 中身兼两职。在第 15 章中,我们用它接收 Actor 消息。 在异步函数中,我们用它等待结果。这不是巧合——它是同一个机制。异步函数_就是_ Actor。

使用 `on` 处理异步请求/响应

输出如下:

Status: 200

这个例子展示了 Actor 和异步的自然融合:

  1. fn http_server() —— 一个带有消息循环的普通 Actor 函数(for true 配 合 on)。它没有被声明为异步——它是一个碰巧处理请求并发送响应的 Actor。

  2. type Request 和 type Response —— 自定义类型(来自第 6 章), 定义了调用者和服务器之间的协议。服务器接收 Request 消息并发送回 Response 消息。

  3. send http_server Request { url: "/api/data" } —— 调用者向服务器 Actor 发送请求。这是即发即忘的,就像任何 Actor 消息一样。

  4. on resp Response —— 调用者等待 Response 到达。这是异步部分:调用者 挂起,直到服务器发送回响应。

http_server 函数是一个长期运行的 Actor。它在 ~T 意义上本身不是异步的—— 它不返回蓝图。相反,它是一个处理请求并发送响应的服务。调用者使用异步模式与 它交互:send 发起请求,on 等待响应。

在 Rust 中,这个模式需要显式通道、循环和 recv()。在 Python 中,需要 threading.Thread 和 Queue。在 C 中,没有标准的等价物。在 Auto 中,它是 我们用于所有 Actor 通信的相同的 on/send 模式。

组合异步 ​

~T 的真正威力在你需要链接多个异步操作时显现。当一个异步操作依赖于另一个 操作的结果时,你嵌套 on 块。每个 on 等待其蓝图完成后才运行内部代码。

链接异步操作

输出如下:

User: User1
Posts: Posts for User1

这是异步链接模式:

  1. var user = fetch_user(1) —— 调用第一个异步函数。user 是一个 ~String 蓝图。函数开始在后台运行。

  2. on u String { ... } —— 等待第一个蓝图完成。完成后,u 持有实际的 String 值("User1")。

  3. var posts = fetch_posts(u) —— 使用第一个操作的结果作为第二个操作的输入。 这只在第一个 on 完成后才运行,因此 u 保证是一个真实值。

  4. on p String { ... } —— 等待第二个蓝图完成。在这个块内,u 和 p 都 作为真实值可用。

on 块的嵌套是 Auto 中等价于 JavaScript 的 .then() 链或 Rust 中顺序 .await 的方式。每个 on 引入一个作用域,在其中蓝图已被解析,值可用于使用。

在 TypeScript 中,这将是 await fetchUser(1) 后跟 await fetchPosts(u)。 在 Rust 中,将是 let u = fetch_user(1).await 后跟 let p = fetch_posts(&u).await。 在 Auto 中,on 块实现了相同的效果,同时保持在 Actor 消息传递范式内。

异步 + Actor ​

Auto 异步模型最深刻的洞察是异步和 Actor 是同一件事。异步函数就是返回值的 Actor。Actor 就是并发运行的函数。~T 类型修饰符是连接它们的桥梁:它标记一个 将为其调用者产生结果的函数。

这意味着 Actor 可以调用异步函数,异步函数可以向 Actor 发送消息,一切自然 组合。

将异步函数与 Actor 模型结合

输出类似如下:

Processed: task-a
Processed: task-b

(顺序可能不同,因为 Worker 并发运行。)

这个例子把我们学到的所有内容整合在一起:

  1. enum Work —— 定义消息协议。Process(String) 是执行工作的命令; Result(String) 是完成的结果。这与第 15 章的 Actor 通信模式和 第 9 章的错误处理模式相同。

  2. fn worker(id int) —— 一个带参数的 Actor(来自第 15 章)。每个 实例有自己的 id 和邮箱。Worker 作为独立的 Actor 运行。

  3. is work { Process(data) => ..., Result(_) => ... } —— 对 Work 枚举进行 模式匹配(来自第 7 章)。Worker 处理 Process 消息,忽略 Result 消息。

  4. spawn worker(1)、spawn worker(2) —— 两个独立的 Worker Actor。每个从 自己的邮箱处理消息。

  5. send worker(1) Work.Process("task-a") —— 将工作分发到特定 Worker。 Work 枚举将数据与类型信息一起包装。

  6. on result Work { is result { Result(msg) => print(msg), ... } } —— 主 Actor 接收 Worker 的结果并进行模式匹配以提取消息。

Work 枚举是这里的关键设计模式。通过将命令和结果包装在同一个枚举类型中, Actor 系统提供了类型安全的通信。编译器确保每个变体都被处理,防止因未处理 消息类型而导致的 bug。

这就是使 Auto 作为 AI 操作系统强大的 Actor-异步模式。Worker 是 Actor。任务 是消息。结果是消息。类型系统确保一切协调一致。没有共享内存,没有锁,没有 竞争条件。

超时与取消 ​

当你生成一个异步操作时,你可能不想永远等待。如果网络很慢怎么办?如果计算 耗时太长怎么办?你需要一种方法来设置时间限制,并在操作未按时完成时取消它。

带有潜在超时的长时间运行异步操作

输出如下:

Done!

这个例子展示了超时和取消的基本结构。long_running 函数模拟一个耗时的操作。 main 函数启动它并用 on 等待结果。

在实际实现中,你会添加一个超时机制。模式将是:

  1. 启动异步操作(var job = long_running())
  2. 同时启动一个定时器
  3. 使用 on 接收先完成的结果——实际结果或超时

因为 Auto 的 Actor 模型是基于消息的,取消也是基于消息的。你会向运行长操作 的 Actor 发送一条取消消息,该 Actor 在其消息循环中检查该消息。这从根本上比 其他语言中的线程取消更简单,在其他语言中你需要处理中断标志、 CancellationToken 或 thread.interrupt()。

关键洞察是在 Auto 中,一切都是 Actor,Actor 通过消息通信。这意味着超时和 取消不是特殊的运行时功能——它们只是另一种消息模式。你不需要特殊 API。你不 需要 CancellationToken 类型。你发送一条"停止"的消息,Actor 就停止了。

在 Rust 中,等价的模式使用 tokio::select! 或 tokio::time::timeout。在 Python 中,asyncio.wait_for。在 JavaScript 中,Promise.race。在 Auto 中, 相同的 Actor 原语处理正常完成和超时。

总结 ​

本章介绍了 ~T —— Auto 的异步编程方法。以下是我们学到的内容:

概念语法用途
异步返回类型fn foo() ~T声明一个返回未来值的函数
等待结果on var T { }阻塞直到蓝图解析
回复调用者send caller value用值完成蓝图
链接异步操作嵌套 on 块运行顺序异步操作
与 Actor 组合spawn + send + on混合异步函数和 Actor 模式
超时/取消基于消息将取消作为普通消息发送

关键要点:

  1. ~T 是类型修饰符,不是关键字 —— 它使用与 ?T(可选)和 !T(结果) 相同的系统。不需要新关键字。

  2. 异步和 Actor 是同一件事 —— 异步函数就是返回值的 Actor。on/send 原语对两者都适用。

  3. 通过嵌套组合 —— 通过嵌套 on 块链接异步操作。每个 on 解析一个蓝图 并使值可用。

  4. 全程类型安全 —— 编译器追踪哪些值是蓝图(~T),哪些已解析(T)。 你不能意外地将蓝图用作真实值。

  5. 超时只是一种消息 —— 因为一切都是基于 Actor 的,超时和取消是消息模式, 不是特殊的运行时功能。

~T 类型修饰符是 Auto 并发故事的最后一块拼图。结合第 15 章的 Actor 模型,它提供了一个完整的系统来编写并发程序:Actor 用于有状态的长期运行服务, ~T 用于产生结果的请求/响应模式。它们共同使 Auto 天然适合 AI 操作系统模型, 其中数十个服务并发运行,通过消息通信,并组合它们的结果。

在下一章中,我们将探索 Auto 如何使用 is、has 和 spec 关键字处理面向 对象编程。

基于 MIT 许可发布。