进阶手册
Table of Contents
数据类型
原始数据类型
字符串
单行字符串
与大多数编程语言的字符串一致,使用双引号闭合:
"Hello, nix!\n"
多行字符串
多行字符串是通过 两个单引号 闭合的
'' This is the first line. This is the second line. This is the third line. ''
多行字符串往往会带有不同程度的缩进,会被进一步处理。也就是说对于以下字符串:
'' This is the first line. This is the second line. This is the third line. ''
会被“智能缩进”处理,每一行都被前移了最小缩进数个字符。处理后的结果是:
This is the first line. This is the second line. This is the third line.
同时,假如第一行被占空了,也会对其进行处理:
'' There's a row of spaces up there. ''
处理后的数据是:
There's a row of spaces up there.
Nix 只会将自动处理后的字符串当作输入,而不是原始字符串 raw string
URL
为了书写简便, RFC 2396 规定了对于 URI 可以 不使用 引号 闭合:
UriWithoutQuotes = http://example.org/foo.tar.bz2 UriWithQuotes = "http://example.org/foo.tar.bz2"
两者是等价的
数字
数字被分为
浮点型
比如 .114514
整型
比如 2233
数字是类型兼容的:纯整数运算总是返回整数,而任何涉及至少一个浮点数的运算都会返回一个浮点数
路径
路径 至少 需要包含一个斜杠才能被识别为路径:
/foo/bar/bla.nix ~/foo/bar.nix ../foo/bar/qux.nix
除了某些尖括号路径(比如 <nixpkgs> )外,其他路径都支持 字符串插值
"${./foo.txt}"
布尔
true 或 false
空
字面意义上的 null
列表
列表使用 中括号 闭合, 空格 分隔元素,一个列表允许包含 不同类型 的值:
[ 123 ./foo.nix "abc" ( x: x+1) ]
此处如果不给 f { x = y; } 打上括号,就会把函数也当作此列表的值
属性集
属性集是用 大括号 括起来的 名称与值对 (称为属性)的集合:
- 属性名:可以是 标识符 或 字符串
- 标识符必须以字母或下划线开头,可以包含字母、数字、 下划线 、 单引号 ' 或 连接符 -
{ x = 123; text = "Hello"; y = { bla = 456; }; }
使用 . 访问各个属性:
{ a = "Foo"; b = "Bar"; }.a #"Foo"
使用 or 关键字,可以在属性选择中提供 默认值 :
{ a = "Foo"; b = "Bar"; }.c or "Xyzzy" # ":Xyzzy"
因为属性 c 不在属性集里,故输出默认值
也可以用字符串去访问属性:
{ "$!@#?" = 123; }."$!@#?" # 123
属性名也支持字符串插值:
let bar = "foo"; in { foo = 123; }.${bar} # 123 let bar = "foo"; in { ${bar} = 123; }.foo # 123
两者的值都是123
在特殊情况下,如果集合声明中的属性名求值为 null(这是错误的,因为 null 不能被强制为字符串),那么该属性将不会被添加到集合中:
{ ${if foo then "bar" else null} = true; }
如果 foo 的值为 false,则其值为 {}
如果一个集合的 __functor 属性的值是 可调用的 (即它本身是一个函数或是其中一个集合的 __functor 属性的值是可调用的),那么它就可以 像函数一样被应用 ,首先传入的是集合本身,例如:
let add = { __functor = self: x: x + self.x; }; # 定义 属性集 add 的 __functor 属性 inc = add // { x = 1; }; # inc属性集相当于把 { x = 1; } 属性集 传入 add,这样 inc 属性集里的 self.x = 1 in inc 2 # 3 这里传入的2 对应于 inc 里的x ,相当 x + self.x = 2 + 1 = 3
这可用于为函数附加元数据,而调用者无需对其进行特殊处理 也可用于实现面向对象编程等形式
数据构造
递归属性集
let绑定
继承属性
函数
条件判断
Nix 的条件判断表达式的结构类似于这样:
if <exprCond> then <exprThen> else <exprElse>
其中 exprCond 的求值结果必须为 布尔值 true 或 false
- 当 exprCond 求值为 true 时,上述条件表达式的结果为 exprThen
- 否则结果为 exprElse
定义时的使用例子如下:
# 利用 if-then-else 表达式实现函数: myFunction = x: if x > 0 then "Positive" else "Non-positive" myFunction 0 # Non-positive myFunction 1 # Positive
利用 if-then-else 表达式定义变量:
no = 7 gt0 = if no > 0 then "yes" else "no" # gt0 变量值为 "yes" gt0 # => "yes"
亦可嵌套使用
# 利用 if-then-else 表达式实现函数: myPlan = target: if target == "fitness" then "I'm going swimming." else if target == "purchase" then "I'm going shopping." else if target == "learning" then "I'm going to read a book." else "I'm not going anywhere." myPlan "fitness" # "I'm going swimming." myPlan null # "I'm not going anywhere." # 利用 if-then-else 表达式定义变量: x = null text = if x == "a" then "hello" else if x == "b" then "hi" else if x == "c" then "ciao" else "x is invalid" # "x is invalid"
循环控制
Nix 是一种函数式编程语言,每一段 Nix 程序都是一个完整的表达式
这与流行的 Java, Python 等命令式编程语言有很大不同,命令式编程语言的程序往往是一段包含变量声明、赋值、跳转等指令的指令序列。 这里不展开说明函数式编程与命令式编程的区别
一言蔽之,Nix 语言中 没有 while/for 循 环这类控制结构,取而代之的是以 递归 为基础实现的一系列 高阶函数
Nix 的标准库提供了一系列此类高阶函数用于对字符串、列表等可迭代对象进行操作
内建函数与 nixpkgs.lib 函数库
Nix 语言自身提供了一些精简的内建函数,可通过 builtins.xxx 这种方式使用
官方文档参见 https://nix.dev/manual/nix/2.22/language/builtins#built-in-functions
此外,官方函数库 nixpkgs.lib 提供了比 builtins 更丰富的功能
社区提供的 https://noogle.dev/ 可以相当容易地搜索上述两类函数,推荐一用
下面针对 builtins 与 nixpkgs.lib 的用法各举一例:
# 通过 nixpkgs.lib.range,生成指定范围元素的列表 nixpkgs = import <nixpkgs> {} alist = nixpkgs.lib.range 4 7 alist # => [ 4 5 6 7 ] # 通过内建函数 filter,遍历列表中的元素并过滤 builtins.filter (item: item == "hello") [ "hello" "world" ] # => [ "hello" ]
递归函数
如前所述,Nix 语言作为一种函数式编程语言,它采用递归取代了命令式编程语言中的循环等控制结构
这里举个简单的例子来说明 Nix 语言中如何实现一个递归函数:
let recurse = n: if n <= 0 then [] else recurse (n - 1) ++ [n]; in recurse 5 # [1 2 3 4 5]
️注意:对新手而言,nixpkgs.lib 中的函数基本够用了 建议优先查找使用内建函数和 nixpkgs.lib 中的函数,如果找不到自己想要的功能再考虑自行实现