diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index ff647ae6..64ff7311 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -10,6 +10,10 @@ ::: +
仅面向本文维护者的说明,单击以切换折叠/展开 +本文在设计上是线性的,也即只需要读者具备一点点基础,就可以通过按顺序从头读到尾的方式完成本文的学习。因此,请留心说明顺序,例如讲 let 绑定时如果举了一个列表的例子,你需要确保前面已经正式介绍过列表。再如,讲 with 语法糖的时候同时用到 let 绑定和列表,那么这两个概念都需要在前面已经正式介绍过。否则,读者很可能会面对初次接触的语法或者概念而被卡住,这会严重影响学习效率甚至是完成率。若出于顺序安排的其他合理性原因,实在无法避开在说明中涉及陌生概念,可以提示读者相关部分不需要理解,后面会讲到。 +
+ Nix 作为语言,是一门简单的函数式语言,它被专门设计并用于 Nix 包管理器及相关生态 (NixOS、Home-Manager 等)。 @@ -155,7 +159,39 @@ let a = builtins.div 2 0; b = 3; in b **好了,下面正式介绍 Nix 语法。** -## 名称和值 +## 注释、缩进与换行 +注释、缩进与换行的语法与机制,对编程语言的风格有重要影响。本节将介绍 Nix 语言中的注释、缩进与换行。 + +- 注释:在 Nix 语言中,用 `#` 表示注释,在它之后直到行末的部分都会被忽略。 +- 缩进与换行:与 Python 这种对缩进有要求的语言不同,在 Nix 语言中,大多数情况下,换行与缩进只是为了更好的可读性,并不影响代码的本质。 + +例如,下面有两段示例代码(你目前还不需要理解它们的含义),它们在本质上(也即在 Nix 解释器看来)并没有区别。 + +第一例: +```nix +foo = { a = 1; b = 2; } +``` + +第二例: +```nix +foo = { # 这是一句注释,放在代码末尾。 + # 这也是一句注释,单独占了一行。 + a = 1; # 这里即使不缩进,也不影响代码本质。 + b = 2; + # c = 3; # 这里的代码被注释掉了,相当于不存在。 +} +``` + + +::: warning +换行与空格一样具有分隔作用,请勿在不可分隔的地方胡乱断行。 + + +::: + +## 赋值与集合 +与大多数编程语言类似,变量与值是 Nix 语言中最基础的概念。本节将会介绍 Nix 中如何为变量**赋值**,以及最常用的数据类型——**属性集**,继而引出**递归属性集**与**列表**的概念。 +### 名称和值 我们可以使用 `=` 为名称绑定值,形成赋值语句。例如将名称 `foo` 赋值为 `123`: @@ -164,9 +200,10 @@ foo = 123 ``` -::: info 名称与变量的区别 -名称不等同常见编程语言中的变量。 -传统的赋值会改变变量的状态,而 Nix 语言中的名称一旦赋值(定义)就无法改变。 +::: info 名称与其它语言中变量的区别 +在很多语言中,赋值会改变变量的状态,而 Nix 语言中的名称一旦赋值(定义)就无法改变。 + +不过,将 Nix 中的名称称为变量也没问题。 ::: @@ -182,7 +219,7 @@ foo = 123 - 列表(list),例如 `[ 1 "tux" false ]` - 属性集(attribute set),例如 `{ a = 1; b = "tux"; c = false; }` -## 属性集 +### 属性集 在 Nix 语法中,属性集(简称集合)是最常见的数据类型之一,基本示例如下: @@ -203,26 +240,6 @@ foo = { - 集合以 `{` `}` 为边界,其内部为多个赋值语句,且各个赋值语句末尾必须添加 `;` 。 - -::: info 换行与缩进 -对于 Nix 语言,大多数情况下,换行与缩进只是为了更好的可读性。 - -例如,上述代码与下面的代码在本质上并没有区别: - -```nix -foo = { a = 1; b = 2; }; -``` - - -::: - - -::: warning -换行与空格一样具有分隔作用,请勿在不可分隔的地方胡乱断行。 - - -::: - 上述代码将 `foo` 的值定义为集合 `{ a = 1; b = 2; }` ,因此可称之为集合 `foo` 。 集合 `foo` 中有两个属性: @@ -258,7 +275,7 @@ foo.b.d = 3; ::: -## 递归属性集 +### 递归属性集 普通的属性集不支持递归引用,举个例子: ```nix @@ -286,7 +303,7 @@ rec { { a = 1; b = 3; } ``` -可以看到,结果中的 `a = 1` 在前面,`b = 2` 在后面。这种顺序实际上与任何其它因素(包括声明顺序、求值依赖关系)都无关,而只与**属性名称本身的排序**有关。例如,对 `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 @@ -295,7 +312,7 @@ rec { a = 1; } ``` -你会发现,Nix 也能自动处理求值顺序,并不会因为 `a` 的声明被调整到后面而影响求值结果(与之前的完全一致,这里就不贴了)。这看起来相当“智能”,你甚至可以写得更复杂一些,比如 Nix 也能自动处理下面的例子(结果略): +你会发现,Nix 解释器能自动处理求值顺序,并不会因为 `a` 的声明被调整到后面而影响求值结果(与之前的完全一致,从略)。这看起来相当“智能”,你甚至可以写得更复杂一些,比如 Nix 解释器也能自动处理下面的例子(结果略): ```nix rec { c = a * 2 - b + d - 35; @@ -319,34 +336,41 @@ rec { b = «error: infinite recursion encountered»; } ``` -从这个输出来看,我们可以发现递归属性集内部在处理求值顺序的机制,确实是递归的,而如果递归陷入死循环就会报错。 +由此可见,递归属性集内部处理求值顺序的机制,确实是递归的,而如果递归陷入死循环就会报错。 -## `let` 绑定 +### 列表 +之前我们学习了属性集,它含有多个元素,例如: +```nix +fruits = { + a = "apple"; + b = "orange"; + c = "banana"; +} +``` +上面的名称 `a` `b` `c` 或许可以有明确的含义,但有时我们不需要这些名称,而只关心后面的值,这种情况下就可以使用列表,例如: +```nix +fruits = [ "apple" "orange" "banana" ] +``` +需要注意语法细节: +- 列表以 `[` `]` 为边界,其内部为多个元素,每个元素都是值(value)而不是赋值语句。 +- 元素之间使用空格(或换行)分隔,各元素**不**以 `;` 结尾。 -一个完整的 `let` 绑定有两个部分: `let` 绑定名称与值, `in` 使用名称。在 `let` -与 `in` 之间的语句中,你可以声明需要被复用的名称,并将其与值绑定。它们可以在 -`in` 之后的表达式中发挥作用: +## let 绑定与属性访问 +前面关于变量的赋值与使用是非常基本的,我们还需要更灵活的处理方法。本节将会介绍用于定义局部变量的 **let 绑定**,以及风格简洁的**属性访问**。 +### `let` 绑定 +有时我们希望定义一个变量,使其不影响全局,仅在局部生效。此时就可以使用 `let` 绑定,示例如下: ```nix let - b = a + 1; a = 1; + b = 2; in - a + b + a + b # 结果是 2 ``` -引用到 `a` 的地方有两处,它们都会将 `a` "替换"成值来计算或赋值,类似于常量。 - - -::: tip -你不需要关心名称的声明顺序,不会出现名称未定义的情况。 - - -::: - -**`in` 后面只能跟随一个表达式,并且 `let` 绑定的名称只在该表达式是有效的的**,这 -里演示一个列表: - +注意语法细节: +- `let` 与 `in` 之间的赋值语句以 `;` 结尾; +- `in` 之后**只有一个表达式**。注意,这只是语法形式上的要求,并不代表 `let` 绑定的用处很有限,因为表达式本身可以很复杂,常见的是嵌套属性集。作为基本示例,下面演示刚刚学到的列表: ```nix let b = a + 1; @@ -355,17 +379,14 @@ let in [ a b c ] ``` - -输出的值为: - -```nix +求值的结果如下: +``` [ 1 2 3 ] ``` -::: danger 作用域 -**`let` 绑定是有作用域的,绑定的名称只能在作用域使用,或者说每个 `let` 绑定的名 -称只能在该表达式内使用:** +::: info 作用域 +`let` 绑定是有作用域的,绑定的名称只能在作用域使用,或者说每个 `let` 绑定的名称只能在该表达式内使用。例如下面的例子: ```nix { @@ -374,23 +395,16 @@ in } ``` -`x` 未定义: +由于 `b = x;` 不在作用域之内,会有报错如下: -```bash +``` error: undefined variable 'x' - - at «string»:3:7: - - 2| a = let x = 1; in x; - 3| b = x; - | ^ - 4| } ``` ::: -## 属性访问 +### 属性访问 使用 `.` 访问属性: @@ -419,7 +433,9 @@ in a.b.c ``` -## `with` 表达式 +## 语法糖 `with` 和 `inherit` +语法糖(syntactic sugar)是对语言功能没有影响,但更方便使用的一种语法。本节将介绍两种常用的语法糖 `with` 和 `inherit`。 +### `with` 表达式 `with` 表达式可以让你少写几次属性集的名称,是个语法糖: @@ -463,7 +479,7 @@ error: undefined variable 'x' 11| } ``` -## `inherit` 表达式 +### `inherit` 表达式 `inherit` 本意就是继承,我们可以使用它完成一对命名相同的名称和属性之间的赋值: