├── .gitignore ├── README.md ├── SUMMARY.md ├── a_simple_spec.md ├── about-this-guide.md ├── assets ├── behaviors_result.png ├── error_trace.png ├── import.png ├── import1.png ├── import2.png ├── invariant.png ├── model_assert_fail.png ├── model_moneyinvariant1.png ├── model_moneyinvariant2.png ├── model_moneyinvariant_fail.png ├── model_overview.png ├── model_run.png ├── multiprocess_fail.png └── with_result.png ├── behaviors.md ├── example.md ├── expressions.md ├── introduction.md ├── operators.md ├── pluscal.md ├── tla+.md └── using_the_toolbox.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | assets/.DS_Store 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TLA+教程中文翻译(待完成) 2 | 3 | * 原作: [Learn TLA+](https://learntla.com/introduction/) 4 | * 作者: [Hillel](https://hillelwayne.com/about/) 5 | * 译者: [Leviathan](https://github.com/Leviathan1995) 6 | * Gitbook地址: [Gitbook](https://legacy.gitbook.com/book/leviathan1995/learn-tla-cn/details) (需要科学上网) 7 | * 阅读方式: [Gitbook在线阅读](https://leviathan1995.gitbooks.io/learn-tla-cn/content/)或者直接下载[PDF版本](https://legacy.gitbook.com/download/pdf/book/leviathan1995/learn-tla-cn) 8 | 9 | ## 写在前面 10 | 11 | TLA+属于比较小众的领域,其中的资料更是少之又少。在经过原作者的允许下,翻译这本TLA+的教程供大家入门。这本教程也仅供入门,系统的学习TLA+还是需要参考大量原版的资料: 12 | 13 | * [TLA+官网](https://lamport.azurewebsites.net/tla/tla.html) 14 | * [The TLA+ Hyperbook](https://lamport.azurewebsites.net/tla/hyperbook.zip) 15 | * [Specifying Systems](https://lamport.azurewebsites.net/tla/book-02-08-08.pdf) 16 | * [TLA+ Examples](https://github.com/tlaplus/Examples) 17 | 18 | 19 | 关于TLA+学习,欢迎与我讨论:Leviathan0992@gmail.com 20 | 21 | ## License 22 | 23 | [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/) 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [关于本书](README.md) 4 | * [介绍](introduction.md) 5 | * [关于教程](about-this-guide.md) 6 | * [例子](example.md) 7 | * [PlusCal](pluscal.md) 8 | * [一个简单的例子](a_simple_spec.md) 9 | * [使用Toolbox](using_the_toolbox.md) 10 | * [行为(BEHAVIORS)](behaviors.md) 11 | * [TLA+](tla+.md) 12 | * [运算符](operators.md) 13 | * [表达式](expressions.md) 14 | 15 | -------------------------------------------------------------------------------- /a_simple_spec.md: -------------------------------------------------------------------------------- 1 | # A SIMPLE SPEC 2 | 3 | TLA+最初是一种表示法,模型检查器TLC在5年后才问世。由于TLA+并未打算运行,所以有人认为它只是一个可读的文档,而不是可运行的代码。这意味着我们需要先完成一些模板。 4 | 5 | 为了让TLC分析specifying,它必须具有以下格式: 6 | 7 | ``` 8 | ---- MODULE module_name ---- 9 | \* TLA+ code 10 | 11 | (* --algorithm algorithm_name 12 | begin 13 | \* PlusCal code 14 | end algorithm; *) 15 | ==== 16 | ``` 17 | 18 | TLC只分析`----`开始和`====`结束之间的代码。任何超出这些界限的东西都会被忽略。`module_name`必须与文件名相同,而`algorithm_name`可以是任何您想要的内容。`\*`是行注释,`(*`是块注释。注意,我们把算法放到注释中。如果不把它放到注释中,就会出现语法错误,因为PlusCal不是TLA+。 19 | 20 | 每个文件只能有一个PlusCal算法。 21 | 22 | 对于PlusCal本身,这是一个基本单线程的算法的布局: 23 | 24 | ```sql 25 | variables x = 1, y \in {3, 4}, z = {3, 4}; 26 | begin 27 | \* your code here 28 | end algorithm; 29 | ``` 30 | 31 | 在`variables`块中`=`是赋值。在其他地方,`=`是等式检查,而`:=`是赋值。 32 | 33 | 你可能已经注意到`y \in {3, 4}`。这是集合的定义,这意味着对于这个算法,y可以是3或4。当你检查模型时,TLC会确保它适用于y的**所有**可能值,它首先会测试`y = 3`时没有任何失败,然后测试`y = 4`时没有任何失败。 34 | 35 | 将`y \in{3,4`}与`z ={3,4}`进行比较的话,z是集合`{3,4}`。任何类型的数据结构都可以在TLA+中分配给一个变量。 36 | 37 | 38 | 39 | 让我们从Hello World开始 40 | 41 | ```sql 42 | EXTENDS TLC 43 | 44 | (* --algorithm hello_world 45 | variable s \in {"Hello", "World!"}; 46 | begin 47 | A: 48 | print s; 49 | end algorithm; *) 50 | ``` 51 | 52 | `EXTENDS`是TLA+的`#include`模拟。`TLC`是一个包括了`print`和`assert`的模块。顺便说一下,`print`是唯一可以使用TLA+的IO,它是为调试目的而提供的。 53 | 54 | 这里唯一不同的是`A:`,这是标签。TLC将标签视为规范中的“步骤”;标签里的一切都同时发生。只有在标签之间,模型才能检查不变量和切换进程。同样,您不能在同一个标签中两次分配同一个变量。在大多数情况下,它在这里并不是很有用,但以后会非常重要。 55 | 56 | 我想你对其他编程语言很熟悉。为了使建模更熟悉,PlusCal有类似的结构。语义非常明显,下面是它们的样子。假设所有变量都已经初始化,并且我们处于一个`begin`块中。 57 | 58 | ### 逻辑运算符 59 | 60 | | logic | operator | `TRUE` | `FALSE` | 61 | | --------- | -------- | --------------- | ---------------- | 62 | | equal | `=` | `1 = 1` | `1 = 2` | 63 | | not equal | `/=` | `1 /= 2` | `1 /= 1` | 64 | | and | `/\` | `TRUE /\ TRUE` | `TRUE /\ FALSE` | 65 | | or | `\/` | `FALSE \/ TRUE` | `FALSE \/ FALSE` | 66 | | not | `~` | `~FALSE` | `~TRUE` | 67 | 68 | ### If 69 | 70 | ```c++ 71 | if x = 5 then 72 | skip; 73 | elsif x = 6 then 74 | skip; 75 | else 76 | skip; 77 | end if; 78 | ``` 79 | 80 | ### While 81 | 82 | ```c++ 83 | x := 5; 84 | while x > 0 do 85 | x := x - 1; 86 | end while; 87 | ``` 88 | 89 | 这是唯一的循环形式。 90 | 91 | ### Goto 92 | 93 | ```c++ 94 | A: 95 | if TRUE then goto B else goto C endif; 96 | B: 97 | skip; 98 | C: 99 | skip; 100 | ``` 101 | 102 | Goto在PlusCal中是非常有用的。 -------------------------------------------------------------------------------- /about-this-guide.md: -------------------------------------------------------------------------------- 1 | # 关于教程 2 | 3 | 这本教程重点介绍以最低要求就能让你启动,运行以及改进你的模型。虽然这种方法将略过一些TLA+的内容,以便使我们更容易学习各个简单的模块,但这并不意味着其他部分无聊,困难或没什么用。远非如此,只是它们超出了本教程的内容范围。 4 | 5 | 假设你具有以下背景: 6 | 7 | * **你是一位经验丰富的程序员**。这本教程不是教授如何去编程,并且TLA+也不是一个用户友好的工具。 8 | * **你熟悉测试**。如果你之前没有使用过单元测试,那么对你来说学习测试比学习TLA+更有用。 9 | * **你知道一些数学**。 TLA+大量借鉴数学结构和语法。假如你听说过德·摩根定理,知道什么是集合,并且能够理解什么`(P => Q) => (~Q => ~P)`是什么意思就更好了。否则,虽然你仍然可以学习TLA+,但可能不太直观。 10 | 11 | **你需要下载[TLA+ Toolbox](https://github.com/tlaplus/tlaplus/releases/latest)**,并且确保你还能获取以下资源: 12 | 13 | * [PlusCal手册](https://research.microsoft.com/en-us/um/people/lamport/tla/pluscal.html):PlusCal是TLA+的算法接口。虽然我们将在本指南中介绍它的所有内容,但是有一个完整的语法参考还是很好的。 14 | * [TLA + Cheat Sheet](http://lamport.azurewebsites.net/tla/summary-standalone.pdf):这里包括本教程范围之外的TLA+语法。 15 | * [Specifying Systems](https://research.microsoft.com/en-us/um/people/lamport/tla/book.html):*Specifying Systems*是由TLA+的发明者Leslie Lamport编写的,目前仍然是关于TLA+的最全面的一本书。 16 | 17 | -------------------------------------------------------------------------------- /assets/behaviors_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leviathan1995/Learn-TLA-cn/337433a8837295f800f1ac48fd2518ba3637e607/assets/behaviors_result.png -------------------------------------------------------------------------------- /assets/error_trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leviathan1995/Learn-TLA-cn/337433a8837295f800f1ac48fd2518ba3637e607/assets/error_trace.png -------------------------------------------------------------------------------- /assets/import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leviathan1995/Learn-TLA-cn/337433a8837295f800f1ac48fd2518ba3637e607/assets/import.png -------------------------------------------------------------------------------- /assets/import1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leviathan1995/Learn-TLA-cn/337433a8837295f800f1ac48fd2518ba3637e607/assets/import1.png -------------------------------------------------------------------------------- /assets/import2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leviathan1995/Learn-TLA-cn/337433a8837295f800f1ac48fd2518ba3637e607/assets/import2.png -------------------------------------------------------------------------------- /assets/invariant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leviathan1995/Learn-TLA-cn/337433a8837295f800f1ac48fd2518ba3637e607/assets/invariant.png -------------------------------------------------------------------------------- /assets/model_assert_fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leviathan1995/Learn-TLA-cn/337433a8837295f800f1ac48fd2518ba3637e607/assets/model_assert_fail.png -------------------------------------------------------------------------------- /assets/model_moneyinvariant1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leviathan1995/Learn-TLA-cn/337433a8837295f800f1ac48fd2518ba3637e607/assets/model_moneyinvariant1.png -------------------------------------------------------------------------------- /assets/model_moneyinvariant2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leviathan1995/Learn-TLA-cn/337433a8837295f800f1ac48fd2518ba3637e607/assets/model_moneyinvariant2.png -------------------------------------------------------------------------------- /assets/model_moneyinvariant_fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leviathan1995/Learn-TLA-cn/337433a8837295f800f1ac48fd2518ba3637e607/assets/model_moneyinvariant_fail.png -------------------------------------------------------------------------------- /assets/model_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leviathan1995/Learn-TLA-cn/337433a8837295f800f1ac48fd2518ba3637e607/assets/model_overview.png -------------------------------------------------------------------------------- /assets/model_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leviathan1995/Learn-TLA-cn/337433a8837295f800f1ac48fd2518ba3637e607/assets/model_run.png -------------------------------------------------------------------------------- /assets/multiprocess_fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leviathan1995/Learn-TLA-cn/337433a8837295f800f1ac48fd2518ba3637e607/assets/multiprocess_fail.png -------------------------------------------------------------------------------- /assets/with_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leviathan1995/Learn-TLA-cn/337433a8837295f800f1ac48fd2518ba3637e607/assets/with_result.png -------------------------------------------------------------------------------- /behaviors.md: -------------------------------------------------------------------------------- 1 | # 行为 2 | 3 | 到目前为止,我们已经了解了PlusCal的基本知识以及如何运行模型。我们还看到,如果我们的起始变量被指定为一个集合,它将扩展TLC可以搜索的状态空间。这需要我们编写有用的specifications。最后一步是添加不同的行为:允许系统在给定的步骤中做不同的事情。在单一的进程PlusCal算法中,有两种简单的方式来引入并发性:`with`语句和`either`语句。 4 | 5 | ## Either 6 | 7 | `either`看起来都很像一个基本的if语句。其语法如下: 8 | 9 | ```sql 10 | either 11 | skip; 12 | or 13 | skip; 14 | or 15 | skip; 16 | end either; 17 | ``` 18 | 19 | 需要注意的是TLC将运行每个分支。当它遇到任意一个时,它创建一个单独的行为并为每个行为执行一个不同的分支。例如: 20 | 21 | ```sql 22 | variables x = 3, i = 2; 23 | begin 24 | while i > 0 do 25 | either 26 | x := x + 2; 27 | or 28 | x := x * 2; 29 | end either; 30 | i := i - 1; 31 | end while 32 | ``` 33 | 34 | 内循环将发生两次。每次模型可以添加两个或两个x,这意味着有四个可能的最终结果: 35 | 36 | ![model_run](/assets/behaviors_result.png) 37 | 38 | 39 | 40 | ## With 41 | 42 | `with`的作用的是一样的,除了为每个可能的分支创建一个新的行为之外,它还为集合中的每个元素创建一个行为。在下面的情况下,我们有三个可能的行为: 43 | 44 | ``` 45 | with a \in {1, 2, 3} do 46 | x := x + a 47 | end with; 48 | ``` 49 | 50 | 这将为集合中的每个元素创建一个单独的时间线。 51 | 52 | ![model_run](/assets/with_result.png) 53 | 54 | 55 | 56 | ## 例子 57 | 58 | 描述一个系统,该系统有三个标志,打开和关闭,还有一个更改标志的状态。 59 | 60 | 现在我们在实际操作上有一些限制,但是我们已经可以开始构建简单的模式了。这里有一种写法: 61 | 62 | ```sql 63 | ---- MODULE flags ---- 64 | EXTENDS TLC, Integers 65 | (* --algorithm flags 66 | variables f1 \in BOOLEAN, f2 \in BOOLEAN, f3 \in BOOLEAN 67 | begin 68 | while TRUE do 69 | with f \in {1, 2, 3} do 70 | if f = 1 then 71 | either 72 | f1 := TRUE; 73 | or 74 | f1 := FALSE; 75 | end either; 76 | elsif f = 2 then 77 | either 78 | f2 := TRUE; 79 | or 80 | f2 := FALSE; 81 | end either; 82 | else 83 | either 84 | f3 := TRUE; 85 | or 86 | f3 := FALSE; 87 | end either; 88 | end if; 89 | end with; 90 | end while; 91 | end algorithm; *) 92 | 93 | ==== 94 | ``` 95 | 96 | 这并不是最理想的写法,但我想在这里同时展示`with`和`either`。你可以用这两种方法。`BOOLEAN`是一个TLA+内建,它等于集合`{TRUE, FALSE}`。如你所见,每一步都选择一个标志,并将其设置为true或false。如果觉得麻烦,我们可以这样写: 97 | 98 | ```sql 99 | ---- MODULE flags ---- 100 | EXTENDS TLC, Integers 101 | (* --algorithm flags 102 | variables flags \in [1..3 -> BOOLEAN], next = TRUE; 103 | begin 104 | while next do 105 | with f \in DOMAIN flags, n \in BOOLEAN do 106 | flags[f] := ~flags[f]; 107 | next := n; 108 | end with; 109 | end while; 110 | end algorithm; *) 111 | 112 | ==== 113 | ``` 114 | 115 | The `flags \in [1..3 -> BOOLEAN]` 叫做一个[函数式集合](functions.md),后面的内容会涉及。 -------------------------------------------------------------------------------- /example.md: -------------------------------------------------------------------------------- 1 | # 一个例子 2 | 3 | 一般来说,模型检查在理论上似乎很有吸引力,但人们常常怀疑它是否能在实践中使用。因此,这里有一个示例,说明如何实现一个简单的模型并查找错误。**这个例子目前不需要理解教程的其他部分**。这里的所有内容将在接下来的章节中更详细地介绍。不过,提前获得一些实践经验还是值得的。 4 | 5 | ## 问题 6 | 7 | 你要为银行写一个软件,目前有Alice和Bob两个客户,他们俩每个人都有固定数目的钱在他们的账户,Alice希望转一些钱给Bob,假如只专注于他们的这两个账户,该如何实现这个模型呢? 8 | 9 | ### 第一步 10 | 11 | ```sql 12 | ---- MODULE transfer ---- 13 | EXTENDS Naturals, TLC 14 | 15 | (* --algorithm transfer 16 | variables alice_account = 10, bob_account = 10, money = 5; 17 | 18 | begin 19 | A: alice_account := alice_account - money; 20 | B: bob_account := bob_account + money; 21 | 22 | end algorithm *) 23 | ==== 24 | ``` 25 | 26 | 在我们开始研究它之前,我们先来分解一下当前的语法。由于TLA+是一种规范的描述语言,它对程序的结构施加了一定的限制。 27 | 28 | * 如果文件名是"transfer", 第一行必须是`---- MODULE transfer ----`,而且必须在两边保留至少四个破折号,并且在最后一行必须有至少四个等号,在此之外的所有代码都会被忽略。 29 | * `EXTENDS` 等同于其他语言`import` 语句。 30 | * `\*`代表注释,`(* *)`代表注释块。需要注意的是:整个算法都被写在注释块里,是故意这样做的,因为PlusCal描述的算法不是语法有效的TLA+,所以我们不能在TLA文件中运行它。因此,将它们放在注释中,然后让PlusCal翻译器翻译整个算法。 31 | * `variables`指变量。注意,在声明变量时我们使用`=`,而在算法实现中我们使用`:=`。变量定义以外的`=`符号是比较运算符,即相等,不等号是`/=`。例如: `1 + 2 = 3; s2 + 3 /= 4`。 32 | * `A:`、` B:`是标签\(label\)。它们定义了算法的步骤,理解标签如何工作对于编写更复杂的算法至关重要,因为它们定义了并发性可能严重出错的地方。我们稍后会更深入地研究它们。 33 | 34 | 那么我们该如何运行呢?呃,目前还不能。首先,它不是真正的代码,我们必须先把它转译出来。而且,我们也不直接“运行”它。相反,我们需要设计模型来测试它。让我们现在就开始吧。 35 | 36 | ### TLA+ Toolbox {#tla-toolbox} 37 | 38 | TLA+ Toolbox 是一个TLA+的IDE,无论你的偏好是什么,使用IDE都是一个非常正确的选择。 39 | 40 | ![img1](/assets/import.png) 41 | 42 | 让我们来打开IDE,添加第一个项目,关于specification有两部分:Modules和Models。Modules包括我们的代码,Models用来测试它们。我们首先新建一个新的specification,然后将它转换为TLA+ \(Mac上直接使用`⌘T`\) 43 | 44 | ![img2](/assets/import1.png) 45 | 46 | 转换之后你将看到一堆代码出现,这就是我们的PlusCal算法的TLA+翻译,也就是模型检查器实际检查的东西。说到这里,我们来创建一个模型: 47 | 48 | ![img3](/assets/import2.png) 49 | 50 | 这个模型目前是空的,即使没有任何的配置,我们也可以在下一节直接使用它。 51 | 52 | ### 断言和集合{#assertions-and-sets} 53 | 54 | Alice的账户会成为负值吗?虽然我们的specification现在允许这样,但却不是我们想要的。我们可以在交易完成之后加一个基本的断言检查,TLA+的断言和其他语言的断言类似。但是在TLA+中,它通常用来调试,在这个例子中,TLA+拥有更强大的工具来检查"合同"和''财产"的正确性。 55 | 56 | 这里就是我们加了断言的代码: 57 | 58 | ```sql 59 | ---- MODULE transfer ---- 60 | EXTENDS Naturals, TLC 61 | 62 | (* --algorithm transfer 63 | variables alice_account = 10, bob_account = 10, money = 5; 64 | 65 | begin 66 | A: alice_account := alice_account - money; 67 | B: bob_account := bob_account + money; 68 | C: assert alice_account >= 0; 69 | end algorithm *) 70 | ===== 71 | ``` 72 | 73 | 如果我们运行整个模型,会发现它是可以通过的。 74 | 75 | 至少,它适用于我们尝试的那个数字。 这并不意味着它适用于所有情况。 在测试时,通常很难选择恰当的测试用例来暴露你想要查找的错误。 这是因为大多数语言都可以轻松测试特定的状态,但不能测试大量的状态。 但是在TLA+中,可以测试范围很广: 76 | 77 | ```sql 78 | ---- MODULE transfer ---- 79 | EXTENDS Naturals, TLC 80 | 81 | (* --algorithm transfer 82 | variables alice_account = 10, bob_account = 10, money \in 1..20; 83 | 84 | begin 85 | A: alice_account := alice_account - money; 86 | B: bob_account := bob_account + money; 87 | C: assert alice_account >= 0; 88 | end algorithm *) 89 | ===== 90 | ``` 91 | 92 | 这里我们只做了一个改动:将`money = 5`修改为`money \in 1..20`,这个变动将算法里面的`money`的数值变化改为可以从1到20,所以检查模型时就有20种可能的状态: `(10, 10, 1), (10, 10, 2), ...`。TLA+将尝试所有的二十种可能性,并查看它们是否触发断言。这里我们还可以继续扩展:`alice_account \in {5, 10}`,这时候就有40种可能的状态了。 93 | 94 | (`{}`是集合。`a..b`是语法糖,代表"a和b之间所有整数的集合",所以`1..3` = `{1,2,3}`) 95 | 96 | 现在当我们运行模型检查时,我们会发现得到了一个错误: 97 | 98 | ![img4](/assets/model_assert_fail.png) 99 | 100 | 我们可以通过将整个检查包在一个`If`代码块中来解决这个问题: 101 | 102 | ```sql 103 | ---- MODULE transfer ---- 104 | EXTENDS Naturals, TLC 105 | 106 | (* --algorithm transfer 107 | variables alice_account = 10, bob_account = 10, money \in 1..20; 108 | 109 | begin 110 | Transfer: 111 | if alice_account >= money then 112 | A: alice_account := alice_account - money; 113 | B: bob_account := bob_account + money; 114 | end if; 115 | C: assert alice_account >= 0; 116 | end algorithm *) 117 | ===== 118 | ``` 119 | 120 | 现在可以正常运行了。 121 | 122 | 这更接近于测试所有可能的情况,但仍然没有测试所有可能的情况。如果`money`是`4997`,算法会出错吗? 如果我们真的想测试所有可能的情况,我们可以将`money \in 1..20`替换为`money \in Nat`,其中Nat是自然数字的集合。 这在TLA+是完全有效的。 不幸的是,它也是模型检查器无法处理的东西。 TLC只能检查TLA+的子集,而无限集不是其中的一部分。 123 | 124 | 125 | 126 | ### TLA+和不变量 127 | 128 | 你能汇一笔负数的钱吗? 我们可以在算法的开头添加一个断言:`assert money > 0`。 不过,这一次,我们将介绍一种新方法,为下一节做准备: 129 | 130 | ```sql 131 | ---- MODULE Transfer ---- 132 | EXTENDS Naturals, TLC 133 | 134 | (* --algorithm transfer 135 | variables alice_account = 10, bob_account = 10, money \in 1..20; 136 | 137 | begin 138 | Transfer: 139 | if alice_account >= money then 140 | A: alice_account := alice_account - money; 141 | B: bob_account := bob_account + money; 142 | end if; 143 | C: assert alice_account >= 0; 144 | 145 | end algorithm *) 146 | 147 | \* BEGIN TRANSLATION 148 | \* Removed for clarity... 149 | \* END TRANSLATION 150 | 151 | MoneyNotNegative == money >= 0 152 | 153 | ==== 154 | ``` 155 | 156 | 这里有一个多出来的部分,需要注意的是: 首先,这不是PlusCal算法的一部分。 这是我们放在文件底部的纯TLA +,以便能够引用转换后的TLA+。 只要在`END TRANSLATION`标记之后,TLA+就可以引用你的PlusCal所能提供的任何东西。 其次,它没有改变任何东西。 相反,它是系统的一个属性。 如果`money`是负数,`MoneyNotNegative`是`false`,否则就是`true`。 157 | 158 | 这与断言有什么不同? 断言检查在一个地方。 我们可以将`MoneyNotNegative`指定为系统的不变量,在所有可能的系统状态中都必须为`true`。 它成为模型的一部分,然后它将在从Alice的账户中提取资金之前进行检查,即存款和提款等操作。如果我们设置`money = 1`,当我们添加`money := money - 2`这一步时,`MoneyNotNegative`在任何一个状态下都会被捕获为失败。 159 | 160 | 161 | 162 | ### 再进一步:检查原子性 163 | 164 | 到目前为止,我们还没有做过任何与众不同的事情。 通过单元测试,以及我们所做的一切都可以在真实系统中轻松覆盖。 但我想表明我们已经可以用我们学到的东西来找到更复杂的错误。 Alice想给Bob1000美元。 如果我们不走运,它可能发生这样的情况: 165 | 166 | * 系统检查Alice拥有足够的money 167 | * 从Alice的账户减去$1000 168 | * Eve用棒球棒猛击服务器 169 | * Bob并没有收到这笔钱 170 | * $1000完全从整个系统中消失了 171 | * 证券交易委员会以欺诈为由将你的银行关闭。 172 | 173 | 但我们已经有了完整的工具链来检查这个问题了。首先,我们需要弄清楚如何表示不变量的限制被触发?我们可以通过要求系统中的总资金总是相同的来实现这一点: 174 | 175 | ```sql 176 | ---- MODULE Transfer ---- 177 | EXTENDS Naturals, TLC 178 | 179 | (* --algorithm transfer 180 | variables alice_account = 10, bob_account = 10, money \in 1..20, 181 | account_total = alice_account + bob_account; 182 | 183 | begin 184 | Transfer: 185 | if alice_account >= money then 186 | A: alice_account := alice_account - money; 187 | B: bob_account := bob_account + money; 188 | end if; 189 | C: assert alice_account >= 0; 190 | 191 | end algorithm *) 192 | 193 | MoneyNotNegative == money >= 0 194 | MoneyInvariant == alice_account + bob_account = account_total 195 | 196 | ==== 197 | ``` 198 | 199 | 然后,我们在模型中声明这个要被检查的不变量: 200 | 201 | ![img5](/assets/model_moneyinvariant1.png) 202 | 203 | ![img6](/assets/model_moneyinvariant2.png) 204 | 205 | 当我们运行的时候,TLC发现了一个反例:在步骤a和步骤B之间,不变量是不成立。 206 | 207 | ![img7](/assets/model_moneyinvariant_fail.png) 208 | 209 | 我们怎么解决这个问题?它取决于我们关心的抽象级别。如果您正在设计一个数据库,那么您需要确保每一步操作都会保持系统的一致性。在我们的目前级别上,我们通过使用数据库事务,并可以“抽象出”原子性检查。我们的方法是将A和B合并为一个“事务”步骤。这告诉TLA+这两种操作同时发生,并且系统永远不会到达一个无效的状态。 210 | 211 | ```sql 212 | ---- MODULE Transfer ---- 213 | EXTENDS Naturals, TLC 214 | 215 | (* --algorithm transfer 216 | variables alice_account = 10, bob_account = 10, money \in 1..20, 217 | account_total = alice_account + bob_account; 218 | 219 | begin 220 | Transfer: 221 | if alice_account >= money then 222 | A: alice_account := alice_account - money; 223 | bob_account := bob_account + money; \* Both now part of A 224 | end if; 225 | C: assert alice_account >= 0; 226 | 227 | end algorithm *) 228 | 229 | MoneyNotNegative == money >= 0 230 | MoneyInvariant == alice_account + bob_account = account_total 231 | 232 | ==== 233 | ``` 234 | 235 | 236 | 237 | ### 多进程算法 238 | 239 | 作为示例的最后一部分,让我们讨论并发。现在我们的系统似乎足够稳定,但是如果是两拨人之间的进行多次交易会发生什么呢?每个帐户都使用相同的帐户状态,但彼此独立操作,所以我们能测试这个系统的行为吗? 240 | 241 | PlusCal支持多处理算法。完全不同的算法刻意同时发生,也可以是相同的算法并行发生(或者两者都发生),后者是我们想要说明的。 242 | 243 | ```sql 244 | ---- MODULE Transfer ---- 245 | EXTENDS Naturals, TLC 246 | 247 | (* --algorithm transfer 248 | variables alice_account = 10, bob_account = 10, 249 | account_total = alice_account + bob_account; 250 | 251 | process Transfer \in 1..2 252 | variable money \in 1..20; 253 | begin 254 | Transfer: 255 | if alice_account >= money then 256 | A: alice_account := alice_account - money; 257 | bob_account := bob_account + money; 258 | end if; 259 | C: assert alice_account >= 0; 260 | end process 261 | 262 | end algorithm *) 263 | 264 | MoneyNotNegative == money >= 0 265 | MoneyInvariant == alice_account + bob_account = account_total 266 | 267 | ==== 268 | ``` 269 | 270 | 这些账户是全局变量,而货币是进程的局部变量。这意味着有400个可能的初始状态,因为第一个转移可以是1美元,第二个是7美元。然而,实际上有2400种可能的行为!这是因为TLC可以选择运行进程的顺序,以及如何交替执行。 271 | 272 | 然而,`MoneyNotNegative`现在没什么用了,因为money有两种值。如果你将它已经添加到模型中,请确保取消检查它以避免错误,然后重新运行后你应该得到以下错误: 273 | 274 | ![img8](/assets/multiprocess_fail.png) 275 | 276 | 我们检查Alice是否有足够的钱与我们实际转账时之间是有差距的。对于一个进程来说,这不是问题,但是对于两个进程来说,这意味着她的账户可以变成负数。TLC可以提供复制错误所需的初始状态和步骤。 277 | 278 | 此示例仅涵盖工具的一小部分;我们甚至还没有接触到时间属性、活跃度或设置操作。不过,希望这已经向你表现了即使是对TLA+的粗略了解也足以发现有趣的问题。 -------------------------------------------------------------------------------- /expressions.md: -------------------------------------------------------------------------------- 1 | # 表达式 2 | 3 | 到目前为止,我们一直在隐式地使用表达式;我们只是没说清楚。对于我们的目的,表达式是任何跟在`==`,` =`,`:=`或`\in`后面的东西。在本节中,我们将介绍一些通用表达式修饰符。 4 | 5 | ## 逻辑联结词 6 | 7 | 我们已经使用了逻辑联结词有一段时间:`/\` (且),`\/`( 或)。我们可以把表达式和它们连接起来。一个微妙之处是,这是TLA+对空格敏感的唯一情况。如果你对某一行进行缩进,TLA+将认为这是子句的开始。例如: 8 | 9 | ``` 10 | /\ TRUE 11 | \/ TRUE 12 | /\ FALSE \* (T \/ T) /\ F 13 | 14 | /\ TRUE 15 | \/ TRUE 16 | \/ FALSE \* (T \/ T \/ F) 17 | 18 | \/ TRUE 19 | \/ TRUE 20 | /\ FALSE \* T \/ (T /\ F) 21 | ``` 22 | 23 | 这有几点经验: 24 | 25 | * 如果两个逻辑运算符位于相同的缩进级别,那么它们属于相同的表达式级别。 26 | * 如果逻辑运算符位于缩进的更高级别,则它是前面运算符语句的一部分。 27 | * 每个缩进级别只使用一种类型的运算符。 28 | 29 | 30 | 31 | ## LET-IN 32 | 33 | 任何表达式都可以使用`LET-IN`将局部运算符和定义单独添加到该表达式中: 34 | 35 | ``` 36 | LET IsEven(x) == x % 2 = 0 37 | IN IsEven(5) 38 | 39 | LET IsEven(x) == x % 2 = 0 40 | Five == 5 41 | IN IsEven(Five) 42 | 43 | LET IsEven(x) == LET Two == 2 44 | Zero == 0 45 | IN x % Two = Zero 46 | Five == 5 47 | IN IsEven(Five) 48 | ``` 49 | 50 | 这里的空格并不重要:我们可以`LET IsEven(x) == x % 2 = 0 Five == 5 IN IsEven(Five)`,它将正确解析为`LET-IN`中的两个独立操作符。但是为了易读性,你还是应该使用换行符。 51 | 52 | 53 | 54 | ## IF-THEN-ELSE 55 | 56 | 正如你所期望的: 57 | 58 | ``` 59 | IsEven(x) == IF x % 2 = 0 60 | THEN TRUE 61 | ELSE FALSE 62 | ``` 63 | 64 | 和之前一样,是否对齐并不重要,但是你应该对齐它们,除非你真的讨厌你的同事。 65 | 66 | 67 | 68 | ## CASE 69 | 70 | 大多数情况下,情况都是你所期望的一样,但有一个细微的不同: 71 | 72 | ``` 73 | CASE x = 1 -> TRUE 74 | [] x = 2 -> TRUE 75 | [] x = 3 -> 7 76 | [] OTHER -> FALSE 77 | ``` 78 | 79 | `OTHER`的是默认的。如果没有一种情况匹配,而你遗漏了另一种情况,TLC认为这是一个错误。如果有多个匹配项,TLC将为你选择一个,而不是分支。换句话说,就是下面的代码: 80 | 81 | ``` 82 | CASE TRUE -> FALSE 83 | [] TRUE -> TRUE 84 | ``` 85 | 86 | 87 | 88 | ## Nesting(嵌套) 89 | 90 | 表达式的所有部分都是表达式或标识符,因此可以将表达式放在其他表达式中。此外,所有表达式都可以在PlusCal代码中使用。 -------------------------------------------------------------------------------- /introduction.md: -------------------------------------------------------------------------------- 1 | ## 什么是TLA+? 2 | 3 | TLA+是一种形式化描述语言。它是一种以编程的方式验证设计的系统或算法是否存在严重的Bug,相当于软件版的蓝图。 4 | 5 | ## 这是一个初学者友好的教程吗? 6 | 7 | 本教程以简单实用的方式介绍了TLA +的基础知识。如果您想从头开始,你需要先了解在[这里](about-this-guide.md)介绍的内容。如果你想先直接深入进来,可以先试试这个[例子](example.md)? 8 | 9 | 无论哪种方式,欢迎来到TLA+! 10 | -------------------------------------------------------------------------------- /operators.md: -------------------------------------------------------------------------------- 1 | # OPERATORS 2 | 3 | 操作符一组一起执行一个任务的语句,程序员可能会把它称为函数,你使用一个 `==`来定义它们。 4 | 5 | ```c++ 6 | Five == 5 7 | Five + 5 \* 10 8 | ``` 9 | 10 | 你也可以给它们参数 11 | 12 | ```c++ 13 | IsFive(x) == x = 5 14 | ``` 15 | 16 | 或者多个参数 17 | 18 | ```c++ 19 | SumWithFive(a, b) == a + b + 5 20 | ``` 21 | 22 | 你可以在任何使用其他表达式的地方使用操作符。 23 | 24 | > Tip 25 | 26 | 27 | 28 | ## 高阶运算符 29 | 30 | 你可以使用特殊的语法编写高阶运算符调用其他的运算符: 31 | 32 | ``` 33 | Sum(a, b) == a + b 34 | Do(op(_,_), a, b) == op(a, b) 35 | 36 | Do(Sum, 1, 2) = 3 37 | ``` 38 | 39 | 如果在调用之前使用`RECURSIVE`指定操作符,也可以进行操作符递归: 40 | 41 | ``` 42 | RECURSIVE SetReduce(_, _, _) 43 | 44 | SetReduce(Op(_, _), S, value) == IF S = {} THEN value 45 | ELSE LET s == CHOOSE s \in S: TRUE 46 | IN SetReduce(Op, S \ {s}, Op(s, value)) 47 | 48 | CandlesOnChannukah == SetReduce(Sum, 2..9, 0) \* 44 49 | ``` 50 | 51 | 52 | 53 | ## 结合PlusCal 54 | 55 | 如果你的操作符不引用任何PlusCal变量(如`IsEmpty(S) == S = {}`),那么您可以将其置于PlusCal算法的开始之上,它将像其他表达式一样可用。 56 | 57 | 如果你的操作符确实引用任何PlusCal变量(比如`HasMoneyLeft == money > 0`,那么它必须出现在算法中或之后。第一个方法是把它放在TLA+翻译之后。这很简单,但也意味着PlusCal不能调用这个操作符。这对不变量很好,对条件不太好。要将运算符直接放在代码中,可以使用`define`定义代码块: 58 | 59 | ``` 60 | define 61 | Foo(bar) == baz 62 | \* ... 63 | end define 64 | ``` -------------------------------------------------------------------------------- /pluscal.md: -------------------------------------------------------------------------------- 1 | # PlusCal 2 | 3 | PlusCal是一个与TLA+的协作的工具:它为specification添加了一个伪代码接口,使程序员更容易理解。虽然并不是所有的specification都可以用PlusCal来编写,但也有相当一部分是可以的,这是建模一个很好的切入点。我们将在本教程中编写的所有specification都以PlusCal为核心。 -------------------------------------------------------------------------------- /tla+.md: -------------------------------------------------------------------------------- 1 | # TLA+ 2 | 3 | 现在我们有了我们的脚手架PlusCal,是时候学习TLA+了。TLA+是一种数学描述语言。不是说如何找到你想要的值,而是说你想要的属性。例如,集合中最大的元素是`CHOOSE x \in S : \A y \in S : y <= x`,这不是一个算法,它只是“比其他数字大的数字。” 4 | 5 | 这给了我们惊人的规格能力。复杂的属性和不变量可以用几行来表示。不利的一面是,它也可能非常令人生畏。也有可能意外地构建不可检查的模型。例如,很容易定义所有停止的通用图灵机器的集合。但是,如果您将它输入到TLC,它将返回一个错误。写TLA+ spec有点艺术。让我们开始吧。 6 | 7 | 注意事项:我们将几乎完全忽略“时间逻辑”的“暂时”部分。作为一种简单的启发式,我们永远不会讨论“启动和非启动操作符”,这大约占指定系统的95%。在它的核心,TLA+是一个优美的方式来优雅地表达一个复杂系统的时间属性。对于我们的使用,TLA+是一种编写更好的plus spec的方法。Sacriligious吗?可能。容易吗?是的。 -------------------------------------------------------------------------------- /using_the_toolbox.md: -------------------------------------------------------------------------------- 1 | # 使用ToolBox 2 | 3 | 我们编写specifications的目的是为了发现隐藏在specifications中的错误。为此,我们首先需要将我们的PlusCal代码转换为TLA+,然后告诉模型检查器要查找什么。这两个都有点棘手。幸运的是,我们有一个可用的IDE,叫做[TLA+ Toolbox](https://research.microsoft.com/en-us/um/people/lamport/tla/toolbox.html)。我们将使用以下示例作为介绍: 4 | 5 | ```sql 6 | ---- MODULE example ---- 7 | EXTENDS Integers 8 | 9 | (* --algorithm example 10 | variables x = 5 11 | begin 12 | Add: 13 | x := x + 1; 14 | end algorithm; *) 15 | ==== 16 | ``` 17 | 18 | 创建一个新的spec(记住名字必须匹配)并将其复制进去。运行转换器,首先你会看到你的文件被这样更新了: 19 | 20 | ```sql 21 | \* BEGIN TRANSLATION 22 | 23 | A WHOLE LOTTA STUFF 24 | 25 | \* END TRANSLATION 26 | ``` 27 | 28 | 这是生成的TLA +。关于为什么我们要从PlusCal开始,可能现在说得通了。不过别担心,我们将在下一章深入介绍TLA+,因此目前你不需要完全理解整个翻译。但这就是我们要进行模型检查的实际代码: 29 | 30 | 下一步,我们新建一个model,告诉TLC需要检查什么。我们感兴趣的部分是overview,应该是这样的: 31 | 32 | ![model_overview](/assets/model_overview.png) 33 | 34 | Terms: 35 | 36 | * **Model Description:**没有什么意义,只是文档。 37 | * **What is the behavior spec?**:暂时把它放在“时间公式”上。 38 | * **What is the model?:** 我们将在“Models”一章中讨论这个问题。 39 | * **What to check?:**这就是我们所关心的, 40 | * *Deadlock*检查程序本身不能捕获22。 41 | * *Invariants*检查某个方程,例如`x = TRUE`,对于TLC可以达到的所有可能状态都是正确的。 42 | * *Temporal*属性检查所有可能的行为是否为真,即“生存期”。例如,“x从TRUE切换到FALSE并返回”。 43 | * **How to run?:** 运行时优化。这是超出范围的。 44 | 45 | 我们可以通过单击左上方的绿色箭头来运行模型。TLC将搜索整个状态空间,在我们的不变量中寻找可能的错误。由于我们实际上还没有设置任何不变量,它不会发现任何失败,所以这将是成功的。注意,它列出了直径为2,不同状态数为2。前者意味着最长的状态空间的步数有两步: 初始状态`x = 5`和`Add`之后`x = 6`。因为只有一个可能的起始状态,所以总共有两个不同的状态: 46 | 47 | ![model_run](/assets/model_run.png) 48 | 49 | 如果我们把`x = 5`换成`x \in 1..5`会发生什么?重新转换算法并重新运行模型。你会看到,虽然最长的状态空间的步数仍然是2,但现在有10个不同的状态。由于有5个潜在的初始值,TLC将运行带有所有这些值的模型,以防其中一个值违反不变量(虽然还是没有设置)。 50 | 51 | 那么,让我们添加一个会失败的不变量吧。回到模型概述,并将`x /= 6`添加到“不变量”部分,这个不变量意味着我们的程序的x的值不会是6。很明显,当x = 5时它就失效了,这是可能的初始状态之一。当我们重新运行模型时,我们会得到这样的结果: 52 | 53 | ![model_run](/assets/invariant.png) 54 | 55 | ![model_run](/assets/error_trace.png) 56 | 57 | 这不仅向我们展示了什么是失败的,也向我们展示了它是如何失败的。在这种情况下,“初始状态谓词”告诉我们起始状态:x是5。下面的“动作”行显示“添加”步骤已经完成,x变为6,这将使我们的不变量无效。 58 | 59 | ### 计算表达式 60 | 61 | 我们切换回Model的overview部分。将行为规范更改为“no behavior spec”。这告诉TLC,你正在建模的系统是不存在的,你只是在尝试TLA+语法。我们不能在这里运行任何东西。但是我们可以做的是到“模型检查结果”选项中,把TLA放到“evaluate constant expression”中。试着输入`CHOOSE y \in {1, 2, 3} : y*y = 4`在这个选项中,然后重新运行模型。您应该会在”Value”框中看到`2`。 62 | 63 | 我们不会使用“no behavior spec”模式来测试实际系统,但它是一个很好的工具,可以测试您理解一些TLA+表达式是如何工作的。别担心,当我们深入到TLA+时,我们会重新介绍这一点。 --------------------------------------------------------------------------------