Skip to content

Commit 50b23e4

Browse files
clstyryan4yin
andauthored
改掉错误使用的赋值、语句、局部变量概念(#50) (#51)
* 改掉错误使用的赋值、语句、局部变量概念 * 改集合为属性集 避免名称变量概念混淆(仅针对其中一句) * 关于“局部变量”的补充说明 Co-authored-by: Ryan Yin <[email protected]>
1 parent 713c9b7 commit 50b23e4

File tree

1 file changed

+57
-37
lines changed

1 file changed

+57
-37
lines changed

src/tutorials/lang/QuickOverview.md

Lines changed: 57 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
::: warning 基础要求
55
以下教程需要你具有一定基础。
66
具体来说,如果你已经知道在编程领域什么是变量(variable)、
7-
赋值(assign)、字符串(string)、函数(function)及参数(argument),
7+
字符串(string)、函数(function)及参数(argument),
88
那么你的知识水平就差不多足够了。
99

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

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

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

62-
回车,得到输出结果如下
62+
回车即可求值,得到结果如下
6363

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

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

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

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

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

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

117117
---
118118

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

122122
```nix
@@ -165,16 +165,16 @@ let a = builtins.div 2 0; b = 3; in b
165165
- 注释:在 Nix 语言中,用 `#` 表示注释,在它之后直到行末的部分都会被忽略。
166166
- 缩进与换行:与 Python 这种对缩进有要求的语言不同,在 Nix 语言中,大多数情况下,换行与缩进只是为了更好的可读性,并不影响代码的本质。
167167

168-
例如,下面有两段示例代码(你目前还不需要理解它们的含义),它们在本质上(也即在 Nix 解释器看来)并没有区别。
168+
例如,下面的两段示例代码(你目前还不需要理解它们的含义),它们在本质上(也即在 Nix 解释器看来)并没有区别。
169169

170170
第一例:
171171
```nix
172-
foo = { a = 1; b = 2; }
172+
{ a = 1; b = 2; }
173173
```
174174

175175
第二例:
176176
```nix
177-
foo = { # 这是一句注释,放在代码末尾。
177+
{ # 这是一句注释,放在代码末尾。
178178
# 这也是一句注释,单独占了一行。
179179
a = 1; # 这里即使不缩进,也不影响代码本质。
180180
b = 2;
@@ -189,21 +189,29 @@ foo = { # 这是一句注释,放在代码末尾。
189189
<!-- prettier-ignore -->
190190
:::
191191

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

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

198199
```nix
199200
foo = 123
200201
```
201202

202203
<!-- prettier-ignore -->
203-
::: info 名称与其它语言中变量的区别
204-
在很多语言中,赋值会改变变量的状态,而 Nix 语言中的名称一旦赋值(定义)就无法改变。
204+
::: info 函数式语言与命令式语言中“变量”的区别
205+
| 维度 | 命令式语言 | Nix(函数式) |
206+
|---|---|---|
207+
| 底层模型 | 存储格(内存单元) | 无存储格,只有「名称-值」映射 |
208+
| 操作 | 赋值:随时把新值写回同一单元 | 绑定:一次性把名称贴到值,不可重写 |
209+
| 所谓“变量” | 存储格的别名 → 之后可反复擦写 | 数学意义上的变量 → 同一作用域内值固定 |
210+
| 文档用词 | variable = 可重写的存储格 | variable = 一次性绑定的名称(不会变) |
211+
212+
一句话:
213+
在 Nix 里,“名称”就是“变量”,只是这个变量一旦绑定便成永恒;它保留了数学“可取不同值”的语义,却丢掉了命令式“可重新赋值”的存储格含义。
205214

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

208216
<!-- prettier-ignore -->
209217
:::
@@ -221,7 +229,7 @@ foo = 123
221229

222230
### 属性集
223231

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

226234
```nix
227235
foo = {
@@ -233,22 +241,22 @@ foo = {
233241
概念说明:
234242

235243
- 属性集(attribute set)就是装载若干对**名称与值**的集合。
236-
- 集合内的名称被称为这个集合的**属性**(attribute);
237-
- 集合内由名称和值组成的对被称为该属性的**元素**(element);
244+
- 属性集内的名称被称为这个属性集的**属性**(attribute);
245+
- 属性集内由名称和值组成的对被称为该属性的**元素**(element);
238246

239247
语法说明:
240248

241-
- 集合以 `{` `}` 为边界,其内部为多个赋值语句,且各个赋值语句末尾必须添加 `;`
249+
- 属性集以 `{` `}` 为边界,其内部为多个“名称-值”对,且它们末尾必须添加 `;`
242250

243-
上述代码将 `foo` 的值定义为集合 `{ a = 1; b = 2; }`因此可称之为集合 `foo`
251+
上述代码将 `foo` 的值定义为属性集 `{ a = 1; b = 2; }`因此可称之为属性集 `foo`
244252

245-
集合 `foo` 中有两个属性:
253+
属性集 `foo` 中有两个属性:
246254

247255
- 属性 `a`,其值为 `1`
248256
- 属性 `b`,其值为 `2`
249257

250-
属性的值除了可以是 `1` `2` 这样的数值外,也可以是一个集合(也即支持嵌套),例如
251-
`b` 的值改为集合 `{ c = 2; d = 3; }`
258+
属性的值除了可以是 `1` `2` 这样的数值外,也可以是一个属性集(也即支持嵌套),例如
259+
`b` 的值改为属性集 `{ c = 2; d = 3; }`
252260

253261
```nix
254262
foo = {
@@ -260,7 +268,7 @@ foo = {
260268
};
261269
```
262270

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

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

293301
```nix
294302
rec {
@@ -303,7 +311,7 @@ rec {
303311
{ a = 1; b = 3; }
304312
```
305313

306-
可以看到,结果中的 `a = 1` 在前面,`b = 3` 在后面。这种顺序实际上与任何其它因素(包括声明顺序、求值依赖关系)都无关,而只与**属性名称本身的排序**有关。例如,对 `rec { a = 1; b = 2; }``rec { b = 2; a = 1; }` 的求值,都会把 `a = 1` 放在前面,归因到底,这只是因为 `a` 在字母表中位于 `b` 之前罢了。(直接原因则与 Nix 解释器对名称排序所用到的算法或者调用的库有关,这里不再深入。)
314+
可以看到,结果中的 `a = 1` 在前面,`b = 3` 在后面。这种顺序实际上与任何其它因素(包括声明顺序、求值依赖关系)都无关,而只与**属性名称本身的排序**有关。例如,对 `rec { a = 1; b = 2; }``rec { b = 2; a = 1; }` 的求值,都会把 `a = 1` 放在前面,归因到底,只是 `a` 在字母表中位于 `b` 之前罢了。(直接原因则与 Nix 解释器对名称排序所用到的算法或者调用的库有关,这里不再深入。)
307315

308316
既然如此,将上面属性集里的两个元素位置对调:
309317
```nix
@@ -352,13 +360,13 @@ fruits = {
352360
fruits = [ "apple" "orange" "banana" ]
353361
```
354362
需要注意语法细节:
355-
- 列表以 `[` `]` 为边界,其内部为多个元素,每个元素都是值(value)而不是赋值语句
363+
- 列表以 `[` `]` 为边界,其内部为多个元素,每个元素都是值(value)。
356364
- 元素之间使用空格(或换行)分隔,各元素****`;` 结尾。
357365

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

363371
```nix
364372
let
@@ -369,7 +377,7 @@ in
369377
```
370378

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

415+
<!-- prettier-ignore -->
416+
::: note 局部变量(?)
417+
Nix 中不存在“全局变量”,因而“局部变量”的说法可能引起误会,应当尽量避免使用。
418+
419+
不过,[Nix manual](https://nix.dev/manual/nix/2.31/language/syntax.html) 中对 let 绑定的介绍提到了局部变量(local variable)。
420+
> A let-expression allows you to define local variables for an expression.
421+
422+
这种说法可能不合适,但既然官方文档也有用到,其他地方自然也可能会出现,留心即可。
423+
424+
<!-- prettier-ignore -->
425+
:::
426+
407427
### 属性访问
408428

409429
使用 `.` 访问属性:
@@ -424,7 +444,7 @@ in
424444
attrset.a.b.c
425445
```
426446

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

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

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

442462
```nix
443463
let
@@ -481,7 +501,7 @@ error: undefined variable 'x'
481501

482502
### `inherit` 表达式
483503

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

486506
```nix
487507
let
@@ -740,7 +760,7 @@ x: y: x + y
740760
{ a, b }: a + b
741761
```
742762

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

745765
```nix
746766
{ a, b ? 0 }: a + b
@@ -836,7 +856,7 @@ in
836856
let g = f 1; in g 2
837857
```
838858

839-
也可以一次性赋值
859+
也可以一次性接收两个参数
840860

841861
```nix
842862
let
@@ -895,7 +915,7 @@ in
895915
f { a = 1; }
896916
```
897917

898-
赋值是可选的,根据你的需要来:
918+
传入参数值是可选的,根据你的需要来:
899919

900920
```nix
901921
let
@@ -925,7 +945,7 @@ in
925945
## 命名参数集
926946

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

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

0 commit comments

Comments
 (0)