From 45f957430a9ff42cccba8fae271ff4dae0ad91d4 Mon Sep 17 00:00:00 2001 From: clsty Date: Sun, 19 Oct 2025 07:07:19 +0800 Subject: [PATCH 01/30] Update function --- src/tutorials/lang/QuickOverview.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index 20e7bb49..d93490bf 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1022,7 +1022,12 @@ in ## 函数 -函数在 Nix 语言中是人上人,我们先来声明一个匿名函数(Lambda): +作为函数式编程(functional programming)的语言, +Nix 中的函数地位非常重要。 + +### 函数的基本构成 +函数由参数和函数体组合而成,它们之间由 `:` 分隔。 +- 每个函数在形式上仅能接受一个参数,但这个参数可以是属性集等其它数据类型,所以实际可以承载更多信息。 ```nix x: x + 1 @@ -1030,12 +1035,6 @@ x: x + 1 冒号左边是函数参数,冒号右边跟随一个空格,随即是函数体。 -这是个嵌套的函数,支持多重参数(柯里化函数): - -```nix -x: y: x + y -``` - 参数当然可以是属性集类型: ```nix @@ -1090,7 +1089,7 @@ Hello NixOS (x: x + 1) 1 # 向该 Lambda 函数传入参数 1 ``` -## 柯里化函数 +### 柯里化函数 我们将 `f (a,b,c)` 转换为 `f (a)(b)(c)` 的过程就是柯里化。为什么需要柯里化?因为 它很灵活,可以避免重复传入参数,当你传入第一个参数的时候,该函数就已经具有了第一 @@ -1147,7 +1146,7 @@ in f 1 2 ``` -## 属性集参数 +### 属性集参数 当我们被要求必须传入多个参数时,使用这种函数声明方法: @@ -1186,7 +1185,7 @@ error: 'f' at (string):2:7 called with unexpected argument 'c' 5| ``` -## 默认参数 +### 默认参数 前面稍微提到过一点,没有什么需要过多讲解的地方: @@ -1206,7 +1205,7 @@ in f { a = 1; b = 2; } ``` -## 额外参数 +### 额外参数 有的时候,我们设计的函数不得不接收一些我们不需要的额外参数,我们可以使用 `...` 允许接收额外参数: @@ -1224,7 +1223,7 @@ in f { a = 1; b = 2; c = 3; } ``` -## 命名参数集 +### 命名参数集 又名 "`@` 模式"。在上文中,我们已经可以接收到额外的参数了,假如我们需要使用某 个额外参数,我们可以使用命名属性集将其接收到一个另外的属性集: From 951a4676765956422a514ccdbb7782dc21b7015b Mon Sep 17 00:00:00 2001 From: clsty Date: Wed, 22 Oct 2025 08:31:45 +0800 Subject: [PATCH 02/30] Update QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index d93490bf..9b9e686d 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1023,24 +1023,44 @@ in ## 函数 作为函数式编程(functional programming)的语言, -Nix 中的函数地位非常重要。 +Nix 中函数的地位非常重要。 ### 函数的基本构成 函数由参数和函数体组合而成,它们之间由 `:` 分隔。 -- 每个函数在形式上仅能接受一个参数,但这个参数可以是属性集等其它数据类型,所以实际可以承载更多信息。 + +例如,对于数学上的函数 $f(x)=x+1$ ,用 Nix 的函数表达如下: ```nix x: x + 1 ``` -冒号左边是函数参数,冒号右边跟随一个空格,随即是函数体。 +在此例中,左边的 `x` 是参数,右边的 `x+1` 是函数体。 -参数当然可以是属性集类型: +每个函数在形式上仅能接受一个参数,但这个参数还可以是属性集,例如 ```nix { a, b }: a + b ``` + +::: tip +你应该注意到了,这里的属性集和前面介绍的并不相同。 + +在函数中作为参数出现的属性集,只包含属性名称,并且用 `,` 分隔。 + +作为对比,下面是一个标准的属性集的示例: +```nix +{ a = 1; b = 2; } +``` + +再来一个列表的示例: +```nix +[ a b ] +``` + + +::: + 为函数指定默认参数,在缺少该参数的值的情况下,它就是默认值: ```nix From fdad0b7641148c62d904ddc7980ab02d62359d9c Mon Sep 17 00:00:00 2001 From: clsty Date: Wed, 22 Oct 2025 09:05:17 +0800 Subject: [PATCH 03/30] Callout blocks improve --- src/tutorials/lang/QuickOverview.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index 9b9e686d..fe6dcc45 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -98,7 +98,7 @@ nix-instantiate --eval foo.nix ``` -::: info 拓展说明:求值的惰性与嵌套迭代 +::: note 拓展说明:求值的惰性与嵌套迭代 此部分内容较长,仅供有兴趣的人阅读。 @@ -201,7 +201,7 @@ foo = 123 ``` -::: info +::: tip 如何测试此示例 上面的示例不属于表达式(但可以作为表达式的一部分),所以你无法将它直接写入 nix 文件进行文件求值;不过 `nix repl` 有一些灵活的处理,允许你输入这样的结构。 @@ -319,14 +319,14 @@ rec { ``` -::: note 求值结果的排序依据 +::: info 求值结果的排序依据 可以看到,结果中的 `a = 1` 在前面,`b = 3` 在后面。这种顺序实际上与任何其它因素(包括声明顺序、求值依赖关系)都无关,而只与**属性名称本身的排序**有关。例如,对 `rec { a = 1; b = 2; }` 与 `rec { b = 2; a = 1; }` 的求值,都会把 `a = 1` 放在前面,归因到底,只是 `a` 在字母表中位于 `b` 之前罢了。(直接原因则与 Nix 解释器对名称排序所用到的算法或者调用的库有关,这里不再深入。) ::: -::: info 求值过程的顺序机制 +::: note 求值过程的顺序机制 既然求值结果的排序与求值顺序等因素无关,那么求值顺序由什么决定呢? 将刚才例子中属性集里的两个元素位置对调: @@ -514,7 +514,7 @@ in ``` -::: note 就近性 +::: info 就近性 不过,这种等价并不是恒定的, 比如下面的例子, 我们在 `let` 后面直接加一行 `x = 0;`: @@ -1044,9 +1044,9 @@ x: x + 1 ::: tip -你应该注意到了,这里的属性集和前面介绍的并不相同。 +注意语法细节,在函数中作为参数出现的属性集,只包含属性名称,并且用 `,` 分隔。 -在函数中作为参数出现的属性集,只包含属性名称,并且用 `,` 分隔。 +这与之前介绍的属性集和列表都不同。 作为对比,下面是一个标准的属性集的示例: ```nix From aec7f534b57c2c746ba6126fb74eff09c5892cbd Mon Sep 17 00:00:00 2001 From: clsty Date: Wed, 22 Oct 2025 11:30:29 +0800 Subject: [PATCH 04/30] Update QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 223 +++++++++++++++++++++++++--- 1 file changed, 202 insertions(+), 21 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index fe6dcc45..4b7eb15d 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1036,15 +1036,75 @@ x: x + 1 在此例中,左边的 `x` 是参数,右边的 `x+1` 是函数体。 -每个函数在形式上仅能接受一个参数,但这个参数还可以是属性集,例如 + +::: info 匿名函数 +机智的你可能会发现, +此示例实现的 $f(x)=x+1$ 并不完整—— + +毕竟,$f(x)=x+1$ 的函数名 $f$ 去哪里了? + +确实,上面的例子所写的是**匿名函数**,即没有和名称绑定。 +我们对它进行求值,结果如下: +```plain + +``` + +这里的 LAMBDA(即希腊字母 λ)就是匿名函数的代表符号。 +至于为什么 λ 被用来代表匿名函数, +请自行搜索“lambda 演算”以及“函数式编程”, +这里不再展开。 + + +::: + + +:::tip 直接调用匿名函数 +利用 `(` `)` 将函数包裹起来,就可以直接调用匿名函数,例如 ```nix -{ a, b }: a + b +(x: x + 1) 2 ``` +求值结果为 `3`。 -::: tip -注意语法细节,在函数中作为参数出现的属性集,只包含属性名称,并且用 `,` 分隔。 +::: + +函数本身作为数据类型,是可以与名称绑定的。 + +在前面例子的基础上,我们将函数绑定到名称 `f`,并且将 2 作为其参数来调用: + +```nix +let + f = x: x + 1; +in + f 2 +``` + +求值,结果如下: +```plain +3 +``` +这相当于定义了函数 $f(x)=x+1$ 之后求值 $f(2)$,结果为 3。 + +### 用属性集参数实现多元函数 +在前面的例子中,我们只实现了一个非常简单的一元函数 $f(x)=x+1$。 + +那么对于多元函数,比如 $f(x,y)=3x+\frac{y}{2}$,在 Nix 中应该怎么实现呢? +- 坏消息是,根据 Nix 语法规范,每个函数在形式上**有且仅有一个参数**。 +- 好消息是,这个参数可以是属性集,并且在函数体中可以将属性集中的**各个属性单独拿出来使用**。 + +例如 + +```nix +{ x, y }: ( 3 * x ) + ( y / 2 ) +``` + +上面的函数虽然仅接受一个参数(属性集 `{ x, y }`), +实际功能却相当于数学上的二元函数 $f(x,y)=3x+\frac{y}{2}$。 + + +::: tip 属性集的语法细节 +在**函数定义**中作为参数出现的属性集,只包含属性名称,并且用 `,` 分隔。 这与之前介绍的属性集和列表都不同。 @@ -1061,33 +1121,160 @@ x: x + 1 ::: -为函数指定默认参数,在缺少该参数的值的情况下,它就是默认值: +我们为前面的例子绑定名称 `f` 并且以参数 `{ x = 1; y = 4; }` 来调用它: ```nix -{ a, b ? 0 }: a + b +let + f = { x, y }: ( 3 * x ) + ( y / 2 ); +in + f { x = 1; y = 4; } ``` +求值,结果如下: +```plain +5 +``` +这相当于定义了函数 $f(x,y)=3x+\frac{y}{2}$ 之后求值 $f(1,4)$,结果为 5。 -允许传入额外的属性: +作为编程语言,Nix 的函数当然也能处理其它数据类型。 +例如,定义一个函数 `concat3` 并调用它来拼接 `"Hello"` `" "` 和 `"world"`: +```nix +let + concat3 = { a, b, c }: a + b + c; +in + concat3 { a = "Hello"; b = " "; c = "world"; } +``` +函数的结果自然也可以嵌套使用。 +例如,定义一个函数 `concat2` 并多次调用它来拼接 `"Hello"` `" "` 和 `"world"`: ```nix -{ a, b, ...}: a + b # 明确传入的属性有 a 和 b,传入额外的属性将被忽略 -{ a, b, ...}: a + b + c # 即使传入的属性有 c,一样不会参与计算,这里会报错 +let + concat2 = { a, b }: a + b; +in + concat2 { + a = concat2 { a = "Hello"; b = " "; }; + b = "world"; + } ``` +求值结果仍然为 `"Hello world"`。 -为额外的参数绑定到参数集,然后调用: +由于 `concat2` 接受的属性集仅含两个属性, +此例先拼接了 `"Hello"` 和 `" "`,再将此结果与 `"world"` 拼接。 +求值结果如下: +```plain +"Hello world" +``` + +### 属性集参数的要求 +函数被调用时所接受的属性集, +必须符合**定义中作为参数的属性集的要求**, +否则就会报错。 + +例如前面的 `concat2` 函数,我们多给一个 `c` 的值: ```nix -args@{ a, b, ... }: a + b + args.c -{ a, b, ... }@args: a + b + args.c # 也可以是这样 +let + concat2 = { a, b }: a + b; +in + concat2 { a = "Hello"; b = "world"; c = "!"; } +``` +求值,报错: +```plain +error: function 'concat2' called with unexpected argument 'c' +``` +或者,这次我们只给出 `b` 的值: +```nix +let + concat2 = { a, b }: a + b; +in + concat2 { b = "world"; } +``` +求值,报错: +```plain +error: function 'concat2' called without required argument 'a' ``` -为函数命名: +但是,前述要求可以设置得更加灵活。 + +#### 默认值 +在属性后面加 `? ` ,可以设定默认值。 +例如定义一个“问候”函数 `greet`, +- 它的功能是对 `object` 进行“问候”; +- 支持设定问候语 `greeting`,默认值为 `Hello `。 + - 设置方法是,在函数定义中参数里的 `greeting` 后面附加 `? "Hello, "`。 + +测试例: ```nix let - f = x: x + 1; + greet = { greeting ? "Hello, ", object }: lang + object; in - f + { + # 对 world 进行问候 + R1 = greet { object = "world"; } ; + # 对 my friend 进行问候 + R2 = greet { object = "my friend"; } ; + # 自定义问候语,对 my friend 进行问候 + R3 = greet { greeting = "Welcome, "; object = "my friend"; } ; + } +``` +严格求值,结果如下: +```plain +{ + R1 = "Hello, world"; + R2 = "Hello, my friend"; + R3 = "Welcome, my friend"; +} +``` + +#### 额外属性 +在属性中添加一个占位符 `...`,这允许了传入额外属性。 + +例如: +```nix +let + concat2 = { a, b, ... }: a + b; +in +{ + R1 = concat2 { a = "Hello "; b = "world"; }; + # 传入额外的 c 不会引发报错 + R2 = concat2 { a = "Hello "; b = "world"; c = "!"; }; +} +``` +严格求值结果如下: +```plain +{ + R1 = "Hello world"; + R2 = "Hello world"; +} +``` +注意这里的 `R1` 和 `R2` 的值相同, +因为 `c` 作为额外属性,没有参与计算。 + +当然,定义的函数体中也并没有 `c`, +但若直接更改函数体,将会报错。 +例如: +```nix +let + # 参数中没有 c,但函数体里有 c + concat2 = { a, b, ... }: a + b + c; +in +{ + R1 = concat2 { a = "Hello "; b = "world"; }; + R2 = concat2 { a = "Hello "; b = "world"; c = "!"; }; +} +``` +求值,报错(注意这个报错发生在 `in` **之前**): +```plain +error: undefined variable 'c' +``` + +#### 其他 + +为额外的参数绑定到参数集,然后调用: + +```nix +args@{ a, b, ... }: a + b + args.c +{ a, b, ... }@args: a + b + args.c # 也可以是这样 ``` 调用函数,并使用函数构建新属性集: @@ -1103,12 +1290,6 @@ concat { a = "Hello "; b = "NixOS"; } Hello NixOS ``` -由于函数与参数使用空格分隔,所以我们可以使用括号将函数体与参数分开: - -```nix -(x: x + 1) 1 # 向该 Lambda 函数传入参数 1 -``` - ### 柯里化函数 我们将 `f (a,b,c)` 转换为 `f (a)(b)(c)` 的过程就是柯里化。为什么需要柯里化?因为 From b881b6cfc05dac409c628fb42bc5ca1cf7dbf151 Mon Sep 17 00:00:00 2001 From: clsty Date: Wed, 22 Oct 2025 11:34:38 +0800 Subject: [PATCH 05/30] Trigger workflow --- src/tutorials/lang/QuickOverview.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index 4b7eb15d..b756cc76 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1485,3 +1485,4 @@ $ echo "x: x + 1" > file.nix import ./file.nix 1 2 ``` + From 36c6141774c9360379a47719c679b0ad14b1fa15 Mon Sep 17 00:00:00 2001 From: clsty Date: Wed, 22 Oct 2025 15:20:01 +0800 Subject: [PATCH 06/30] Update QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 254 ++++++++++++---------------- 1 file changed, 111 insertions(+), 143 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index b756cc76..3933b5f7 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -326,7 +326,7 @@ rec { ::: -::: note 求值过程的顺序机制 +::: note 拓展说明:求值过程的顺序机制 既然求值结果的排序与求值顺序等因素无关,那么求值顺序由什么决定呢? 将刚才例子中属性集里的两个元素位置对调: @@ -432,7 +432,7 @@ error: undefined variable 'x' ::: -::: note 局部变量(?) +::: note 拓展说明:局部变量(?) Nix 中不存在“全局变量”,因而“局部变量”的说法可能引起误会,应当尽量避免使用。 不过,[Nix manual](https://nix.dev/manual/nix/2.31/language/syntax.html) 中对 let 绑定的介绍提到了局部变量(local variable)。 @@ -940,7 +940,7 @@ to get distro info. ``` -::: note 智能去除缩进 +::: info 智能去除缩进 Nix 的多行字符串会统一去除开头的缩进, 这在其他语言中是不常见的。 @@ -1086,7 +1086,7 @@ in ``` 这相当于定义了函数 $f(x)=x+1$ 之后求值 $f(2)$,结果为 3。 -### 用属性集参数实现多元函数 +### 作为参数的属性集:基本形式 在前面的例子中,我们只实现了一个非常简单的一元函数 $f(x)=x+1$。 那么对于多元函数,比如 $f(x,y)=3x+\frac{y}{2}$,在 Nix 中应该怎么实现呢? @@ -1135,7 +1135,10 @@ in ``` 这相当于定义了函数 $f(x,y)=3x+\frac{y}{2}$ 之后求值 $f(1,4)$,结果为 5。 -作为编程语言,Nix 的函数当然也能处理其它数据类型。 + +:::tip 更多示例 +Nix 的函数也能处理其它数据类型。 + 例如,定义一个函数 `concat3` 并调用它来拼接 `"Hello"` `" "` 和 `"world"`: ```nix let @@ -1143,9 +1146,13 @@ let in concat3 { a = "Hello"; b = " "; c = "world"; } ``` +求值结果如下: +```plain +"Hello world" +``` -函数的结果自然也可以嵌套使用。 -例如,定义一个函数 `concat2` 并多次调用它来拼接 `"Hello"` `" "` 和 `"world"`: +调用函数进行的求值,自然也可以嵌套使用。 +例如,定义一个函数 `concat2`,并两次调用它来拼接 `"Hello"` `" "` 和 `"world"`: ```nix let concat2 = { a, b }: a + b; @@ -1155,17 +1162,16 @@ in b = "world"; } ``` -求值结果仍然为 `"Hello world"`。 - 由于 `concat2` 接受的属性集仅含两个属性, 此例先拼接了 `"Hello"` 和 `" "`,再将此结果与 `"world"` 拼接。 +求值结果仍然为 `"Hello world"`。 -求值结果如下: -```plain -"Hello world" -``` -### 属性集参数的要求 + +::: + + +:::warning 函数被调用时所接受的属性集, 必须符合**定义中作为参数的属性集的要求**, 否则就会报错。 @@ -1193,20 +1199,24 @@ in error: function 'concat2' called without required argument 'a' ``` -但是,前述要求可以设置得更加灵活。 +但是,前述要求可以设置得更加灵活, +下面的若干节将会对此进行介绍。 + + +::: -#### 默认值 -在属性后面加 `? ` ,可以设定默认值。 +### 作为参数的属性集:属性默认值 +在属性后面加 `? ` ,会将此属性的默认值设为 ``。 -例如定义一个“问候”函数 `greet`, +例如,我们来定义一个“问候”函数 `greet`: - 它的功能是对 `object` 进行“问候”; -- 支持设定问候语 `greeting`,默认值为 `Hello `。 +- 支持设定问候语 `greeting`,默认值为 `"Hello "`。 - 设置方法是,在函数定义中参数里的 `greeting` 后面附加 `? "Hello, "`。 测试例: ```nix let - greet = { greeting ? "Hello, ", object }: lang + object; + greet = { greeting ? "Hello, ", object }: greeting + object + "!"; in { # 对 world 进行问候 @@ -1220,14 +1230,14 @@ in 严格求值,结果如下: ```plain { - R1 = "Hello, world"; - R2 = "Hello, my friend"; - R3 = "Welcome, my friend"; + R1 = "Hello, world!"; + R2 = "Hello, my friend!"; + R3 = "Welcome, my friend!"; } ``` -#### 额外属性 -在属性中添加一个占位符 `...`,这允许了传入额外属性。 +### 作为参数的属性集:额外属性 +为了在调用函数时能传入额外属性,需要在属性集中添加一个占位符 `...`。 例如: ```nix @@ -1248,53 +1258,107 @@ in } ``` 注意这里的 `R1` 和 `R2` 的值相同, -因为 `c` 作为额外属性,没有参与计算。 +因为 `c` 作为额外属性,不能出现在函数定义中,自然也不会参与计算。 -当然,定义的函数体中也并没有 `c`, -但若直接更改函数体,将会报错。 + +::: warning +在函数定义中,若函数体使用了参数中未定义的属性,不论参数是否含 `...` 都会报错。 例如: ```nix let # 参数中没有 c,但函数体里有 c concat2 = { a, b, ... }: a + b + c; in -{ - R1 = concat2 { a = "Hello "; b = "world"; }; - R2 = concat2 { a = "Hello "; b = "world"; c = "!"; }; -} + concat2 { a = "Hello "; b = "world"; c = "!"; } ``` -求值,报错(注意这个报错发生在 `in` **之前**): +求值,报错(注意这个报错发生在函数的定义部分): ```plain error: undefined variable 'c' ``` -#### 其他 + +::: -为额外的参数绑定到参数集,然后调用: +### 作为参数的属性集:命名属性集 +这里再次展示前面举过的例子, +定义函数 $f(x,y)=3x+\frac{y}{2}$,求值 $f(1,4)=5$, +用 Nix 实现如下: ```nix -args@{ a, b, ... }: a + b + args.c -{ a, b, ... }@args: a + b + args.c # 也可以是这样 +let + f = { x, y }: ( 3 * x ) + ( y / 2 ); +in + f { x = 1; y = 4; } +``` +求值,结果如下: +```plain +5 ``` -调用函数,并使用函数构建新属性集: + +:::info 命名属性集 +与匿名函数的概念类似, +若一个属性集没有与名称绑定, +则称其为匿名属性集。 +反之,则称为命名属性集。 + +::: -```nix -concat = { a, b }: a + b # 等价于 concat = x: x.a + x.b -concat { a = "Hello "; b = "NixOS"; } +此例的函数定义中,匿名属性集 `{ x, y }` 作为了参数。 + +而命名属性集也可以作为参数, +此时往往需要结合属性访问。 + +例如,上面的例子等价于:(求值结果不变) +``` +let + # 用命名属性集 A 代替了匿名属性集 { x, y } + # 同时 x、y 也要改用属性访问的写法 A.x、A.y + f = A: ( 3 * A.x ) + ( A.y / 2 ); +in + f { x = 1; y = 4; } ``` -输出: +此外,函数的参数可以是**一个**命名属性集与**一个**匿名属性集的结合, +两者以 `@` 连接(先后顺序不限),并且匿名属性集必须包含 `...` 以允许额外属性。 +例如,上面的例子还等价于:(求值结果不变) ```nix -Hello NixOS +let + f = { x, ... }@A: ( 3 * x ) + ( A.y / 2 ); + # 也可以写成 + # f = A@{ x, ... }: ( 3 * x ) + ( A.y / 2 ); +in + f { x = 1; y = 4; } ``` - ### 柯里化函数 -我们将 `f (a,b,c)` 转换为 `f (a)(b)(c)` 的过程就是柯里化。为什么需要柯里化?因为 -它很灵活,可以避免重复传入参数,当你传入第一个参数的时候,该函数就已经具有了第一 -个参数的状态(闭包)。 +柯里化(currying)指的是将一个 N 元函数转换为 N 个一元函数的嵌套序列的过程。 + + +:::note 拓展说明:数学上的柯里化 + +这里所说的一元函数不是普通的函数,在数学上对应的概念实际上是映射。 + +例如,三元函数 $F(x,y,z)=x+y+z$ 也即映射 $F: x,y,z\mapsto x+y+z$ +可以转换为三个一元映射的嵌套,分别是: +1. $F_1: x \mapsto ( y \mapsto ( z \mapsto x + y + z ))$ +2. $F_2: y \mapsto ( z \mapsto x + y + z )$ +3. $F_3: z \mapsto x + y + z$ + + +::: + + +:::note 拓展说明:柯里化的核心优点 + +柯里化很灵活,可以避免重复传入参数。 + +当你传入第一个参数的时候, +该函数就已经具有了第一个参数的状态(闭包)。 + + +::: 尝试声明一个柯里化函数: @@ -1347,102 +1411,6 @@ in f 1 2 ``` -### 属性集参数 - -当我们被要求必须传入多个参数时,使用这种函数声明方法: - -```nix -{a, b}: a + b -``` - -调用该函数: - -```nix -let - f = {a, b}: a + b; -in - f { a = 1; b = 2; } -``` - -如果我们额外传入参数,会怎么样? - -```nix -let - f = {a, b}: a + b; -in - f { a = 1; b = 2; c = 3; } -``` - -意外参数 `c`: - -```bash -error: 'f' at (string):2:7 called with unexpected argument 'c' - - at «string»:4:1: - - 3| in - 4| f { a = 1; b = 2; c = 3; } - | ^ - 5| -``` - -### 默认参数 - -前面稍微提到过一点,没有什么需要过多讲解的地方: - -```nix -let - f = {a, b ? 0}: a + b; -in - f { a = 1; } -``` - -传入参数值是可选的,根据你的需要来: - -```nix -let - f = {a, b ? 0}: a + b; -in - f { a = 1; b = 2; } -``` - -### 额外参数 - -有的时候,我们设计的函数不得不接收一些我们不需要的额外参数,我们可以使用 `...` -允许接收额外参数: - -```nix -{a, b, ...}: a + b -``` - -不比上个例子,这次不会报错: - -```nix -let - f = {a, b, ...}: a + b; -in - f { a = 1; b = 2; c = 3; } -``` - -### 命名参数集 - -又名 "`@` 模式"。在上文中,我们已经可以接收到额外的参数了,假如我们需要使用某 -个额外参数,我们可以使用命名属性集将其接收到一个另外的属性集: - -```nix -{a, b, ...}@args: a + b + args.c # 这样声明函数 -args@{a, b, ...}: a + b + args.c # 或是这样 -``` - -具体示例如下: - -```nix -let - f = {a, b, ...}@args: a + b + args.c; -in - f { a = 1; b = 2; c = 3; } -``` - ## 函数库 除了一些 From 9552de7cd323f4b298a1f708577e471f0d585fde Mon Sep 17 00:00:00 2001 From: clsty Date: Wed, 22 Oct 2025 15:55:50 +0800 Subject: [PATCH 07/30] Update QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 62 +++++++++++++++++------------ 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index 3933b5f7..7eb7efe2 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1049,9 +1049,9 @@ x: x + 1 ``` -这里的 LAMBDA(即希腊字母 λ)就是匿名函数的代表符号。 +这里的 LAMBDA(即希腊字母 λ)就是函数的代表符号。 -至于为什么 λ 被用来代表匿名函数, +至于为什么 λ 被用来代表函数, 请自行搜索“lambda 演算”以及“函数式编程”, 这里不再展开。 @@ -1060,7 +1060,7 @@ x: x + 1 :::tip 直接调用匿名函数 -利用 `(` `)` 将函数包裹起来,就可以直接调用匿名函数,例如 +利用 `(` `)` 将匿名函数的整体包裹起来,就可以直接调用了,例如 ```nix (x: x + 1) 2 ``` @@ -1166,7 +1166,6 @@ in 此例先拼接了 `"Hello"` 和 `" "`,再将此结果与 `"world"` 拼接。 求值结果仍然为 `"Hello world"`。 - ::: @@ -1360,19 +1359,24 @@ in ::: -尝试声明一个柯里化函数: +例如,函数 `{x,y}:x+y` 的柯里化形式如下: ```nix -x: y: x + y +x: (y: x + y) ``` -为了更好的可读性,我们推荐你这样写: - + +:::tip +虽然会降低可读性,下面的写法也是可以的: ```nix -x: (y: x + y) +x: y: x + y ``` + +::: -这个例子中的柯里化函数,虽然接收两个参数,但不是"迫切"需要: +虽然这个柯里化函数最终需要两个参数, +但它可以逐次接受这两个参数。 +例如我们先给它其中一个参数作为 `x` 的值: ```nix let @@ -1380,30 +1384,31 @@ let in f 1 ``` - -输出为: - +求值,结果显示为: ```nix ``` - -`f 1` 的值依然是函数,这个函数大概是: - +也即 `f 1` 的值依然是函数,此函数实际上就是 ```nix y: 1 + y; ``` -我们可以保存这个状态的函数,稍后再来使用: - +我们可以保存这个状态的函数,稍后再来使用。 +例如将 `f 1` 绑定到名称 `g`,再求值 `g 2`。 +其中 1 会作为参数 `x` 的值。 +而 2 会作为参数 `y` 的值。 ```nix let f = x: y: x + y; in - let g = f 1; in g 2 + let + g = f 1; + in + g 2 ``` +求值结果为 `3`。 -也可以一次性接收两个参数: - +也可以一次性接收两个参数(求值结果不变): ```nix let f = x: y: x + y; @@ -1413,11 +1418,11 @@ in ## 函数库 -除了一些 -[内建操作符](https://nixos.org/manual/nix/stable/language/operators.html) (`+`, -`==`, `&&`, 等),我们还要学习一些被视为事实标准的库。 +前面我们已经接触到了 `+`、`-`、`*`、`/` 等运算符号,实际上它们都是 Nix 语言中的[内建操作符](https://nixos.org/manual/nix/stable/language/operators.html)。 -### 内建函数 +除了内建操作符之外,还有两个被广泛使用的函数库,它们加在一起被视为 Nix 语言的事实标准。 + +### 内建函数 builtins 它们在 Nix 语言中并不是 `` 类型,而是 `` 元操作类型(primitive operations)。这些函数是内置在 Nix 解释器中,由 C++ 实现。查询 @@ -1428,7 +1433,8 @@ operations)。这些函数是内置在 Nix 解释器中,由 C++ 实现。查 builtins.toString() # 通过 builtins 使用函数 ``` -### 导入 + +:::info import `import` 表达式以其他 Nix 文件的路径为参数,返回该 Nix 文件的求值结果。 @@ -1454,3 +1460,7 @@ import ./file.nix 1 2 ``` + +::: + +### pkgs.lib From 1db27e25c86826ec9a7a9c578983806e04e93128 Mon Sep 17 00:00:00 2001 From: clsty Date: Wed, 22 Oct 2025 17:43:17 +0800 Subject: [PATCH 08/30] Update QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 191 +++++++++++++++++++++++----- 1 file changed, 160 insertions(+), 31 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index 7eb7efe2..79a085b6 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -11,7 +11,7 @@ :::
仅面向本文维护者的说明,单击以切换折叠/展开 -本文在设计上是线性的,也即只需要读者具备一点点基础,就可以通过按顺序从头读到尾的方式完成本文的学习。因此,请留心说明顺序,例如讲 let 绑定时如果举了一个列表的例子,你需要确保前面已经正式介绍过列表。再如,讲 with 语法糖的时候同时用到 let 绑定和列表,那么这两个概念都需要在前面已经正式介绍过。否则,读者很可能会面对初次接触的语法或者概念而被卡住,这会严重影响学习效率甚至是完成率。若出于顺序安排的其他合理性原因,实在无法避开在说明中涉及陌生概念,可以提示读者相关部分不需要理解,后面会讲到。 +本文在设计上是线性的,也即只需要读者具备一点点基础,就可以通过按顺序从头读到尾的方式完成本文的学习。因此,请留心说明顺序,例如讲 let 绑定时如果举了一个列表的例子,你需要确保前面已经正式介绍过列表。再如,讲 with 语法糖的时候同时用到 let 绑定和列表,那么这两个概念都需要在前面已经正式介绍过。否则,读者很可能会面对初次接触的语法或者概念而被卡住,这会严重影响学习效率甚至是完成率。若出于顺序安排的其他合理性原因,实在无法避开在说明中涉及陌生概念,可以提示读者相关部分不需要理解,后面会讲到。另外,常用的 callout 块中,info 显示为蓝色,适用于普通知识点;而 note 显示为灰色,适用于就算不理解也没关系的高阶或补充知识。至于 tip 和 warning,它们有可能涉及到知识,也可能不涉及,重点区别在于 tip 偏向于“实用建议/能帮助理解或加强记忆的提示”,而 warning 偏向于“能够避免损失的提示”(包括时间精力方面的)以及“一不小心就可能陷入误区”。
Nix 作为语言,是一门简单的函数式语言,它被专门设计并用于 Nix 包管理器及相关生态 @@ -1037,7 +1037,7 @@ x: x + 1 在此例中,左边的 `x` 是参数,右边的 `x+1` 是函数体。 -::: info 匿名函数 +::: info 匿名函数与 λ 机智的你可能会发现, 此示例实现的 $f(x)=x+1$ 并不完整—— @@ -1051,6 +1051,9 @@ x: x + 1 这里的 LAMBDA(即希腊字母 λ)就是函数的代表符号。 +在一些语言中,λ 特指匿名函数, +而在 Nix 语言中,`` 只是一种数据类型,指代一般的函数。 + 至于为什么 λ 被用来代表函数, 请自行搜索“lambda 演算”以及“函数式编程”, 这里不再展开。 @@ -1069,7 +1072,7 @@ x: x + 1 ::: -函数本身作为数据类型,是可以与名称绑定的。 +函数是一种数据类型,自然可以将函数与名称绑定。 在前面例子的基础上,我们将函数绑定到名称 `f`,并且将 2 作为其参数来调用: @@ -1084,14 +1087,14 @@ in ```plain 3 ``` -这相当于定义了函数 $f(x)=x+1$ 之后求值 $f(2)$,结果为 3。 +这相当于先定义函数 $f(x)=x+1$ ,再求 $f(2)$ 的值,结果为 3。 ### 作为参数的属性集:基本形式 -在前面的例子中,我们只实现了一个非常简单的一元函数 $f(x)=x+1$。 +在前面的例子中,我们只实现了一个简单的一元函数 $f(x)=x+1$。 那么对于多元函数,比如 $f(x,y)=3x+\frac{y}{2}$,在 Nix 中应该怎么实现呢? - 坏消息是,根据 Nix 语法规范,每个函数在形式上**有且仅有一个参数**。 -- 好消息是,这个参数可以是属性集,并且在函数体中可以将属性集中的**各个属性单独拿出来使用**。 +- 好消息是,这个参数**可以是属性集**,并且在函数体中可以将属性集中的**各个属性单独拿出来使用**。 例如 @@ -1103,7 +1106,7 @@ in 实际功能却相当于数学上的二元函数 $f(x,y)=3x+\frac{y}{2}$。 -::: tip 属性集的语法细节 +::: warning 属性集的语法细节 在**函数定义**中作为参数出现的属性集,只包含属性名称,并且用 `,` 分隔。 这与之前介绍的属性集和列表都不同。 @@ -1418,49 +1421,175 @@ in ## 函数库 -前面我们已经接触到了 `+`、`-`、`*`、`/` 等运算符号,实际上它们都是 Nix 语言中的[内建操作符](https://nixos.org/manual/nix/stable/language/operators.html)。 +前面我们已经接触到了 `+`、`-`、`*`、`/` 等运算符号, +实际上它们都属于 Nix 语言中的内建操作符。 +常用的内建操作符还有 `==` `&&` 等。 +建议至少浏览一遍[内建操作符的文档页面](https://nix.dev/manual/nix/stable/language/operators.html), +以熟悉可用的功能。 + +除了内建操作符之外,还有两个被广泛使用的函数库, +它们加在一起被视为 Nix 语言的事实标准。 -除了内建操作符之外,还有两个被广泛使用的函数库,它们加在一起被视为 Nix 语言的事实标准。 +### builtins +builtins 即内建函数,也称为“原始操作” +(primitive operations,简写为 primops)。 -### 内建函数 builtins +Nix 附带许多内建函数, +它们作为 Nix 语言解释器的一部分,用 C++ 实现。 -它们在 Nix 语言中并不是 `` 类型,而是 `` 元操作类型(primitive -operations)。这些函数是内置在 Nix 解释器中,由 C++ 实现。查询 -[内建函数](https://nixos.org/manual/nix/stable/language/builtins.html) 以了解其 -使用方法。 +Nix 手册列出了所有 [builtins](https://nix.dev/manual/nix/stable/language/builtins.html) 函数。 + +这些函数可以通过常量 `builtins` 访问,例如前面提到过的 `toString`: ```nix -builtins.toString() # 通过 builtins 使用函数 +builtins.toString +``` +求值,结果如下: +```plain + ``` -:::info import +:::info import 函数 +大多数内置函数只能通过 `builtins` 访问。 +一个显著的例外是 `import`,它可在顶层直接使用。 + +`import` 接受的参数是 Nix 文件的路径, +会对其进行文件求值并返回结果。 +此路径也可以是目录, +这种情况下则会使用该目录下的 `default.nix` 文件。 + +例如,令 `foo.nix` 的文件内容为 `1 + 2`, +有如下示例 +```nix +import ./foo.nix +``` +求值,结果为 `3`。 -`import` 表达式以其他 Nix 文件的路径为参数,返回该 Nix 文件的求值结果。 +被导入的 Nix 文件必须是 Nix 表达式, +这个表达式自然也可以是函数本身。 -`import` 的参数如果为文件夹路径,那么会返回该文件夹下的 `default.nix` 文件的执行 -结果。 +例如,令 `foo.nix` 的文件内容为 `x: x + 1`, +有如下示例 +```nix +import ./foo.nix 4 +``` +求值,结果为 `5`。 -如下示例中,`import` 会导入 `./file.nix` 文件,并返回该文件的求值结果: + +::: -```bash -$ echo 1 + 2 > file.nix -import ./file.nix -3 +### pkgs.lib + +[`nixpkgs`](https://github.com/NixOS/nixpkgs) 仓库包含一个名为 [`lib`](https://github.com/NixOS/nixpkgs/blob/master/lib/default.nix) 的属性集, +它提供了大量有用的函数,详见 [Nixpkgs 手册](https://nixos.org/manual/nixpkgs/stable/#sec-functions-library)。 + +这些函数是基于 Nix 语言实现的, +而不是像 `builtins` 那样本身作为语言的一部分而存在。 + +这些函数通常通过 `pkgs.lib` 访问,因为 Nixpkgs 的属性集通常约定命名为 `pkgs`。 + +例如能够将小写转大写的 `pkgs.lib.strings.toUpper` 函数,示例: + +```nix +let + pkgs = import {}; +in +pkgs.lib.strings.toUpper "Have a good day!" +``` +求值,结果如下: +```plain +"HAVE A GOOD DAY!" +``` + +上面的例子较为复杂,不过到现在你应该熟悉它的各个组成部分了。 + +名称 `pkgs` 被声明为从路径为 `` 的文件 `import` 出来的表达式。 +至于 `` 的具体值则由环境变量 `$NIX_PATH` 决定。 +由于该表达式是一个函数,需要一个参数才能求值, +在这个例子中传入空的属性集 `{}` 就足够了。 + +现在 `pkgs` 在 `let ... in ...` 的作用域内,其下的属性可以被访问。 +据 Nixpkgs 手册可知, +其下存在一个函数 [`lib.strings.toUpper`](https://nixos.org/manual/nixpkgs/stable/#function-library-lib.strings.toUpper), +作用是小写转大写: +> Converts an ASCII string s to upper-case. + + +:::note 固定 Nixpkgs 的版本 +函数 `toUpper` 足够简单,使用不同版本的 Nixpkgs 一般不会有不同的结果, +因此使用 `` 也足够了。 + +然而,更复杂的情况下,要保证完全可重复的例子, +应该像下面这样使用固定(pin)版本的 Nixpkgs: + +```nix +let + nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/archive/06278c77b5d162e62df170fec307e83f1812d94b.tar.gz"; + pkgs = import nixpkgs {}; +in +pkgs.lib.strings.toUpper "Have a good day!" ``` -被导入的 Nix 文件可以返回任何内容,返回值可以向上面的例子一样是数值,也可以是属 -性集(attribute set)、函数、列表,等等。 + +::: + + +:::tip 作为参数的 pkgs 和 lib -如下示例导入了 `file.nix` 文件中定义的一个函数,并使用参数调用了该函数: +`pkgs` 常被作为参数传递给函数。 +按约定,可以假设它指的是 Nixpkgs 的属性集, +该属性集有一个 `lib` 属性。 +例如,将下面的例子写入 `foo.nix`: +```nix +{ pkgs, ... }: +pkgs.lib.strings.removePrefix "I " "I see you!" +``` +在命令行将 `{ pkgs = import {}; }` 作为参数, +进行文件求值: ```bash -$ echo "x: x + 1" > file.nix -import ./file.nix 1 -2 +nix-instantiate --eval foo.nix --arg pkgs 'import {}' +``` +运行结果如下: +```plain +"see you!" +``` + +而在 NixOS 配置中以及 Nixpkgs 内部, +你还经常会看到直接传入 `lib` 的情况。 +此时可以假设它指的是 Nixpkgs 的属性集下的 `lib`, +也即前面那种情况下的 `pkgs.lib`。 + +例如,将下面的例子写入 `foo.nix`: +```nix +{ lib, ... }: +lib.strings.removePrefix "I " "I see you!" +``` +在命令行将 `{ lib = (import {}).lib; }` 作为参数, +进行文件求值: +```bash +nix-instantiate --eval foo.nix --arg lib '(import {}).lib' +``` +运行结果与前面一例相同。 + +有时还会同时传入 `pkgs` 和 `lib`, +此时可以假设 `pkgs.lib` 与 `lib` 是等价的。 +这样做则是为了通过避免重复使用 `pkgs.lib` 来提高可读性。 + +示例: +```nix +{ pkgs, lib, ... }: +# ... 多次使用 `pkgs` +# ... 多次使用 `lib` ``` ::: -### pkgs.lib + +:::note +出于历史原因,`pkgs.lib` 中的一些函数与同名的 `builtins` 等价。 + + +::: From 6d3efc8d01ae9a49684dbf905443f2676abac358 Mon Sep 17 00:00:00 2001 From: clsty Date: Wed, 22 Oct 2025 19:36:49 +0800 Subject: [PATCH 09/30] Update QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 105 ++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index 79a085b6..3db9a00f 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1062,7 +1062,7 @@ x: x + 1 ::: -:::tip 直接调用匿名函数 +::: tip 直接调用匿名函数 利用 `(` `)` 将匿名函数的整体包裹起来,就可以直接调用了,例如 ```nix (x: x + 1) 2 @@ -1096,6 +1096,14 @@ in - 坏消息是,根据 Nix 语法规范,每个函数在形式上**有且仅有一个参数**。 - 好消息是,这个参数**可以是属性集**,并且在函数体中可以将属性集中的**各个属性单独拿出来使用**。 + +::: tip +“每个函数在形式上有且仅有一个参数”, +这个特性为我们后面将会提到的函数的柯里化提供了方便。 + + +::: + 例如 ```nix @@ -1139,7 +1147,7 @@ in 这相当于定义了函数 $f(x,y)=3x+\frac{y}{2}$ 之后求值 $f(1,4)$,结果为 5。 -:::tip 更多示例 +::: tip 更多示例 Nix 的函数也能处理其它数据类型。 例如,定义一个函数 `concat3` 并调用它来拼接 `"Hello"` `" "` 和 `"world"`: @@ -1173,7 +1181,7 @@ in ::: -:::warning +::: warning 函数被调用时所接受的属性集, 必须符合**定义中作为参数的属性集的要求**, 否则就会报错。 @@ -1221,11 +1229,11 @@ let greet = { greeting ? "Hello, ", object }: greeting + object + "!"; in { - # 对 world 进行问候 + # 对 world 进行问候(默认问候语) R1 = greet { object = "world"; } ; - # 对 my friend 进行问候 + # 对 my friend 进行问候(默认问候语) R2 = greet { object = "my friend"; } ; - # 自定义问候语,对 my friend 进行问候 + # 对 my friend 进行问候(自定义问候语) R3 = greet { greeting = "Welcome, "; object = "my friend"; } ; } ``` @@ -1298,7 +1306,7 @@ in ``` -:::info 命名属性集 +::: info 命名属性集 与匿名函数的概念类似, 若一个属性集没有与名称绑定, 则称其为匿名属性集。 @@ -1338,7 +1346,7 @@ in 柯里化(currying)指的是将一个 N 元函数转换为 N 个一元函数的嵌套序列的过程。 -:::note 拓展说明:数学上的柯里化 +::: note 拓展说明:数学上的柯里化 这里所说的一元函数不是普通的函数,在数学上对应的概念实际上是映射。 @@ -1351,17 +1359,6 @@ in ::: - -:::note 拓展说明:柯里化的核心优点 - -柯里化很灵活,可以避免重复传入参数。 - -当你传入第一个参数的时候, -该函数就已经具有了第一个参数的状态(闭包)。 - - -::: - 例如,函数 `{x,y}:x+y` 的柯里化形式如下: ```nix @@ -1369,7 +1366,7 @@ x: (y: x + y) ``` -:::tip +::: tip 虽然会降低可读性,下面的写法也是可以的: ```nix x: y: x + y @@ -1383,7 +1380,7 @@ x: y: x + y ```nix let - f = x: y: x + y; + f = x: (y: x + y); in f 1 ``` @@ -1396,13 +1393,13 @@ in y: 1 + y; ``` -我们可以保存这个状态的函数,稍后再来使用。 -例如将 `f 1` 绑定到名称 `g`,再求值 `g 2`。 +我们可以将 `f 1` 绑定到名称 `g` 以保存起来, +以供后续使用,例如 `g 2`。 其中 1 会作为参数 `x` 的值。 而 2 会作为参数 `y` 的值。 ```nix let - f = x: y: x + y; + f = x: (y: x + y); in let g = f 1; @@ -1414,11 +1411,53 @@ in 也可以一次性接收两个参数(求值结果不变): ```nix let - f = x: y: x + y; + f = x: (y: x + y); in f 1 2 ``` + +::: note 柯里化与闭包 + +前面提到, +> 我们可以将 `f 1` 绑定到名称 `g` 以保存起来, +> 以供后续使用,例如 `g 2`。 + +这种“保存了某种状态”的函数,被称为闭包(closure)。 +利用闭包,有时可以避免重复传入参数。 + +之前,为了演示默认值,我们自定义了一个问候函数 `greet`: +```nix +let + greet = { greeting ? "Hello, ", object }: greeting + object + "!"; +in + { + R1 = greet { object = "world"; } ; + R2 = greet { object = "my friend"; } ; + R3 = greet { greeting = "Welcome, "; object = "my friend"; } ; + } +``` +这里我们改用柯里化结合闭包的方法来实现, +甚至可以更加简洁(求值结果与之前例子相同): +```nix +let + greet = greeting : ( object : greeting + object + "!" ); + # greet_Hello 就是一个闭包,调用它可以避免重复传入 "Hello, " + greet_Hello = greet "Hello, "; +in + { + # 对 world 进行问候(用 greet_Hello) + R1 = greet_Hello "world"; + # 对 my friend 进行问候(用 greet_Hello) + R2 = greet_Hello "my friend"; + # 对 my friend 进行问候(自定义问候语) + R3 = greet "Welcome, " "my friend"; + } +``` + + +::: + ## 函数库 前面我们已经接触到了 `+`、`-`、`*`、`/` 等运算符号, @@ -1450,7 +1489,7 @@ builtins.toString ``` -:::info import 函数 +::: info import 函数 大多数内置函数只能通过 `builtins` 访问。 一个显著的例外是 `import`,它可在顶层直接使用。 @@ -1487,7 +1526,8 @@ import ./foo.nix 4 这些函数是基于 Nix 语言实现的, 而不是像 `builtins` 那样本身作为语言的一部分而存在。 -这些函数通常通过 `pkgs.lib` 访问,因为 Nixpkgs 的属性集通常约定命名为 `pkgs`。 +这些函数通常通过 `pkgs.lib` 访问, +因为 Nixpkgs 的属性集通常约定命名为 `pkgs`。 例如能够将小写转大写的 `pkgs.lib.strings.toUpper` 函数,示例: @@ -1502,6 +1542,8 @@ pkgs.lib.strings.toUpper "Have a good day!" "HAVE A GOOD DAY!" ``` + +::: tip 详细说明 上面的例子较为复杂,不过到现在你应该熟悉它的各个组成部分了。 名称 `pkgs` 被声明为从路径为 `` 的文件 `import` 出来的表达式。 @@ -1516,7 +1558,10 @@ pkgs.lib.strings.toUpper "Have a good day!" > Converts an ASCII string s to upper-case. -:::note 固定 Nixpkgs 的版本 +::: + + +::: note 固定 Nixpkgs 的版本 函数 `toUpper` 足够简单,使用不同版本的 Nixpkgs 一般不会有不同的结果, 因此使用 `` 也足够了。 @@ -1535,7 +1580,7 @@ pkgs.lib.strings.toUpper "Have a good day!" ::: -:::tip 作为参数的 pkgs 和 lib +::: tip 作为参数的 pkgs 和 lib `pkgs` 常被作为参数传递给函数。 按约定,可以假设它指的是 Nixpkgs 的属性集, @@ -1588,7 +1633,7 @@ nix-instantiate --eval foo.nix --arg lib '(import {}).lib' ::: -:::note +::: note 出于历史原因,`pkgs.lib` 中的一些函数与同名的 `builtins` 等价。 From 0f9ee08216bc9d66d1d0625d41c9dd885bd4e9e6 Mon Sep 17 00:00:00 2001 From: clsty Date: Wed, 22 Oct 2025 20:03:16 +0800 Subject: [PATCH 10/30] Update QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 160 +++++++++++++++++++--------- 1 file changed, 107 insertions(+), 53 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index 3db9a00f..34bd7026 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -11,7 +11,7 @@ :::
仅面向本文维护者的说明,单击以切换折叠/展开 -本文在设计上是线性的,也即只需要读者具备一点点基础,就可以通过按顺序从头读到尾的方式完成本文的学习。因此,请留心说明顺序,例如讲 let 绑定时如果举了一个列表的例子,你需要确保前面已经正式介绍过列表。再如,讲 with 语法糖的时候同时用到 let 绑定和列表,那么这两个概念都需要在前面已经正式介绍过。否则,读者很可能会面对初次接触的语法或者概念而被卡住,这会严重影响学习效率甚至是完成率。若出于顺序安排的其他合理性原因,实在无法避开在说明中涉及陌生概念,可以提示读者相关部分不需要理解,后面会讲到。另外,常用的 callout 块中,info 显示为蓝色,适用于普通知识点;而 note 显示为灰色,适用于就算不理解也没关系的高阶或补充知识。至于 tip 和 warning,它们有可能涉及到知识,也可能不涉及,重点区别在于 tip 偏向于“实用建议/能帮助理解或加强记忆的提示”,而 warning 偏向于“能够避免损失的提示”(包括时间精力方面的)以及“一不小心就可能陷入误区”。 +本文在设计上是线性的,也即只需要读者具备一点点基础,就可以通过按顺序从头读到尾的方式完成本文的学习。因此,请留心说明顺序,例如讲 let 绑定时如果举了一个列表的例子,你需要确保前面已经正式介绍过列表。再如,讲 with 语法糖的时候同时用到 let 绑定和列表,那么这两个概念都需要在前面已经正式介绍过。否则,读者很可能会面对初次接触的语法或者概念而被卡住,这会严重影响学习效率甚至是完成率。若出于顺序安排的其他合理性原因,实在无法避开在说明中涉及陌生概念,可以提示读者相关部分不需要理解,后面会讲到。另外,常用的 callout 块中,info 显示为蓝色,适用于普通知识点;而 note 显示为灰色,适用于“就算不理解也没关系”的高阶或补充知识。至于 tip 和 warning,它们有可能涉及到知识,也可能不涉及,重点区别在于 tip 偏向于“实用建议/能帮助理解或加强记忆的提示”,而 warning 偏向于“能够避免损失的提示”(包括时间精力方面的)以及“注意避开误区的说明”。
Nix 作为语言,是一门简单的函数式语言,它被专门设计并用于 Nix 包管理器及相关生态 @@ -27,12 +27,11 @@ Nix 作为语言,是一门简单的函数式语言,它被专门设计并用 ::: warning 本节需要你已经安装了 Nix 或正在使用 NixOS。 -另外,本教程中的示例代码并不全是为了供直接运行而写的。对于每一段代码,若想实践其 -效果,请先理解对应的知识,再基于这段代码自己编写测试代码以运行。 - -(对于 Nix 来说,运行代码被称为**求值**(evaluate),而只有**表达 -式**(expression)能被求值;但是,示例的代码未必是表达式,而可能是属性集的元素 -等。) +另外,本教程中的示例代码并不全是为了供直接运行而写的。 +对于 Nix 来说,运行代码被称为**求值**(evaluate), +而只有**表达式**(expression)能被求值; +但是,示例的代码未必是表达式,此时若仍想进行测试, +则需要你基于此示例,自己编写测试代码。 ::: @@ -74,10 +73,11 @@ nix-repl> ### 文件求值 -交互模式简单快捷,但我们平时使用 Nix 语言进行编辑配置、打包等操作时,大多数情况 -下不会直接使用交互模式,而是对 `*.nix` 纯文本文件进行编辑。 +交互模式简单快捷,但我们平时使用 Nix 语言进行编辑配置、打包等操作时, +大多数情况下不会直接使用交互模式,而是对 `*.nix` 纯文本文件进行编辑。 -因此,如果你习惯于使用编辑器,这里更推荐利用文件求值进行实践。每个 nix 文件的内容都是**一个**表达式,这是 nix 文件能被求值的前提。 +因此,如果你习惯于使用编辑器,这里更推荐利用文件求值进行实践。 +每个 nix 文件的内容都是**一个**表达式,这是 nix 文件能被求值的前提。 例如,新建文件 `foo.nix`,将其内容编辑如下: @@ -177,7 +177,7 @@ let a = builtins.div 2 0; b = 3; in b { # 这是一句注释,放在代码所在行的末尾。 # 这也是一句注释,单独占了一行。 a = 1; # 这一行即使不缩进,也不影响代码本质。 - b = 2; + b = 2; # c = 3; # 这里的代码被注释掉了,相当于不存在。 } ``` @@ -190,11 +190,17 @@ let a = builtins.div 2 0; b = 3; in b ::: ## 名称与属性集 -变量是大多数编程语言中最基础的概念,而与之类似的名称则是 Nix 语言中最基础的概念。本节将会介绍 Nix 中如何将名称**分配给值**,以及最常用的数据类型——**属性集**,继而引出**递归属性集**与**列表**的概念。 +变量是大多数编程语言中最基础的概念, +而与之类似的名称则是 Nix 语言中最基础的概念。 +本节将会介绍 Nix 中如何将名称**分配给值**, +以及最常用的数据类型——**属性集**, +继而引出**递归属性集**与**列表**的概念。 ### 名称和值 -我们可以使用 `=` 将名称分配给值,形成“名称 - 值”对。例如将名称 `foo` 分配给值 `123`: +我们可以使用 `=` 将名称分配给值,形成“名称 - 值”对。 + +例如将名称 `foo` 分配给值 `123`: ```nix foo = 123 @@ -202,14 +208,19 @@ foo = 123 ::: tip 如何测试此示例 -上面的示例不属于表达式(但可以作为表达式的一部分),所以你无法将它直接写入 nix 文件进行文件求值;不过 `nix repl` 有一些灵活的处理,允许你输入这样的结构。 +上面的示例不属于表达式(但可以作为表达式的一部分), +所以你无法将它直接写入 nix 文件进行文件求值; +不过 `nix repl` 有一些灵活的处理,允许你输入这样的结构。 ::: ::: info 函数式语言与命令式语言中“变量”的区别 -太长不看版:在 Nix 里,“名称”就是“变量”,只是这个变量一旦绑定便成永恒;它保留了数学“可取不同值”的语义,却丢掉了命令式“可重新赋值”的含义。 +太长不看版:在 Nix 里,“名称”就是“变量”, +只是这个变量一旦绑定便成永恒; +它保留了数学“可取不同值”的语义, +却丢掉了命令式“可重新赋值”的含义。 | 维度 | 命令式语言 | Nix(函数式) | |---|---|---| @@ -221,8 +232,8 @@ foo = 123 ::: -名称的值并不仅限于 `123` 这种整数。具体来说有以下数据类型(不需要完全理解,留下印象 -即可) +名称的值并不仅限于 `123` 这种整数。 +一些常见的数据类型如下(不需要完全理解,留下印象即可) - 字符串(string),例如 `"Hello world"` - 整数(integer),例如 `1` @@ -231,6 +242,7 @@ foo = 123 - null,只有 `null` 一种 - 列表(list),例如 `[ 1 "tux" false ]` - 属性集(attribute set),例如 `{ a = 1; b = "tux"; c = false; }` +- 函数(function),例如 `x: x + 1` ### 属性集 @@ -260,8 +272,9 @@ foo = 123 - 属性 `a`,其值为 `1` - 属性 `b`,其值为 `2` -属性的值除了可以是 `1` `2` 这样的数值外,也可以是一个属性集(也即支持嵌套),例如 -将 `b` 的值改为属性集 `{ c = 2; d = 3; }`: +属性的值除了可以是 `1` `2` 这样的数值外, +也可以是一个属性集(也即支持嵌套), +例如将 `b` 的值改为属性集 `{ c = 2; d = 3; }`: ```nix { @@ -273,7 +286,8 @@ foo = 123 } ``` -嵌套属性集中的属性也可以利用 `.` 表示,例如上面这段的一种等价写法如下: +嵌套属性集中的属性也可以利用 `.` 表示, +例如上面这段的一种等价写法如下: ```nix { @@ -303,7 +317,11 @@ foo = 123 ``` error: undefined variable 'a' ``` -可见,当属性集内的属性 `b` 需要访问该属性集的另一个属性 `a` 时,即使 `a` 是“先”定义的,也无法访问到。此时就需要我们改用递归(recursive)属性集,它相比普通的属性集,在前面多加了 `rec `: +可见,当属性集内的属性 `b` 需要访问该属性集的另一个属性 `a` 时, +即使 `a` 是“先”定义的,也无法访问到。 + +此时就需要我们改用递归(recursive)属性集, +它相比普通的属性集,在前面多加了 `rec `: ```nix rec { @@ -320,7 +338,15 @@ rec { ::: info 求值结果的排序依据 -可以看到,结果中的 `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 解释器对名称排序所用到的算法或者调用的库有关,这里不再深入。)_ ::: @@ -336,7 +362,12 @@ rec { a = 1; } ``` -你会发现,Nix 解释器能自动处理求值顺序,并不会因为 `a` 的声明被调整到后面而影响求值结果(与之前的完全一致,从略)。这看起来相当“智能”,你甚至可以写得更复杂一些,比如 Nix 解释器也能自动处理下面的例子(结果略): +你会发现,Nix 解释器似乎能自动处理求值顺序, +并不会因为 `a` 的声明被调整到后面而影响求值结果 +(与之前的完全一致,从略)。 + +这看起来相当“智能”,你甚至可以写得更复杂一些, +比如 Nix 解释器也能自动处理下面的例子(结果略): ```nix rec { c = a * 2 - b + d - 35; @@ -345,8 +376,8 @@ rec { d = a - 15; } ``` - -不过,这并不代表你可以直接用它来解方程。例如我们再写一个在数学上有唯一解的方程组: +不过,这并不代表你可以直接用它来解方程。 +例如我们再写一个在数学上有唯一解的方程组: ```nix rec { b = a * 2 + 1; @@ -360,7 +391,8 @@ rec { b = «error: infinite recursion encountered»; } ``` -由此可见,递归属性集内部处理求值顺序的机制,确实是递归的,而如果递归陷入死循环就会报错。 +由此可见,递归属性集内部处理求值顺序的机制, +确实是递归的,而如果递归陷入死循环就会报错。 ::: @@ -374,7 +406,9 @@ rec { c = "banana"; } ``` -上面的名称 `a` `b` `c` 或许可以有明确的含义,但有些场景不需要这些名称,而只关心后面的值,这种情况下就可以使用列表,例如: +上面的名称 `a` `b` `c` 或许可以有明确的含义, +但有些场景不需要这些名称,而只关心后面的值, +这种情况下就可以使用列表,例如: ```nix [ "apple" "orange" "banana" ] ``` @@ -383,9 +417,14 @@ rec { - 元素之间使用空格(或换行)分隔,各元素**不**以 `;` 结尾。 ## let 绑定与属性访问 -前面关于名称的使用是非常基本的,我们还需要更灵活的处理方法。本节将会介绍另一种将名称分配给值的方法——**let 绑定**,以及风格简洁的**属性访问**。 +前面关于名称的使用是非常基本的,我们还需要更灵活的处理方法。 + +本节将会介绍另一种将名称分配给值的方法——**let 绑定**, +以及风格简洁的**属性访问**。 + ### `let` 绑定 -有时我们希望在指定的范围内为值分配名称,此时就可以使用 `let` 绑定,示例如下: +有时我们希望在指定的范围内为值分配名称, +此时就可以使用 `let` 绑定,示例如下: ```nix let @@ -413,17 +452,17 @@ in ::: info 作用域 -`let` 绑定是有作用域的,绑定的名称只能在作用域使用,或者说每个 `let` 绑定的名称只能在该表达式内使用。例如下面的例子: +`let` 绑定是有作用域的,绑定的名称只能在作用域使用, +或者说每个 `let` 绑定的名称只能在该表达式内使用。 +例如下面的例子: ```nix { a = let x = 1; in x; b = x; } ``` - 由于 `b = x;` 不在作用域之内,会有报错如下: - ``` error: undefined variable 'x' ``` @@ -433,21 +472,26 @@ error: undefined variable 'x' ::: note 拓展说明:局部变量(?) -Nix 中不存在“全局变量”,因而“局部变量”的说法可能引起误会,应当尽量避免使用。 +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. -这种说法可能不合适,但既然官方文档也有用到,其他地方自然也可能会出现,留心即可。 +这种说法可能不合适,但既然官方文档也有用到, +其他地方自然也可能会出现,留心即可。 ::: ### 属性访问 -前面提到,嵌套属性集中的属性可以利用 `.` 表示,这被称为属性访问(attribute access)。 +前面提到,嵌套属性集中的属性可以利用 `.` 表示, +这被称为属性访问(attribute access)。 -在下面这个例子中,我们定义了一个嵌套属性集 `a`,并使用 `a.b.c` 访问值 `123`: +在下面这个例子中,我们定义了一个嵌套属性集 `a`, +并使用 `a.b.c` 访问值 `123`: ```nix let a = { b = { c = 123; }; }; @@ -475,9 +519,11 @@ in ::: tip 小结 -本小节给出了属性访问的两种应用场景,第一种是获取属性的值,第二种是为值分配属性名称。 +本小节给出了属性访问的两种应用场景, +第一种是获取属性的值,第二种是为值分配属性名称。 -显然,第二种场景**不是**必须使用属性访问的写法,它只是更方便。仅就这个场景来看,这是一种**语法糖**。 +显然,第二种场景**不是**必须使用属性访问的写法,它只是更方便。 +仅就这个场景来看,这是一种**语法糖**。 我们将在下一节介绍另外两种常用的语法糖。 @@ -485,7 +531,10 @@ in ::: ## 语法糖 `with` 和 `inherit` -语法糖(syntactic sugar)是对语言功能没有影响,但更方便使用的一种语法。本节将介绍两种常用的语法糖 `with` 和 `inherit`。 +语法糖(syntactic sugar)是对语言功能没有影响, +但更方便使用的一种语法。 + +本节将介绍两种常用的语法糖 `with` 和 `inherit`。 ### `with` 表达式 @@ -592,7 +641,8 @@ in ::: warning -`inherit` 的语法结构,例如上面的 `inherit x;`,本质上仍然属于“名称-值”对,不属于表达式。 +`inherit` 的语法结构,例如上面的 `inherit x;`, +本质上仍然属于“名称-值”对,不属于表达式。 ::: @@ -651,7 +701,8 @@ in ## 文件系统路径 -在 Nix 语言中,文件系统路径(file system paths;简称路径)是一种数据类型,它不同于后面要介绍的字符串类型。 +在 Nix 语言中,文件系统路径(file system paths;简称路径)是一种数据类型, +它不同于后面要介绍的字符串类型。 ### 路径的基本语法 @@ -790,7 +841,8 @@ error: syntax error, unexpected '.' ## 字符串 -字符串(string)是一种常见的数据类型,其最简单的形式是以一对双引号 `"` `"` 包裹所需内容。 +字符串(string)是一种常见的数据类型, +其最简单的形式是以一对双引号 `"` `"` 包裹所需内容。 例如: @@ -1043,7 +1095,9 @@ x: x + 1 毕竟,$f(x)=x+1$ 的函数名 $f$ 去哪里了? -确实,上面的例子所写的是**匿名函数**,即没有和名称绑定。 +确实,上面的例子少了函数名, +它没有和名称绑定,被称为**匿名函数**。 + 我们对它进行求值,结果如下: ```plain @@ -1052,7 +1106,7 @@ x: x + 1 这里的 LAMBDA(即希腊字母 λ)就是函数的代表符号。 在一些语言中,λ 特指匿名函数, -而在 Nix 语言中,`` 只是一种数据类型,指代一般的函数。 +不过,在 Nix 语言中,`` 只是一种数据类型,指代一般的函数。 至于为什么 λ 被用来代表函数, 请自行搜索“lambda 演算”以及“函数式编程”, @@ -1099,7 +1153,8 @@ in ::: tip “每个函数在形式上有且仅有一个参数”, -这个特性为我们后面将会提到的函数的柯里化提供了方便。 +这个特性其实不算缺点, +比如它为函数的柯里化(之后会介绍)提供了方便。 ::: @@ -1132,7 +1187,8 @@ in ::: -我们为前面的例子绑定名称 `f` 并且以参数 `{ x = 1; y = 4; }` 来调用它: +我们为前面例子中的匿名函数绑定名称 `f`, +并且以参数 `{ x = 1; y = 4; }` 来调用它: ```nix let @@ -1163,7 +1219,8 @@ in ``` 调用函数进行的求值,自然也可以嵌套使用。 -例如,定义一个函数 `concat2`,并两次调用它来拼接 `"Hello"` `" "` 和 `"world"`: +例如,定义一个函数 `concat2`, +并两次调用它来拼接 `"Hello"` `" "` 和 `"world"`: ```nix let concat2 = { a, b }: a + b; @@ -1307,17 +1364,14 @@ in ::: info 命名属性集 -与匿名函数的概念类似, -若一个属性集没有与名称绑定, -则称其为匿名属性集。 -反之,则称为命名属性集。 +与匿名函数的概念类似,若一个属性集没有与名称绑定, +则称其为匿名属性集。反之,则称为命名属性集。 ::: 此例的函数定义中,匿名属性集 `{ x, y }` 作为了参数。 -而命名属性集也可以作为参数, -此时往往需要结合属性访问。 +而命名属性集也可以作为参数,此时往往需要结合属性访问。 例如,上面的例子等价于:(求值结果不变) ``` From 63548477ab389f23fb9930cc99c61fd51d6743a2 Mon Sep 17 00:00:00 2001 From: clsty Date: Wed, 22 Oct 2025 20:13:52 +0800 Subject: [PATCH 11/30] Update QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index 34bd7026..ee3a0d29 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -174,8 +174,8 @@ let a = builtins.div 2 0; b = 3; in b 第二例: ```nix -{ # 这是一句注释,放在代码所在行的末尾。 - # 这也是一句注释,单独占了一行。 +{ # 这是一句注释,放在代码所在行的末尾。 +# 这也是一句注释,单独占了一行。 a = 1; # 这一行即使不缩进,也不影响代码本质。 b = 2; # c = 3; # 这里的代码被注释掉了,相当于不存在。 @@ -198,7 +198,7 @@ let a = builtins.div 2 0; b = 3; in b ### 名称和值 -我们可以使用 `=` 将名称分配给值,形成“名称 - 值”对。 +我们可以使用 `=` 将名称分配给值,形成“名称—值”对。 例如将名称 `foo` 分配给值 `123`: @@ -224,7 +224,7 @@ foo = 123 | 维度 | 命令式语言 | Nix(函数式) | |---|---|---| -| 底层模型 | 存储格(内存单元) | 无存储格,只有「名称-值」映射 | +| 底层模型 | 存储格(内存单元) | 无存储格,只有「名称—值」映射 | | 操作 | 赋值:随时把新值写回同一单元 | 绑定:一次性把名称贴到值,不可重写 | | 所谓“变量” | 存储格的别名 → 之后可反复擦写 | 数学意义上的变量 → 同一作用域内值固定 | | 文档用词 | variable = 可重写的存储格 | variable = 一次性绑定的名称(不会变) | @@ -263,12 +263,9 @@ foo = 123 语法说明: -- 属性集以 `{` `}` 为边界,其内部为多个“名称-值”对,且它们末尾必须添加 `;` 。 - -上述代码将 `foo` 的值定义为属性集 `{ a = 1; b = 2; }` ,因此可称之为属性集 `foo` 。 - -属性集 `foo` 中有两个属性: +- 属性集以 `{` `}` 为边界,其内部为多个“名称—值”对,且它们末尾必须添加 `;` 。 +属性集 `{ a = 1; b = 2; }` 中有两个属性: - 属性 `a`,其值为 `1` - 属性 `b`,其值为 `2` @@ -435,7 +432,7 @@ in ``` 注意语法细节: -- `let` 与 `in` 之间的“名称-值”对以 `;` 结尾; +- `let` 与 `in` 之间的“名称—值”对以 `;` 结尾; - `in` 之后**只有一个表达式**。注意,这只是语法形式上的要求,并不代表 `let` 绑定的用处很有限,因为表达式本身可以很复杂,常见的是嵌套属性集。作为基本示例,下面演示刚刚学到的列表: ```nix let @@ -620,7 +617,7 @@ in - 而 `x` `y` 则直接从同名变量获取值,这被称为“继承”(inherit)。 下面将要介绍的 `inherit` 语法, -则简化了这种继承所需的“名称-值”对。 +则简化了这种继承所需的“名称—值”对。 比如,刚才的例子可以这样写(求值结果不变): ```nix let @@ -642,7 +639,7 @@ in ::: warning `inherit` 的语法结构,例如上面的 `inherit x;`, -本质上仍然属于“名称-值”对,不属于表达式。 +本质上仍然属于“名称—值”对,不属于表达式。 ::: From 404e8c97bb60b3bdb2f53bf280e90737e40346a7 Mon Sep 17 00:00:00 2001 From: clsty Date: Wed, 22 Oct 2025 21:27:29 +0800 Subject: [PATCH 12/30] Update QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 41 ++++++++++++++++++----------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index ee3a0d29..fe6f464d 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -11,7 +11,7 @@ :::
仅面向本文维护者的说明,单击以切换折叠/展开 -本文在设计上是线性的,也即只需要读者具备一点点基础,就可以通过按顺序从头读到尾的方式完成本文的学习。因此,请留心说明顺序,例如讲 let 绑定时如果举了一个列表的例子,你需要确保前面已经正式介绍过列表。再如,讲 with 语法糖的时候同时用到 let 绑定和列表,那么这两个概念都需要在前面已经正式介绍过。否则,读者很可能会面对初次接触的语法或者概念而被卡住,这会严重影响学习效率甚至是完成率。若出于顺序安排的其他合理性原因,实在无法避开在说明中涉及陌生概念,可以提示读者相关部分不需要理解,后面会讲到。另外,常用的 callout 块中,info 显示为蓝色,适用于普通知识点;而 note 显示为灰色,适用于“就算不理解也没关系”的高阶或补充知识。至于 tip 和 warning,它们有可能涉及到知识,也可能不涉及,重点区别在于 tip 偏向于“实用建议/能帮助理解或加强记忆的提示”,而 warning 偏向于“能够避免损失的提示”(包括时间精力方面的)以及“注意避开误区的说明”。 +本文在设计上是线性的,也即只需要读者具备一点点基础,就可以通过按顺序从头读到尾的方式完成本文的学习。因此,请留心说明顺序,例如讲 let 绑定时如果举了一个列表的例子,你需要确保前面已经正式介绍过列表。再如,讲 with 语法糖的时候同时用到 let 绑定和列表,那么这两个概念都需要在前面已经正式介绍过。否则,读者很可能会面对初次接触的语法或者概念而被卡住,这会严重影响学习效率甚至是完成率。若出于顺序安排的其他合理性原因,实在无法避开在说明中涉及陌生概念,可以提示读者相关部分不需要理解,后面会讲到。另外,常用的 callout 块中,info 显示为蓝色,适用于普通知识点;而 note 显示为灰色,适用于“就算不理解也没关系”的高阶或补充知识。至于 tip 和 warning,它们有可能涉及到知识,也可能不涉及,重点区别在于 tip 偏向于“实用建议/能帮助理解或加强记忆的提示”,而 warning 偏向于“能够避免出现问题或损失的提示”(包括时间精力方面的)以及“注意避开误区的说明”。
Nix 作为语言,是一门简单的函数式语言,它被专门设计并用于 Nix 包管理器及相关生态 @@ -198,7 +198,7 @@ let a = builtins.div 2 0; b = 3; in b ### 名称和值 -我们可以使用 `=` 将名称分配给值,形成“名称—值”对。 +我们可以使用 `=` 将名称(name)分配给值(value),形成“名称—值”对。 例如将名称 `foo` 分配给值 `123`: @@ -410,14 +410,13 @@ rec { [ "apple" "orange" "banana" ] ``` 需要注意语法细节: -- 列表以 `[` `]` 为边界,其内部为多个元素,每个元素都是值(value)。 +- 列表以 `[` `]` 为边界,其内部为多个元素,每个元素都是值。 - 元素之间使用空格(或换行)分隔,各元素**不**以 `;` 结尾。 ## let 绑定与属性访问 -前面关于名称的使用是非常基本的,我们还需要更灵活的处理方法。 - -本节将会介绍另一种将名称分配给值的方法——**let 绑定**, -以及风格简洁的**属性访问**。 +前面关于名称的使用是非常基本的, +本节要介绍的**let 绑定**和**属性访问** +则提供了更灵活的处理方法。 ### `let` 绑定 有时我们希望在指定的范围内为值分配名称, @@ -869,7 +868,7 @@ in ::: warning 名称的值的数据类型 字符串插值语法支持的值必须为字符串类型, -或是可以转换为字符串的数据类型。例如: +例如: ```nix let @@ -1046,16 +1045,14 @@ in '' ``` -但如果我们希望在字符串中使用原始字符 `''`, -因为会与多行字符串原有的语义冲突, -不能直接写 `''`,而必须改用 `'''` 三个单引号。 - -也就是说, -在多行字符串中的 `'''` 三个单引号这样的组合, -实际输出的是原始字符串 `''`. + +::: note 拓展说明:连续多个单引号 -举个例子: +对于多个单引号来说,因为 `''` 本身被用来转义,输出它们的方法有些特殊: +- 若要在字符串中使用原始字符 `''`(2 个),可以用 `'''`(3 个)。 +- 若要在字符串中使用原始字符 `'''`(3 个),可以用 `''''`(4 个)。 +例如: ```nix let a = "1"; @@ -1069,6 +1066,18 @@ in "the value of a is:\n ''1''\n" ``` +而对于更多的单引号,转义机制较为复杂。 +- 一般地,当存在连续 $n$ 个单引号时($n\ge 3,n\in \mathbb Z$),令 $N=n\mod 3$, + - 若 $N\neq 2$ 则会转义出 $\frac{n-N}{3}\times 2+N=\frac{2n+N}{3}$ 个单引号。 + - 若 $N=2$ 则会报错: + ```plain + error: syntax error, unexpected end of file, expecting IND_STR or DOLLAR_CURLY or IND_STRING_CLOSE + ``` +- 反过来说,若需要在字符串中使用连续 $m$ 个单引号作为原始字符($m\ge 2,m\in \mathbb Z$),令 $M=m\mod 2$,则需要 $M+\frac{m-M}{2}\times 3=\frac{3m-M}{2}$ 个单引号来进行转义。 + + +::: + ## 函数 作为函数式编程(functional programming)的语言, From 6c72236e7df03af5a4e507fc48a54ac7862a949a Mon Sep 17 00:00:00 2001 From: clsty Date: Wed, 22 Oct 2025 21:34:02 +0800 Subject: [PATCH 13/30] Update QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index fe6f464d..7fbcd2f2 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1067,13 +1067,13 @@ in ``` 而对于更多的单引号,转义机制较为复杂。 -- 一般地,当存在连续 $n$ 个单引号时($n\ge 3,n\in \mathbb Z$),令 $N=n\mod 3$, +- 一般地,当存在连续 $n$ 个单引号时($n\ge 3,n\in \mathbb Z$),令 $N=n\bmod 3$, - 若 $N\neq 2$ 则会转义出 $\frac{n-N}{3}\times 2+N=\frac{2n+N}{3}$ 个单引号。 - 若 $N=2$ 则会报错: ```plain error: syntax error, unexpected end of file, expecting IND_STR or DOLLAR_CURLY or IND_STRING_CLOSE ``` -- 反过来说,若需要在字符串中使用连续 $m$ 个单引号作为原始字符($m\ge 2,m\in \mathbb Z$),令 $M=m\mod 2$,则需要 $M+\frac{m-M}{2}\times 3=\frac{3m-M}{2}$ 个单引号来进行转义。 +- 反过来说,若需要在字符串中使用连续 $m$ 个单引号作为原始字符($m\ge 2,m\in \mathbb Z$),令 $M=m\bmod 2$,则需要 $M+\frac{m-M}{2}\times 3=\frac{3m-M}{2}$ 个单引号来进行转义。 ::: From 9122b88b4ae00e545eb77436b4334483c27ea510 Mon Sep 17 00:00:00 2001 From: clsty Date: Wed, 22 Oct 2025 22:34:52 +0800 Subject: [PATCH 14/30] Update QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 77 ++++++++++++++++++----------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index 7fbcd2f2..ac9df836 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1310,7 +1310,8 @@ in ``` ### 作为参数的属性集:额外属性 -为了在调用函数时能传入额外属性,需要在属性集中添加一个占位符 `...`。 +前面已经提到,在调用函数时如果传入额外属性,会引发报错。 +但有时我们需要传入额外属性,此时就需要在属性集中添加一个占位符 `...`。 例如: ```nix @@ -1319,7 +1320,7 @@ let in { R1 = concat2 { a = "Hello "; b = "world"; }; - # 传入额外的 c 不会引发报错 + # 传入额外属性 c,这次不会引发报错 R2 = concat2 { a = "Hello "; b = "world"; c = "!"; }; } ``` @@ -1405,10 +1406,22 @@ in 柯里化(currying)指的是将一个 N 元函数转换为 N 个一元函数的嵌套序列的过程。 + +::: note 拓展说明:柯里的由来 +“柯里”是 curry 的音译(也可译作“卡瑞”“加里”等), +它得名自数理逻辑学家 Haskell Brooks Curry。 + +curry 还有其它音译, +但它们可能代表完全不同的其他含义, +例如咖喱、库里,等等。 + + +::: + ::: note 拓展说明:数学上的柯里化 -这里所说的一元函数不是普通的函数,在数学上对应的概念实际上是映射。 +上述一元函数并不是数学意义上的函数,在数学上对应的概念实际上是映射。 例如,三元函数 $F(x,y,z)=x+y+z$ 也即映射 $F: x,y,z\mapsto x+y+z$ 可以转换为三个一元映射的嵌套,分别是: @@ -1419,28 +1432,22 @@ in ::: -例如,函数 `{x,y}:x+y` 的柯里化形式如下: +例如,函数 `{ x , y } : 10 * x + y` 的柯里化形式如下: ```nix -x: (y: x + y) +x: (y: 10 * x + y) +# 虽然会降低可读性,下面的写法也是可以的: +# x: y: 10 * x + y ``` - -::: tip -虽然会降低可读性,下面的写法也是可以的: -```nix -x: y: x + y -``` - -::: - 虽然这个柯里化函数最终需要两个参数, 但它可以逐次接受这两个参数。 + 例如我们先给它其中一个参数作为 `x` 的值: ```nix let - f = x: (y: x + y); + f = x: (y: 10 * x + y); in f 1 ``` @@ -1450,28 +1457,29 @@ in ``` 也即 `f 1` 的值依然是函数,此函数实际上就是 ```nix -y: 1 + y; +y: 10 + y; ``` 我们可以将 `f 1` 绑定到名称 `g` 以保存起来, -以供后续使用,例如 `g 2`。 -其中 1 会作为参数 `x` 的值。 -而 2 会作为参数 `y` 的值。 +以供后续使用。 +例如 ```nix let - f = x: (y: x + y); + f = x: (y: 10 * x + y); in let + # 1 对应函数定义中的 x g = f 1; in + # 2 对应函数定义中的 y g 2 ``` -求值结果为 `3`。 +求值结果为 `12`。 也可以一次性接收两个参数(求值结果不变): ```nix let - f = x: (y: x + y); + f = x: (y: 10 * x + y); in f 1 2 ``` @@ -1521,12 +1529,13 @@ in ## 函数库 前面我们已经接触到了 `+`、`-`、`*`、`/` 等运算符号, -实际上它们都属于 Nix 语言中的内建操作符。 +实际上它们都属于 Nix 语言中的内建操作符(built-in operator)。 + 常用的内建操作符还有 `==` `&&` 等。 建议至少浏览一遍[内建操作符的文档页面](https://nix.dev/manual/nix/stable/language/operators.html), 以熟悉可用的功能。 -除了内建操作符之外,还有两个被广泛使用的函数库, +除了内建操作符之外,还有两个被广泛使用的函数库(function library), 它们加在一起被视为 Nix 语言的事实标准。 ### builtins @@ -1534,9 +1543,7 @@ builtins 即内建函数,也称为“原始操作” (primitive operations,简写为 primops)。 Nix 附带许多内建函数, -它们作为 Nix 语言解释器的一部分,用 C++ 实现。 - -Nix 手册列出了所有 [builtins](https://nix.dev/manual/nix/stable/language/builtins.html) 函数。 +均在 [Nix 手册](https://nix.dev/manual/nix/stable/language/builtins.html) 列出。 这些函数可以通过常量 `builtins` 访问,例如前面提到过的 `toString`: @@ -1548,6 +1555,17 @@ builtins.toString ``` + +::: note 拓展说明:primop 类型 +注意这里返回的结果不是 ``, +说明内建函数与普通的函数是有差别的。 + +实际上,普通的函数由 Nix 语言实现, +而这些内建函数则作为 Nix 语言解释器的一部分,由 C++ 实现。 + + +::: + ::: info import 函数 大多数内置函数只能通过 `builtins` 访问。 @@ -1566,7 +1584,8 @@ import ./foo.nix 求值,结果为 `3`。 被导入的 Nix 文件必须是 Nix 表达式, -这个表达式自然也可以是函数本身。 +这个表达式自然也可以是函数本身, +而函数是可以接受参数的。 例如,令 `foo.nix` 的文件内容为 `x: x + 1`, 有如下示例 @@ -1626,7 +1645,7 @@ pkgs.lib.strings.toUpper "Have a good day!" 因此使用 `` 也足够了。 然而,更复杂的情况下,要保证完全可重复的例子, -应该像下面这样使用固定(pin)版本的 Nixpkgs: +应当为 Nixpkgs 固定(pin)版本,例如: ```nix let From a369e8587e54241e8d06de35b21a40f221733e85 Mon Sep 17 00:00:00 2001 From: clsty Date: Wed, 22 Oct 2025 22:53:02 +0800 Subject: [PATCH 15/30] Update QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index ac9df836..e9f23084 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -702,6 +702,10 @@ in ### 路径的基本语法 +在 Nix 语言中, +路径的基本语法与 POSIX 的路径虽有共通之处, +但有细节上的差异,不注意的话很容易导致问题。 + 路径有绝对路径(absolute path) 和相对路径(relative path)两种, 它们都必须满足: From 52cf483495c4f62122f162953cd8dc9e7b3c6aad Mon Sep 17 00:00:00 2001 From: clsty Date: Thu, 23 Oct 2025 13:27:21 +0800 Subject: [PATCH 16/30] Minor improve QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index e9f23084..b0869e8d 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1138,7 +1138,7 @@ x: x + 1 函数是一种数据类型,自然可以将函数与名称绑定。 -在前面例子的基础上,我们将函数绑定到名称 `f`,并且将 2 作为其参数来调用: +沿续前一个例子,我们将函数 `x: x + 1` 绑定到名称 `f`,并且将 2 作为其参数来调用: ```nix let @@ -1285,10 +1285,13 @@ error: function 'concat2' called without required argument 'a' ### 作为参数的属性集:属性默认值 在属性后面加 `? ` ,会将此属性的默认值设为 ``。 -例如,我们来定义一个“问候”函数 `greet`: -- 它的功能是对 `object` 进行“问候”; -- 支持设定问候语 `greeting`,默认值为 `"Hello "`。 - - 设置方法是,在函数定义中参数里的 `greeting` 后面附加 `? "Hello, "`。 +在下面的例子中,我们来定义一个“问候”函数 `greet`。其功能是: +- 使用作为问候语的参数 `greeting`, +- 对作为问候对象的参数 `object` 进行“问候”。 + +我们可以将最常用的问候语(例如 `"Hello, "`)作为默认值, +这样在不需要特别设定问候语的情况下就可以减少输入参数,直接采用默认值。 +具体方法是,在函数定义中,为参数里的 `greeting` 后面附加 `? "Hello, "`。 测试例: ```nix From 73e97d390f0e652319f6a2de7d1377775a8c59e7 Mon Sep 17 00:00:00 2001 From: clsty Date: Thu, 23 Oct 2025 13:32:54 +0800 Subject: [PATCH 17/30] Minor improve QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index b0869e8d..2ed9699c 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1290,10 +1290,9 @@ error: function 'concat2' called without required argument 'a' - 对作为问候对象的参数 `object` 进行“问候”。 我们可以将最常用的问候语(例如 `"Hello, "`)作为默认值, -这样在不需要特别设定问候语的情况下就可以减少输入参数,直接采用默认值。 -具体方法是,在函数定义中,为参数里的 `greeting` 后面附加 `? "Hello, "`。 +这样就可以选择不传入此参数,而直接采用默认值。 -测试例: +实例如下,注意在函数定义中,我们在参数 `greeting` 后面附加了 `? "Hello, "`: ```nix let greet = { greeting ? "Hello, ", object }: greeting + object + "!"; From eddd43670f56b86871c8f1e28b69f1a8ab0f74d3 Mon Sep 17 00:00:00 2001 From: "Celestial.y" Date: Thu, 23 Oct 2025 15:08:17 +0800 Subject: [PATCH 18/30] Update src/tutorials/lang/QuickOverview.md Co-authored-by: Ryan Yin --- src/tutorials/lang/QuickOverview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index 2ed9699c..cd282aa2 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1084,7 +1084,7 @@ in ## 函数 -作为函数式编程(functional programming)的语言, +作为一门函数式编程语言(functional programming language), Nix 中函数的地位非常重要。 ### 函数的基本构成 From 17ed7e720d1aa38a2ec6b0b79f66f2289cdd9ffb Mon Sep 17 00:00:00 2001 From: "Celestial.y" Date: Thu, 23 Oct 2025 15:08:36 +0800 Subject: [PATCH 19/30] Update src/tutorials/lang/QuickOverview.md Co-authored-by: Ryan Yin --- src/tutorials/lang/QuickOverview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index cd282aa2..4519c715 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1096,7 +1096,7 @@ Nix 中函数的地位非常重要。 x: x + 1 ``` -在此例中,左边的 `x` 是参数,右边的 `x+1` 是函数体。 +在此例中,冒号左边的 `x` 是参数,右边的 `x+1` 是函数体。 ::: info 匿名函数与 λ From b29d53480db7306a906a3b7f04e18a3be594c2ad Mon Sep 17 00:00:00 2001 From: clsty Date: Thu, 23 Oct 2025 16:13:13 +0800 Subject: [PATCH 20/30] Improve QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index 4519c715..5a2185a9 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1574,8 +1574,8 @@ builtins.toString ::: info import 函数 -大多数内置函数只能通过 `builtins` 访问。 -一个显著的例外是 `import`,它可在顶层直接使用。 +有些内置函数只能通过 `builtins` 访问, +但另有一些内置函数,可直接在顶层使用,比如 `import`。 `import` 接受的参数是 Nix 文件的路径, 会对其进行文件求值并返回结果。 @@ -1611,8 +1611,10 @@ import ./foo.nix 4 这些函数是基于 Nix 语言实现的, 而不是像 `builtins` 那样本身作为语言的一部分而存在。 -这些函数通常通过 `pkgs.lib` 访问, -因为 Nixpkgs 的属性集通常约定命名为 `pkgs`。 +Nixpkgs 的属性集通常约定命名为 `pkgs`, +因此往往可以通过 `pkgs.lib` 访问这个属性集。 + +_(其实,当下直接使用 `lib` 而不是 `pkgs.lib` 的情况更常见,后面会提到。)_ 例如能够将小写转大写的 `pkgs.lib.strings.toUpper` 函数,示例: @@ -1645,25 +1647,6 @@ pkgs.lib.strings.toUpper "Have a good day!" ::: - -::: note 固定 Nixpkgs 的版本 -函数 `toUpper` 足够简单,使用不同版本的 Nixpkgs 一般不会有不同的结果, -因此使用 `` 也足够了。 - -然而,更复杂的情况下,要保证完全可重复的例子, -应当为 Nixpkgs 固定(pin)版本,例如: - -```nix -let - nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/archive/06278c77b5d162e62df170fec307e83f1812d94b.tar.gz"; - pkgs = import nixpkgs {}; -in -pkgs.lib.strings.toUpper "Have a good day!" -``` - - -::: - ::: tip 作为参数的 pkgs 和 lib From 7ffd743baa9a0939eb4bbe68334880f754e5a5d0 Mon Sep 17 00:00:00 2001 From: "Celestial.y" Date: Thu, 23 Oct 2025 17:45:58 +0800 Subject: [PATCH 21/30] Minor fix for QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index 5a2185a9..162bb9f2 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1611,8 +1611,8 @@ import ./foo.nix 4 这些函数是基于 Nix 语言实现的, 而不是像 `builtins` 那样本身作为语言的一部分而存在。 -Nixpkgs 的属性集通常约定命名为 `pkgs`, -因此往往可以通过 `pkgs.lib` 访问这个属性集。 +由于 Nixpkgs 的属性集通常约定命名为 `pkgs`, +因此往往可以通过 `pkgs.lib` 使用这些函数。 _(其实,当下直接使用 `lib` 而不是 `pkgs.lib` 的情况更常见,后面会提到。)_ From 5ff52cff4b69941872dba44115d02373fb51eb87 Mon Sep 17 00:00:00 2001 From: clsty Date: Fri, 24 Oct 2025 09:34:00 +0800 Subject: [PATCH 22/30] Rewrite currying --- src/tutorials/lang/QuickOverview.md | 152 ++++++++++++++++------------ 1 file changed, 90 insertions(+), 62 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index 162bb9f2..fa17e716 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1186,11 +1186,13 @@ in 作为对比,下面是一个标准的属性集的示例: ```nix +# 注意分号不是分隔而是后缀,这里出现了两次 { a = 1; b = 2; } ``` 再来一个列表的示例: ```nix +# 用空格分隔 [ a b ] ``` @@ -1408,96 +1410,108 @@ let in f { x = 1; y = 4; } ``` -### 柯里化函数 - -柯里化(currying)指的是将一个 N 元函数转换为 N 个一元函数的嵌套序列的过程。 - - -::: note 拓展说明:柯里的由来 -“柯里”是 curry 的音译(也可译作“卡瑞”“加里”等), -它得名自数理逻辑学家 Haskell Brooks Curry。 - -curry 还有其它音译, -但它们可能代表完全不同的其他含义, -例如咖喱、库里,等等。 - - -::: - - -::: note 拓展说明:数学上的柯里化 - -上述一元函数并不是数学意义上的函数,在数学上对应的概念实际上是映射。 -例如,三元函数 $F(x,y,z)=x+y+z$ 也即映射 $F: x,y,z\mapsto x+y+z$ -可以转换为三个一元映射的嵌套,分别是: -1. $F_1: x \mapsto ( y \mapsto ( z \mapsto x + y + z ))$ -2. $F_2: y \mapsto ( z \mapsto x + y + z )$ -3. $F_3: z \mapsto x + y + z$ - - -::: - -例如,函数 `{ x , y } : 10 * x + y` 的柯里化形式如下: +### 柯里化函数 +前面已经提到如何直接调用匿名函数, +现在考虑下面的表达式: ```nix -x: (y: 10 * x + y) -# 虽然会降低可读性,下面的写法也是可以的: -# x: y: 10 * x + y +( y : 1 * y ) 10 +# 结果为 10 ``` +再考虑表达式: +```nix +( y : 2 * y ) 10 +# 结果为 20 +``` +再考虑表达式: +```nix +( y : 3 * y ) 10 +# 结果为 30 +``` +可以看到,尽管参数始终为 10,表达式的结果会随着匿名函数的函数体内部这个乘数的变化(从 1、2 变到 3)而变化(从 10、20 变到 30)。本质上,这是**函数**本身在随着这个**乘数**的变化而变化。 -虽然这个柯里化函数最终需要两个参数, -但它可以逐次接受这两个参数。 - -例如我们先给它其中一个参数作为 `x` 的值: +这种变化关系自然也是一种函数关系。 +换句话说,设这个乘数为 `x`, +这形成了由 `x` 到 ` y : x * y` 的函数关系。 +这个函数关系本身,也可以用 Nix 的函数来实现: +```nix +x : ( y : x * y ) +# 虽然会降低可读性,也可以这样写: +# x : y : x * y +``` +这个函数接受一个参数, +我们将其命名为 `f` 并传入 10 来测试: ```nix let - f = x: (y: 10 * x + y); + f = x : ( y : x * y ); in - f 1 + f 10 ``` -求值,结果显示为: -```nix +求值,结果如下: +```plain ``` -也即 `f 1` 的值依然是函数,此函数实际上就是 +不出我们预料,`f 10` 是一个函数。 +实际上这个函数正是 ```nix -y: 10 + y; +y : 10 * y ``` - -我们可以将 `f 1` 绑定到名称 `g` 以保存起来, -以供后续使用。 -例如 +`f 10` 接受一个参数,我们传入 2 来测试: ```nix let - f = x: (y: 10 * x + y); + f = x : ( y : x * y ); in - let - # 1 对应函数定义中的 x - g = f 1; - in - # 2 对应函数定义中的 y - g 2 + f 10 2 ``` -求值结果为 `12`。 +求值结果为 20。 + +以上,通过两个一元函数的嵌套, +我们得以先传入一个参数,再传入另一个参数。 -也可以一次性接收两个参数(求值结果不变): +就最终的实际功能来说, +这个函数与普通的二元函数都能接受两个参数。 + +而之前,我们实现二元函数的方法是利用属性集, +比如 ```nix let - f = x: (y: 10 * x + y); + g = { x, y }: x * y; in - f 1 2 + g { x = 10; y = 2; } ``` +求值结果仍然为 20。 + +观察前面例子中 `g` 和 `f` 的函数定义, +它们的函数体中都含有 `x * y`, +只是接受参数的方式不同。 + +由 `g` 到 `f`, +相当于把**一个二元函数** `g` +改写为了函数 `f` 这样的**两个一元函数的嵌套序列**。 + +**一般地,将一个 $n$ 元函数改写为 $n$ 个一元函数的嵌套序列,这个过程就被称为柯里化(currying)。** + + +::: note 拓展说明:柯里的由来 +“柯里”是 curry 的音译(也可译作“卡瑞”“加里”等), +它得名自数理逻辑学家 Haskell Brooks Curry。 + +curry 还有其它音译, +但它们可能代表完全不同的其他含义, +例如咖喱、库里,等等。 + + +::: ::: note 柯里化与闭包 -前面提到, -> 我们可以将 `f 1` 绑定到名称 `g` 以保存起来, -> 以供后续使用,例如 `g 2`。 +在前面的例子中,`f 10` 这个函数保存了 `x = 10` 的这种状态。 + +**这种“保存了某种状态”的函数,被称为闭包(closure)。** -这种“保存了某种状态”的函数,被称为闭包(closure)。 利用闭包,有时可以避免重复传入参数。 之前,为了演示默认值,我们自定义了一个问候函数 `greet`: @@ -1532,6 +1546,20 @@ in ::: + +::: note 拓展说明:数学上的柯里化 + +柯里化中的“一元函数”并不是数学意义上的函数,在数学上对应的概念实际上是映射。 + +例如,三元函数 $F(x,y,z)=x+y+z$ 也即映射 $F: x,y,z\mapsto x+y+z$ +可以转换为三个一元映射的嵌套,分别是: +1. $F_1: x \mapsto ( y \mapsto ( z \mapsto x + y + z ))$ +2. $F_2: y \mapsto ( z \mapsto x + y + z )$ +3. $F_3: z \mapsto x + y + z$ + + +::: + ## 函数库 前面我们已经接触到了 `+`、`-`、`*`、`/` 等运算符号, From 62a1f7d9e30e88d0a6ec8a0ddbbec3a3b0e84794 Mon Sep 17 00:00:00 2001 From: clsty Date: Fri, 24 Oct 2025 09:51:40 +0800 Subject: [PATCH 23/30] Improve closure --- src/tutorials/lang/QuickOverview.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index fa17e716..1924aa45 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1508,9 +1508,12 @@ curry 还有其它音译, ::: note 柯里化与闭包 -在前面的例子中,`f 10` 这个函数保存了 `x = 10` 的这种状态。 +在前面的例子中, +函数 `f 10` 保存了 `x = 10` 的这种状态, +这种函数被称为闭包(closure)。 -**这种“保存了某种状态”的函数,被称为闭包(closure)。** +_(闭包的完整概念较为复杂, +有兴趣可自行了解,这里不再展开。)_ 利用闭包,有时可以避免重复传入参数。 From 166c4b9c8f89af4012ac0f5eec97c2c9d7258f03 Mon Sep 17 00:00:00 2001 From: clsty Date: Fri, 24 Oct 2025 10:00:21 +0800 Subject: [PATCH 24/30] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E4=BE=8B=E5=AD=90?= =?UTF-8?q?=E7=9A=84=E6=95=B0=E5=80=BC=EF=BC=8C=E4=B8=8E=E5=89=8D=E9=9D=A2?= =?UTF-8?q?=E5=BD=A2=E6=88=90=E9=80=92=E8=BF=9B=EF=BC=8C=E6=9B=B4=E6=98=93?= =?UTF-8?q?=E7=90=86=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tutorials/lang/QuickOverview.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index 1924aa45..ce0ce1aa 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1442,30 +1442,30 @@ x : ( y : x * y ) # x : y : x * y ``` 这个函数接受一个参数, -我们将其命名为 `f` 并传入 10 来测试: +我们将其命名为 `f` 并传入 4 来测试: ```nix let f = x : ( y : x * y ); in - f 10 + f 4 ``` 求值,结果如下: ```plain ``` -不出我们预料,`f 10` 是一个函数。 +不出我们预料,`f 4` 是一个函数。 实际上这个函数正是 ```nix -y : 10 * y +y : 4 * y ``` -`f 10` 接受一个参数,我们传入 2 来测试: +`f 4` 接受一个参数,我们传入 10 来测试: ```nix let f = x : ( y : x * y ); in - f 10 2 + f 4 10 ``` -求值结果为 20。 +求值结果为 40。 以上,通过两个一元函数的嵌套, 我们得以先传入一个参数,再传入另一个参数。 @@ -1479,9 +1479,9 @@ in let g = { x, y }: x * y; in - g { x = 10; y = 2; } + g { x = 4; y = 10; } ``` -求值结果仍然为 20。 +求值结果仍然为 40。 观察前面例子中 `g` 和 `f` 的函数定义, 它们的函数体中都含有 `x * y`, @@ -1509,7 +1509,7 @@ curry 还有其它音译, ::: note 柯里化与闭包 在前面的例子中, -函数 `f 10` 保存了 `x = 10` 的这种状态, +函数 `f 4` 保存了 `x = 4` 的这种状态, 这种函数被称为闭包(closure)。 _(闭包的完整概念较为复杂, From 6bb3c5519bc6714084994c1e6ba352f874fd1f78 Mon Sep 17 00:00:00 2001 From: clsty Date: Fri, 24 Oct 2025 10:02:40 +0800 Subject: [PATCH 25/30] Minor improve QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index ce0ce1aa..1ec4dff8 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1471,9 +1471,8 @@ in 我们得以先传入一个参数,再传入另一个参数。 就最终的实际功能来说, -这个函数与普通的二元函数都能接受两个参数。 - -而之前,我们实现二元函数的方法是利用属性集, +这个函数与普通的二元函数都能接受两个参数; +只不过,之前我们实现二元函数的方法是利用属性集, 比如 ```nix let From 2ab4c17a012392aae3bfd209e8fc161a6db37cb3 Mon Sep 17 00:00:00 2001 From: clsty Date: Fri, 24 Oct 2025 10:03:59 +0800 Subject: [PATCH 26/30] Tweak callout --- src/tutorials/lang/QuickOverview.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index 1ec4dff8..a3b87722 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1493,7 +1493,7 @@ in **一般地,将一个 $n$ 元函数改写为 $n$ 个一元函数的嵌套序列,这个过程就被称为柯里化(currying)。** -::: note 拓展说明:柯里的由来 +::: tip 柯里的由来 “柯里”是 curry 的音译(也可译作“卡瑞”“加里”等), 它得名自数理逻辑学家 Haskell Brooks Curry。 @@ -1505,7 +1505,7 @@ curry 还有其它音译, ::: -::: note 柯里化与闭包 +::: info 柯里化与闭包 在前面的例子中, 函数 `f 4` 保存了 `x = 4` 的这种状态, From f2f541073a8b28eb381984d66d5cc6db2b270bb7 Mon Sep 17 00:00:00 2001 From: clsty Date: Fri, 24 Oct 2025 10:25:08 +0800 Subject: [PATCH 27/30] Improve QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index a3b87722..42ce868b 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1505,7 +1505,7 @@ curry 还有其它音译, ::: -::: info 柯里化与闭包 +::: info 柯里化:闭包与嵌套 在前面的例子中, 函数 `f 4` 保存了 `x = 4` 的这种状态, @@ -1514,7 +1514,9 @@ curry 还有其它音译, _(闭包的完整概念较为复杂, 有兴趣可自行了解,这里不再展开。)_ -利用闭包,有时可以避免重复传入参数。 +实际上,柯里化就是通过闭包与嵌套(或者说递归)来实现的。 + +闭包,有时可以避免重复传入参数。 之前,为了演示默认值,我们自定义了一个问候函数 `greet`: ```nix @@ -1527,7 +1529,7 @@ in R3 = greet { greeting = "Welcome, "; object = "my friend"; } ; } ``` -这里我们改用柯里化结合闭包的方法来实现, +这里我们将 `greet` 函数柯里化,利用闭包来实现, 甚至可以更加简洁(求值结果与之前例子相同): ```nix let From 1cc9774820f1c3ed036a479f7a1b6e86a07a0f8f Mon Sep 17 00:00:00 2001 From: clsty Date: Fri, 24 Oct 2025 10:32:15 +0800 Subject: [PATCH 28/30] Improve QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index 42ce868b..ead41a00 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1511,8 +1511,12 @@ curry 还有其它音译, 函数 `f 4` 保存了 `x = 4` 的这种状态, 这种函数被称为闭包(closure)。 -_(闭包的完整概念较为复杂, -有兴趣可自行了解,这里不再展开。)_ +_(支持闭包机制的语言很多, +并且尤其在 Javascript 等采用动态变量的语言中, +闭包的一个重要作用就是将捕捉闭包时外部变量的状态保存下来; +但本文的例子中,闭包则是保存传入参数的值,不涉及外部变量。 +受篇幅限制且为了避免理解困难,这里不介绍闭包的完整概念, +感兴趣可自行了解。)_ 实际上,柯里化就是通过闭包与嵌套(或者说递归)来实现的。 From b4b7829fec0e0e58d107abab51d40fa66e7653b4 Mon Sep 17 00:00:00 2001 From: clsty Date: Fri, 24 Oct 2025 10:45:18 +0800 Subject: [PATCH 29/30] Tweak callout --- src/tutorials/lang/QuickOverview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index ead41a00..a52b934c 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1684,7 +1684,7 @@ pkgs.lib.strings.toUpper "Have a good day!" ::: -::: tip 作为参数的 pkgs 和 lib +::: info Nix 生态中 pkgs、pkgs.lib 和 lib 的约定俗成 `pkgs` 常被作为参数传递给函数。 按约定,可以假设它指的是 Nixpkgs 的属性集, From 6746acc256e5f798c7a38a57d1e8eace8b8215e4 Mon Sep 17 00:00:00 2001 From: Ryan Yin Date: Fri, 24 Oct 2025 10:51:03 +0800 Subject: [PATCH 30/30] Update src/tutorials/lang/QuickOverview.md --- src/tutorials/lang/QuickOverview.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tutorials/lang/QuickOverview.md b/src/tutorials/lang/QuickOverview.md index a52b934c..24c739f2 100644 --- a/src/tutorials/lang/QuickOverview.md +++ b/src/tutorials/lang/QuickOverview.md @@ -1611,7 +1611,7 @@ builtins.toString ::: info import 函数 有些内置函数只能通过 `builtins` 访问, -但另有一些内置函数,可直接在顶层使用,比如 `import`。 +但另有一些内置函数,可直接在顶层使用,比如 `import`, `toString`, `map`。 `import` 接受的参数是 Nix 文件的路径, 会对其进行文件求值并返回结果。