-
Notifications
You must be signed in to change notification settings - Fork 20
大修 Nix 语言快速入门(第三部分) #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,10 @@ | |
| <!-- prettier-ignore --> | ||
| ::: | ||
|
|
||
| <details><summary>仅面向本文维护者的说明,单击以切换折叠/展开</summary> | ||
| 本文在设计上是线性的,也即只需要读者具备一点点基础,就可以通过按顺序从头读到尾的方式完成本文的学习。因此,请留心说明顺序,例如讲 let 绑定时如果举了一个列表的例子,你需要确保前面已经正式介绍过列表。再如,讲 with 语法糖的时候同时用到 let 绑定和列表,那么这两个概念都需要在前面已经正式介绍过。否则,读者很可能会面对初次接触的语法或者概念而被卡住,这会严重影响学习效率甚至是完成率。若出于顺序安排的其他合理性原因,实在无法避开在说明中涉及陌生概念,可以提示读者相关部分不需要理解,后面会讲到。 | ||
| </details> | ||
|
|
||
| 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; # 这里的代码被注释掉了,相当于不存在。 | ||
| } | ||
| ``` | ||
|
|
||
| <!-- prettier-ignore --> | ||
| ::: warning | ||
| 换行与空格一样具有分隔作用,请勿在不可分隔的地方胡乱断行。 | ||
|
|
||
| <!-- prettier-ignore --> | ||
| ::: | ||
|
|
||
| ## 赋值与集合 | ||
| 与大多数编程语言类似,变量与值是 Nix 语言中最基础的概念。本节将会介绍 Nix 中如何为变量**赋值**,以及最常用的数据类型——**属性集**,继而引出**递归属性集**与**列表**的概念。 | ||
| ### 名称和值 | ||
|
|
||
| 我们可以使用 `=` 为名称绑定值,形成赋值语句。例如将名称 `foo` 赋值为 `123`: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
attribute set 字面量语法的一部分。这里的 {
a = 1;
}
{
"a" = 2;
}
{
"${someVariable}" = 3;
}在 let 中表示一个绑定。这里 a 才是一个变量名字,可能可以和赋值语句类比,那样还有一些道理。但我认为应该停留在类比的层面上,不能让读者误以为这是一种赋值。 let a = 1; in ...当然,确实可以说 let 绑定是一种不可变的赋值,毕竟表现上是一样的,那可能也行吧。
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 更重要的问题是,nix 里只有表达式,没有“语句”这一概念。 |
||
|
|
||
|
|
@@ -164,9 +200,10 @@ foo = 123 | |
| ``` | ||
|
|
||
| <!-- prettier-ignore --> | ||
| ::: info 名称与变量的区别 | ||
| 名称不等同常见编程语言中的变量。 | ||
| 传统的赋值会改变变量的状态,而 Nix 语言中的名称一旦赋值(定义)就无法改变。 | ||
| ::: info 名称与其它语言中变量的区别 | ||
| 在很多语言中,赋值会改变变量的状态,而 Nix 语言中的名称一旦赋值(定义)就无法改变。 | ||
|
|
||
| 不过,将 Nix 中的名称称为变量也没问题。 | ||
|
|
||
| <!-- prettier-ignore --> | ||
| ::: | ||
|
|
@@ -182,7 +219,7 @@ foo = 123 | |
| - 列表(list),例如 `[ 1 "tux" false ]` | ||
| - 属性集(attribute set),例如 `{ a = 1; b = "tux"; c = false; }` | ||
|
|
||
| ## 属性集 | ||
| ### 属性集 | ||
|
|
||
| 在 Nix 语法中,属性集(简称集合)是最常见的数据类型之一,基本示例如下: | ||
|
|
||
|
|
@@ -203,26 +240,6 @@ foo = { | |
|
|
||
| - 集合以 `{` `}` 为边界,其内部为多个赋值语句,且各个赋值语句末尾必须添加 `;` 。 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 因为 nix 里并没有赋值语句这种东西,原文其实就有大问题了。 |
||
|
|
||
| <!-- prettier-ignore --> | ||
| ::: info 换行与缩进 | ||
| 对于 Nix 语言,大多数情况下,换行与缩进只是为了更好的可读性。 | ||
|
|
||
| 例如,上述代码与下面的代码在本质上并没有区别: | ||
|
|
||
| ```nix | ||
| foo = { a = 1; b = 2; }; | ||
| ``` | ||
|
|
||
| <!-- prettier-ignore --> | ||
| ::: | ||
|
|
||
| <!-- prettier-ignore --> | ||
| ::: warning | ||
| 换行与空格一样具有分隔作用,请勿在不可分隔的地方胡乱断行。 | ||
|
|
||
| <!-- prettier-ignore --> | ||
| ::: | ||
|
|
||
| 上述代码将 `foo` 的值定义为集合 `{ a = 1; b = 2; }` ,因此可称之为集合 `foo` 。 | ||
|
|
||
| 集合 `foo` 中有两个属性: | ||
|
|
@@ -258,7 +275,7 @@ foo.b.d = 3; | |
| <!-- prettier-ignore --> | ||
| ::: | ||
|
|
||
| ## 递归属性集 | ||
| ### 递归属性集 | ||
|
|
||
| 普通的属性集不支持递归引用,举个例子: | ||
| ```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 绑定**,以及风格简洁的**属性访问**。 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 因为 nix 都没有语句的概念,也没有“全局变量”的概念,因此也谈不上和“全局变量”对应的“局部变量”的概念。 |
||
| ### `let` 绑定 | ||
| 有时我们希望定义一个变量,使其不影响全局,仅在局部生效。此时就可以使用 `let` 绑定,示例如下: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 还是那个问题,nix 就没有全局变量,除非使用 |
||
|
|
||
| ```nix | ||
| let | ||
| b = a + 1; | ||
| a = 1; | ||
| b = 2; | ||
| in | ||
| a + b | ||
| a + b # 结果是 2 | ||
| ``` | ||
|
|
||
| 引用到 `a` 的地方有两处,它们都会将 `a` "替换"成值来计算或赋值,类似于常量。 | ||
|
|
||
| <!-- prettier-ignore --> | ||
| ::: tip | ||
| 你不需要关心名称的声明顺序,不会出现名称未定义的情况。 | ||
|
|
||
| <!-- prettier-ignore --> | ||
| ::: | ||
|
|
||
| **`in` 后面只能跟随一个表达式,并且 `let` 绑定的名称只在该表达式是有效的的**,这 | ||
| 里演示一个列表: | ||
|
|
||
| 注意语法细节: | ||
| - `let` 与 `in` 之间的赋值语句以 `;` 结尾; | ||
| - `in` 之后**只有一个表达式**。注意,这只是语法形式上的要求,并不代表 `let` 绑定的用处很有限,因为表达式本身可以很复杂,常见的是嵌套属性集。作为基本示例,下面演示刚刚学到的列表: | ||
| ```nix | ||
| let | ||
| b = a + 1; | ||
|
|
@@ -355,17 +379,14 @@ let | |
| in | ||
| [ a b c ] | ||
| ``` | ||
|
|
||
| 输出的值为: | ||
|
|
||
| ```nix | ||
| 求值的结果如下: | ||
| ``` | ||
| [ 1 2 3 ] | ||
| ``` | ||
|
|
||
| <!-- prettier-ignore --> | ||
| ::: 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| } | ||
| ``` | ||
|
|
||
| <!-- prettier-ignore --> | ||
| ::: | ||
|
|
||
| ## 属性访问 | ||
| ### 属性访问 | ||
|
|
||
| 使用 `.` 访问属性: | ||
|
|
||
|
|
@@ -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` 本意就是继承,我们可以使用它完成一对命名相同的名称和属性之间的赋值: | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nix 里并没有赋值这一概念,也没有赋值语句。