diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index 64ff7311..6674c72f 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -4,7 +4,7 @@ ::: warning 基础要求 以下教程需要你具有一定基础。 具体来说,如果你已经知道在编程领域什么是变量(variable)、 -赋值(assign)、字符串(string)、函数(function)及参数(argument), +字符串(string)、函数(function)及参数(argument), 那么你的知识水平就差不多足够了。 @@ -27,7 +27,7 @@ Nix 作为语言,是一门简单的函数式语言,它被专门设计并用 ::: warning 本节需要你已经安装了 Nix 或正在使用 NixOS。 -另外,本教程中的示例代码一般不是为了供直接运行而写的。对于每一段代码,若想实践其 +另外,本教程中的示例代码并不全是为了供直接运行而写的。对于每一段代码,若想实践其 效果,请先理解对应的知识,再基于这段代码自己编写测试代码以运行。 (对于 Nix 来说,运行代码被称为**求值**(evaluate),而只有**表达 @@ -59,7 +59,7 @@ nix-repl> 1 + 2 ``` -回车,得到输出结果如下: +回车即可求值,得到结果如下: ```plain 3 @@ -77,7 +77,7 @@ nix-repl> 交互模式简单快捷,但我们平时使用 Nix 语言进行编辑配置、打包等操作时,大多数情况 下不会直接使用交互模式,而是对 `*.nix` 纯文本文件进行编辑。 -因此,如果你习惯于使用编辑器,这里更推荐利用文件求值进行实践。 +因此,如果你习惯于使用编辑器,这里更推荐利用文件求值进行实践。每个 nix 文件的内容都是**一个**表达式,这是 nix 文件能被求值的前提。 例如,新建文件 `foo.nix`,将其内容编辑如下: @@ -105,7 +105,7 @@ nix-instantiate --eval foo.nix
单击以切换折叠/展开 Nix 的求值具有惰性(laziness),只会在有必要时进行。例如,下述代码(看不懂没关 -系)将 `a` 的值赋为 $\dfrac{2}{0}$ ,这是一种典型的数学错误: +系)将名称 `a` 分配给值 $\dfrac{2}{0}$ ,这是一种典型的数学错误: ```nix let a = builtins.div 2 0; b = 3; in b @@ -116,7 +116,7 @@ let a = builtins.div 2 0; b = 3; in b --- -与惰性类似的是另一种行为是,嵌套集合的求值,在交互模式和文件求值模式下,除非必 +与惰性类似的是另一种行为是,嵌套属性集的求值,在交互模式和文件求值模式下,除非必 要,默认不会迭代,而是以占位符替代,例如 ```nix @@ -165,16 +165,16 @@ let a = builtins.div 2 0; b = 3; in b - 注释:在 Nix 语言中,用 `#` 表示注释,在它之后直到行末的部分都会被忽略。 - 缩进与换行:与 Python 这种对缩进有要求的语言不同,在 Nix 语言中,大多数情况下,换行与缩进只是为了更好的可读性,并不影响代码的本质。 -例如,下面有两段示例代码(你目前还不需要理解它们的含义),它们在本质上(也即在 Nix 解释器看来)并没有区别。 +例如,下面的两段示例代码(你目前还不需要理解它们的含义),它们在本质上(也即在 Nix 解释器看来)并没有区别。 第一例: ```nix -foo = { a = 1; b = 2; } +{ a = 1; b = 2; } ``` 第二例: ```nix -foo = { # 这是一句注释,放在代码末尾。 +{ # 这是一句注释,放在代码末尾。 # 这也是一句注释,单独占了一行。 a = 1; # 这里即使不缩进,也不影响代码本质。 b = 2; @@ -189,21 +189,29 @@ foo = { # 这是一句注释,放在代码末尾。 ::: -## 赋值与集合 -与大多数编程语言类似,变量与值是 Nix 语言中最基础的概念。本节将会介绍 Nix 中如何为变量**赋值**,以及最常用的数据类型——**属性集**,继而引出**递归属性集**与**列表**的概念。 +## 名称与属性集 +变量是大多数编程语言中最基础的概念,而与之类似的名称则是 Nix 语言中最基础的概念。本节将会介绍 Nix 中如何将名称**分配给值**,以及最常用的数据类型——**属性集**,继而引出**递归属性集**与**列表**的概念。 + ### 名称和值 -我们可以使用 `=` 为名称绑定值,形成赋值语句。例如将名称 `foo` 赋值为 `123`: +我们可以使用 `=` 将名称分配给值,形成“名称 - 值”对。例如将名称 `foo` 分配给值 `123`: ```nix foo = 123 ``` -::: info 名称与其它语言中变量的区别 -在很多语言中,赋值会改变变量的状态,而 Nix 语言中的名称一旦赋值(定义)就无法改变。 +::: info 函数式语言与命令式语言中“变量”的区别 +| 维度 | 命令式语言 | Nix(函数式) | +|---|---|---| +| 底层模型 | 存储格(内存单元) | 无存储格,只有「名称-值」映射 | +| 操作 | 赋值:随时把新值写回同一单元 | 绑定:一次性把名称贴到值,不可重写 | +| 所谓“变量” | 存储格的别名 → 之后可反复擦写 | 数学意义上的变量 → 同一作用域内值固定 | +| 文档用词 | variable = 可重写的存储格 | variable = 一次性绑定的名称(不会变) | + +一句话: +在 Nix 里,“名称”就是“变量”,只是这个变量一旦绑定便成永恒;它保留了数学“可取不同值”的语义,却丢掉了命令式“可重新赋值”的存储格含义。 -不过,将 Nix 中的名称称为变量也没问题。 ::: @@ -221,7 +229,7 @@ foo = 123 ### 属性集 -在 Nix 语法中,属性集(简称集合)是最常见的数据类型之一,基本示例如下: +在 Nix 语法中,属性集是最常见的数据类型之一,基本示例如下: ```nix foo = { @@ -233,22 +241,22 @@ foo = { 概念说明: - 属性集(attribute set)就是装载若干对**名称与值**的集合。 -- 集合内的名称被称为这个集合的**属性**(attribute); -- 集合内由名称和值组成的对被称为该属性的**元素**(element); +- 属性集内的名称被称为这个属性集的**属性**(attribute); +- 属性集内由名称和值组成的对被称为该属性的**元素**(element); 语法说明: -- 集合以 `{` `}` 为边界,其内部为多个赋值语句,且各个赋值语句末尾必须添加 `;` 。 +- 属性集以 `{` `}` 为边界,其内部为多个“名称-值”对,且它们末尾必须添加 `;` 。 -上述代码将 `foo` 的值定义为集合 `{ a = 1; b = 2; }` ,因此可称之为集合 `foo` 。 +上述代码将 `foo` 的值定义为属性集 `{ a = 1; b = 2; }` ,因此可称之为属性集 `foo` 。 -集合 `foo` 中有两个属性: +属性集 `foo` 中有两个属性: - 属性 `a`,其值为 `1` - 属性 `b`,其值为 `2` -属性的值除了可以是 `1` `2` 这样的数值外,也可以是一个集合(也即支持嵌套),例如 -将 `b` 的值改为集合 `{ c = 2; d = 3; }`: +属性的值除了可以是 `1` `2` 这样的数值外,也可以是一个属性集(也即支持嵌套),例如 +将 `b` 的值改为属性集 `{ c = 2; d = 3; }`: ```nix foo = { @@ -260,7 +268,7 @@ foo = { }; ``` -嵌套集合中的属性也可以利用 `.` 表示,例如上面这段的一种等价写法如下: +嵌套属性集中的属性也可以利用 `.` 表示,例如上面这段的一种等价写法如下: ```nix foo.a = 1; @@ -288,7 +296,7 @@ foo.b.d = 3; ``` error: undefined variable 'a' ``` -可见,当属性集内的属性 `b` 需要访问该集合的另一个属性 `a` 时,即使 `a` 是“先”定义的,也无法访问到。此时就需要我们改用递归(recursive)属性集,它相比普通的属性集,在前面多加了 `rec `: +可见,当属性集内的属性 `b` 需要访问该属性集的另一个属性 `a` 时,即使 `a` 是“先”定义的,也无法访问到。此时就需要我们改用递归(recursive)属性集,它相比普通的属性集,在前面多加了 `rec `: ```nix rec { @@ -303,7 +311,7 @@ rec { { a = 1; b = 3; } ``` -可以看到,结果中的 `a = 1` 在前面,`b = 3` 在后面。这种顺序实际上与任何其它因素(包括声明顺序、求值依赖关系)都无关,而只与**属性名称本身的排序**有关。例如,对 `rec { a = 1; b = 2; }` 与 `rec { b = 2; a = 1; }` 的求值,都会把 `a = 1` 放在前面,归因到底,这只是因为 `a` 在字母表中位于 `b` 之前罢了。(直接原因则与 Nix 解释器对名称排序所用到的算法或者调用的库有关,这里不再深入。) +可以看到,结果中的 `a = 1` 在前面,`b = 3` 在后面。这种顺序实际上与任何其它因素(包括声明顺序、求值依赖关系)都无关,而只与**属性名称本身的排序**有关。例如,对 `rec { a = 1; b = 2; }` 与 `rec { b = 2; a = 1; }` 的求值,都会把 `a = 1` 放在前面,归因到底,只是 `a` 在字母表中位于 `b` 之前罢了。(直接原因则与 Nix 解释器对名称排序所用到的算法或者调用的库有关,这里不再深入。) 既然如此,将上面属性集里的两个元素位置对调: ```nix @@ -352,13 +360,13 @@ fruits = { fruits = [ "apple" "orange" "banana" ] ``` 需要注意语法细节: -- 列表以 `[` `]` 为边界,其内部为多个元素,每个元素都是值(value)而不是赋值语句。 +- 列表以 `[` `]` 为边界,其内部为多个元素,每个元素都是值(value)。 - 元素之间使用空格(或换行)分隔,各元素**不**以 `;` 结尾。 ## let 绑定与属性访问 -前面关于变量的赋值与使用是非常基本的,我们还需要更灵活的处理方法。本节将会介绍用于定义局部变量的 **let 绑定**,以及风格简洁的**属性访问**。 +前面关于名称的使用是非常基本的,我们还需要更灵活的处理方法。本节将会介绍另一种将名称分配给值的方法——**let 绑定**,以及风格简洁的**属性访问**。 ### `let` 绑定 -有时我们希望定义一个变量,使其不影响全局,仅在局部生效。此时就可以使用 `let` 绑定,示例如下: +有时我们希望在指定的范围内为值分配名称,此时就可以使用 `let` 绑定,示例如下: ```nix let @@ -369,7 +377,7 @@ in ``` 注意语法细节: -- `let` 与 `in` 之间的赋值语句以 `;` 结尾; +- `let` 与 `in` 之间的“名称-值”对以 `;` 结尾; - `in` 之后**只有一个表达式**。注意,这只是语法形式上的要求,并不代表 `let` 绑定的用处很有限,因为表达式本身可以很复杂,常见的是嵌套属性集。作为基本示例,下面演示刚刚学到的列表: ```nix let @@ -404,6 +412,18 @@ error: undefined variable 'x' ::: + +::: note 局部变量(?) +Nix 中不存在“全局变量”,因而“局部变量”的说法可能引起误会,应当尽量避免使用。 + +不过,[Nix manual](https://nix.dev/manual/nix/2.31/language/syntax.html) 中对 let 绑定的介绍提到了局部变量(local variable)。 +> A let-expression allows you to define local variables for an expression. + +这种说法可能不合适,但既然官方文档也有用到,其他地方自然也可能会出现,留心即可。 + + +::: + ### 属性访问 使用 `.` 访问属性: @@ -424,7 +444,7 @@ in attrset.a.b.c ``` -当然,就像如何访问属性一样,也可以用 `.` 直接赋值它: +当然,就像如何访问属性一样,也可以用 `.` 为值分配名称: ```nix let @@ -437,7 +457,7 @@ in 语法糖(syntactic sugar)是对语言功能没有影响,但更方便使用的一种语法。本节将介绍两种常用的语法糖 `with` 和 `inherit`。 ### `with` 表达式 -`with` 表达式可以让你少写几次属性集的名称,是个语法糖: +`with` 表达式可以让你少写几次属性集的名称: ```nix let @@ -481,7 +501,7 @@ error: undefined variable 'x' ### `inherit` 表达式 -`inherit` 本意就是继承,我们可以使用它完成一对命名相同的名称和属性之间的赋值: +`inherit` 本意就是继承,我们可以使用它让属性继承名称的值: ```nix let @@ -740,7 +760,7 @@ x: y: x + y { a, b }: a + b ``` -为函数指定默认参数,在缺省该参数赋值的情况下,它就是默认值: +为函数指定默认参数,在缺少该参数的值的情况下,它就是默认值: ```nix { a, b ? 0 }: a + b @@ -836,7 +856,7 @@ in let g = f 1; in g 2 ``` -也可以一次性赋值: +也可以一次性接收两个参数: ```nix let @@ -895,7 +915,7 @@ in f { a = 1; } ``` -赋值是可选的,根据你的需要来: +传入参数值是可选的,根据你的需要来: ```nix let @@ -925,7 +945,7 @@ in ## 命名参数集 又名 "`@` 模式"。在上文中,我们已经可以接收到额外的参数了,假如我们需要使用某 -个额外参数,我们可以使用命名属性集将其接收到一个另外的集合: +个额外参数,我们可以使用命名属性集将其接收到一个另外的属性集: ```nix {a, b, ...}@args: a + b + args.c # 这样声明函数