Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 57 additions & 37 deletions src/tutorials/lang/QuickOverview.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
::: warning 基础要求
以下教程需要你具有一定基础。
具体来说,如果你已经知道在编程领域什么是变量(variable)、
赋值(assign)、字符串(string)、函数(function)及参数(argument),
字符串(string)、函数(function)及参数(argument),
那么你的知识水平就差不多足够了。

<!-- prettier-ignore -->
Expand All @@ -27,7 +27,7 @@ Nix 作为语言,是一门简单的函数式语言,它被专门设计并用
::: warning
本节需要你已经安装了 Nix 或正在使用 NixOS。

另外,本教程中的示例代码一般不是为了供直接运行而写的。对于每一段代码,若想实践其
另外,本教程中的示例代码并不全是为了供直接运行而写的。对于每一段代码,若想实践其
效果,请先理解对应的知识,再基于这段代码自己编写测试代码以运行。

(对于 Nix 来说,运行代码被称为**求值**(evaluate),而只有**表达
Expand Down Expand Up @@ -59,7 +59,7 @@ nix-repl>
1 + 2
```

回车,得到输出结果如下
回车即可求值,得到结果如下

```plain
3
Expand All @@ -77,7 +77,7 @@ nix-repl>
交互模式简单快捷,但我们平时使用 Nix 语言进行编辑配置、打包等操作时,大多数情况
下不会直接使用交互模式,而是对 `*.nix` 纯文本文件进行编辑。

因此,如果你习惯于使用编辑器,这里更推荐利用文件求值进行实践。
因此,如果你习惯于使用编辑器,这里更推荐利用文件求值进行实践。每个 nix 文件的内容都是**一个**表达式,这是 nix 文件能被求值的前提。

例如,新建文件 `foo.nix`,将其内容编辑如下:

Expand Down Expand Up @@ -105,7 +105,7 @@ nix-instantiate --eval foo.nix
<details><summary>单击以切换折叠/展开</summary>

Nix 的求值具有惰性(laziness),只会在有必要时进行。例如,下述代码(看不懂没关
系) `a` 的值赋为 $\dfrac{2}{0}$ ,这是一种典型的数学错误:
系)将名称 `a` 分配给值 $\dfrac{2}{0}$ ,这是一种典型的数学错误:

```nix
let a = builtins.div 2 0; b = 3; in b
Expand All @@ -116,7 +116,7 @@ let a = builtins.div 2 0; b = 3; in b

---

与惰性类似的是另一种行为是,嵌套集合的求值,在交互模式和文件求值模式下,除非必
与惰性类似的是另一种行为是,嵌套属性集的求值,在交互模式和文件求值模式下,除非必
要,默认不会迭代,而是以占位符替代,例如

```nix
Expand Down Expand Up @@ -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;
Expand All @@ -189,21 +189,29 @@ foo = { # 这是一句注释,放在代码末尾。
<!-- prettier-ignore -->
:::

## 赋值与集合
与大多数编程语言类似,变量与值是 Nix 语言中最基础的概念。本节将会介绍 Nix 中如何为变量**赋值**,以及最常用的数据类型——**属性集**,继而引出**递归属性集**与**列表**的概念。
## 名称与属性集
变量是大多数编程语言中最基础的概念,而与之类似的名称则是 Nix 语言中最基础的概念。本节将会介绍 Nix 中如何将名称**分配给值**,以及最常用的数据类型——**属性集**,继而引出**递归属性集**与**列表**的概念。

### 名称和值

我们可以使用 `=` 为名称绑定值,形成赋值语句。例如将名称 `foo` 赋值为 `123`:
我们可以使用 `=` 将名称分配给值,形成“名称 - 值”对。例如将名称 `foo` 分配给值 `123`:

```nix
foo = 123
```

<!-- prettier-ignore -->
::: info 名称与其它语言中变量的区别
在很多语言中,赋值会改变变量的状态,而 Nix 语言中的名称一旦赋值(定义)就无法改变。
::: info 函数式语言与命令式语言中“变量”的区别
| 维度 | 命令式语言 | Nix(函数式) |
|---|---|---|
| 底层模型 | 存储格(内存单元) | 无存储格,只有「名称-值」映射 |
| 操作 | 赋值:随时把新值写回同一单元 | 绑定:一次性把名称贴到值,不可重写 |
| 所谓“变量” | 存储格的别名 → 之后可反复擦写 | 数学意义上的变量 → 同一作用域内值固定 |
| 文档用词 | variable = 可重写的存储格 | variable = 一次性绑定的名称(不会变) |

一句话:
在 Nix 里,“名称”就是“变量”,只是这个变量一旦绑定便成永恒;它保留了数学“可取不同值”的语义,却丢掉了命令式“可重新赋值”的存储格含义。

不过,将 Nix 中的名称称为变量也没问题。

<!-- prettier-ignore -->
:::
Expand All @@ -221,7 +229,7 @@ foo = 123

### 属性集

在 Nix 语法中,属性集(简称集合)是最常见的数据类型之一,基本示例如下:
在 Nix 语法中,属性集是最常见的数据类型之一,基本示例如下:

```nix
foo = {
Expand All @@ -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 = {
Expand All @@ -260,7 +268,7 @@ foo = {
};
```

嵌套集合中的属性也可以利用 `.` 表示,例如上面这段的一种等价写法如下:
嵌套属性集中的属性也可以利用 `.` 表示,例如上面这段的一种等价写法如下:

```nix
foo.a = 1;
Expand Down Expand Up @@ -288,7 +296,7 @@ foo.b.d = 3;
```
error: undefined variable 'a'
```
可见,当属性集内的属性 `b` 需要访问该集合的另一个属性 `a` 时,即使 `a` 是“先”定义的,也无法访问到。此时就需要我们改用递归(recursive)属性集,它相比普通的属性集,在前面多加了 `rec `:
可见,当属性集内的属性 `b` 需要访问该属性集的另一个属性 `a` 时,即使 `a` 是“先”定义的,也无法访问到。此时就需要我们改用递归(recursive)属性集,它相比普通的属性集,在前面多加了 `rec `:

```nix
rec {
Expand All @@ -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
Expand Down Expand Up @@ -352,13 +360,13 @@ fruits = {
fruits = [ "apple" "orange" "banana" ]
```
需要注意语法细节:
- 列表以 `[` `]` 为边界,其内部为多个元素,每个元素都是值(value)而不是赋值语句
- 列表以 `[` `]` 为边界,其内部为多个元素,每个元素都是值(value)。
- 元素之间使用空格(或换行)分隔,各元素**不**以 `;` 结尾。

## let 绑定与属性访问
前面关于变量的赋值与使用是非常基本的,我们还需要更灵活的处理方法。本节将会介绍用于定义局部变量的 **let 绑定**,以及风格简洁的**属性访问**。
前面关于名称的使用是非常基本的,我们还需要更灵活的处理方法。本节将会介绍另一种将名称分配给值的方法——**let 绑定**,以及风格简洁的**属性访问**。
### `let` 绑定
有时我们希望定义一个变量,使其不影响全局,仅在局部生效。此时就可以使用 `let` 绑定,示例如下:
有时我们希望在指定的范围内为值分配名称,此时就可以使用 `let` 绑定,示例如下:

```nix
let
Expand All @@ -369,7 +377,7 @@ in
```

注意语法细节:
- `let` 与 `in` 之间的赋值语句以 `;` 结尾;
- `let` 与 `in` 之间的“名称-值”对以 `;` 结尾;
- `in` 之后**只有一个表达式**。注意,这只是语法形式上的要求,并不代表 `let` 绑定的用处很有限,因为表达式本身可以很复杂,常见的是嵌套属性集。作为基本示例,下面演示刚刚学到的列表:
```nix
let
Expand Down Expand Up @@ -404,6 +412,18 @@ error: undefined variable 'x'
<!-- prettier-ignore -->
:::

<!-- prettier-ignore -->
::: 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.

这种说法可能不合适,但既然官方文档也有用到,其他地方自然也可能会出现,留心即可。

<!-- prettier-ignore -->
:::

### 属性访问

使用 `.` 访问属性:
Expand All @@ -424,7 +444,7 @@ in
attrset.a.b.c
```

当然,就像如何访问属性一样,也可以用 `.` 直接赋值它
当然,就像如何访问属性一样,也可以用 `.` 为值分配名称

```nix
let
Expand All @@ -437,7 +457,7 @@ in
语法糖(syntactic sugar)是对语言功能没有影响,但更方便使用的一种语法。本节将介绍两种常用的语法糖 `with` 和 `inherit`。
### `with` 表达式

`with` 表达式可以让你少写几次属性集的名称,是个语法糖
`with` 表达式可以让你少写几次属性集的名称:

```nix
let
Expand Down Expand Up @@ -481,7 +501,7 @@ error: undefined variable 'x'

### `inherit` 表达式

`inherit` 本意就是继承,我们可以使用它完成一对命名相同的名称和属性之间的赋值
`inherit` 本意就是继承,我们可以使用它让属性继承名称的值

```nix
let
Expand Down Expand Up @@ -740,7 +760,7 @@ x: y: x + y
{ a, b }: a + b
```

为函数指定默认参数,在缺省该参数赋值的情况下,它就是默认值:
为函数指定默认参数,在缺少该参数的值的情况下,它就是默认值:

```nix
{ a, b ? 0 }: a + b
Expand Down Expand Up @@ -836,7 +856,7 @@ in
let g = f 1; in g 2
```

也可以一次性赋值
也可以一次性接收两个参数

```nix
let
Expand Down Expand Up @@ -895,7 +915,7 @@ in
f { a = 1; }
```

赋值是可选的,根据你的需要来:
传入参数值是可选的,根据你的需要来:

```nix
let
Expand Down Expand Up @@ -925,7 +945,7 @@ in
## 命名参数集

又名 "`@` 模式"。在上文中,我们已经可以接收到额外的参数了,假如我们需要使用某
个额外参数,我们可以使用命名属性集将其接收到一个另外的集合
个额外参数,我们可以使用命名属性集将其接收到一个另外的属性集

```nix
{a, b, ...}@args: a + b + args.c # 这样声明函数
Expand Down