Добро пожаловать на четвёртую пилюлю Nix.
В предыдущей статье мы познакомились с окружениями Nix.
Мы устанавливали программы из под пользователя, управляли профилем, переключались между поколениями и писали запросы к хранилищу Nix.
Всё это — основы системного администрирования с помощью Nix.
Для написания выражений, которые конструируют порождения используется Язык Nix.
Для построения порождений из выражений используется утилита nix-build.
Даже если вы не программист, а системный администратор, вам нужно освоить Nix, чтобы настраивать систему.
Используя Nix в вашей работе, в качестве бонуса вы получаете все те возможности, о которых я рассказывал в прошлых статьях.
Синтаксис Nix достаточно непривычный, так что, разбираясь с примерами, вы можете решить, что здесь замешана какая-то магия.
На самом деле речь в основном идёт об удобном написании служебных функций.
Этот синтаксис прекрасно подходит для описания пакетов, так что изучение языка окупится при написании пакетных выражений.
?
В Nix всё является выражением, там нет операторов. Обычное дело в функциональных языках.
?
Значения в Nix неизменяемые (иммутабельные).
Типы значений
В Nix 2.0 есть команда nix repl
.
Это простая утилита командной строки, которая позволяет экспериментировать с языком Nix.
Напомню, что Nix — это не только набор утилит для работы с порождениями, но и чистый ленивый функциональный язык.
Синтаксис nix repl
немного отличается от синтаксиса Nix, когда речь идёт о присваивании переменных (ведь в функциональных языках не бывает присваиваний).
Просто помните об этом, и вы не запутаетесь.
nix repl
поможет нам быстрее вкатиться в язык, поскольку он оставляет простые вещи простыми.
Запустите nix repl
. Прежде всего, Nix поддерживает основные арифметические операции: +
, -
, *
и /
.
(Чтобы выйти из nix repl
, введите команду :q
.
Команда :?
выводит справку.)
nix-repl> 1+3
4
nix-repl> 7-4
3
nix-repl> 3*2
6
Попытка выполнить деление в Nix может вас удивить.
nix-repl> 6/3
/home/nix/6/3
Что произошло?
Вспомним, что Nix не является языком общего назначения, это предметно-ориентированный язык для написания пакетов.
Деление чисел — не самая нужная операция при написании пакетных выражений.
Для Nix 6/3
— это путь, построенный относительно текущего каталога.
Чтобы заставить Nix выполнить деление, добавьте пробел после /
, либо вызовите встроенную функцию builtins.div
.
nix-repl> 6/ 3
2
nix-repl> builtins.div 6 3
2
Другие операторы — это ||
, &&
и |
для булевых значений, и операторы сравнения, такие как !=
, ==
, <
, >
, <=
, >=
. В Nix <
, >
, <=
and >=
используются нечасто.
Есть и другие операторы, с которыми мы познакомимся в этом цикле.
В Nix есть простые типы: целые числа, числа с плавающей запятой, строки, пути, булевы значения и null.
Затем, есть списки, множества и функции.
Этих типов хватает, чтобы собрать целую операционную систему.
Nix является сильно типизированным, но не статически типизированным языком.
То есть, вы не можете смешивать строки и целые числа без предварительного преобразования типа, при этом все проверки типов происходят на этапе выполнения программы.
Мы выяснили, что выражения считаются путями, если не вставить пробел после символа деления.
Поэтому, чтобы указать текущий каталог, пишите ./.
Кроме того, Nix умеет распознавать url'ы.
Не все url'ы или пути могут быть распознаны обычным образом.
Если возникает ошибка распознавания, вы всегда можете вернуться к обычным строкам.
Строковые url'ы и пути также обеспечивают дополнительную безопасность.
Идентификаторы
Идентификаторы в Nix такие же, как в других языках, за исключением того, что могут содержать дефис (-
).
Удобно, имея много пакетов, писать дефис в имени.
Пример:
nix-repl> a-b
error: undefined variable `a-b' at (string):1:1
nix-repl> a - b
error: undefined variable `a' at (string):1:1
Как видите, a-b
распознаётся как идентификатор, а не как вычитание.
Строки
Важно разобраться с синтаксисом строк.
Строки заключаются в двойные кавычки ("
) или в пару одиночных кавычек (''
).
В других языках, например, в Python, можно заключать строки в одиночные кавычки ('foo'
), но не в Nix.
Можно интерполировать выражения Nix внутри строк с помощью синтаксиса ${...}
. Если вы писали на других языках, то можете по привычке написать $foo
или {$foo}
, но такой синтаксис работать не будет.
nix-repl> foo = "strval"
nix-repl> "$foo"
"$foo"
nix-repl> "${foo}"
"strval"
nix-repl> "${2+3}"
error: cannot coerce an integer to a string, at (string):1:2
Помните, что присваивание foo = "strval"
— это специальный синтаксис, доступный только в nix repl
и недоступный в обычном языке.
Как я уже говорил, нельзя смешивать целые числа и строки, нужно в явном виде привести тип.
Мы вернёмся к приведению позже, как и к вызову функций.
Заключая строку в пару одиночных кавычек, можно писать двойные кавычки внутри без необходимости их экранировать.
nix-repl> ''test " test''
"test \" test"
nix-repl> ''${foo}''
"strval"
Экранирование ${...}
в строках с двойными кавычками делается с помощью обратной косой линии (бекслеша), а в строках с парой одиночных кавычек — с помощью ''
:
nix-repl> "\${foo}"
"${foo}"
nix-repl> ''test ''${foo} test''
"test ${foo} test"
Списки
Списки — это последовательность выражений, разделённая пробелом (не запятой):
nix-repl> [ 2 "foo" true (2+3) ]
[ 2 "foo" true 5 ]
Списки, как и всё в Nix, неизменяемы (иммутабельны).
Добавление или удаление элементов в списке возможно, но возвращает новый список.
Наборы атрибутов
Набор атрибутов — это ассоциативный массив со строковыми ключами и значениями Nix.
(В оригинале автор использует термин set, что обычно переводится на русский, как множество. В нашем случае термин множество может ввести в заблуждение, поскольку set содержит не отдельные элементы, а пары ключ-значение. Такую структуру по-русски называют словарём или ассоциативным массивом. В конечном итоге я остановился на слове набор, которое всё-таки не совсем множество — примечание переводчика.)
Ключи могут быть только строками.
Записывая набор атрибутов, ключи можно записывать без кавычек, если они являются идентификаторами.
nix-repl> s = { foo = "bar"; a-b = "baz"; "123" = "num"; }
nix-repl> s
{ "123" = "num"; a-b = "baz"; foo = "bar"; }
Набор атрибутов можно перепутать с набором аргументов при вызове функций, но это разные вещи.
Чтобы обратиться к элементу в наборе атрибутов:
nix-repl> s.a-b
"baz"
nix-repl> s."123"
"num"
Чтобы обратиться к ключу, который не является правильным идентификатором, используйте кавычки.
Внутри набора нельзя ссылаться на другие элементы или на сам набор:
nix-repl> { a = 3; b = a+4; }
error: undefined variable `a' at (string):1:10
Это можно делать с помощью рекурсивных наборов:
nix-repl> rec { a = 3; b = a+4; }
{ a = 3; b = 7; }
Это полезная возможность при описании пакетов, которые часто имеют рекурсивную природу.
Выражения 'if'
Это всё ещё выражения, не операторы.
nix-repl> a = 3
nix-repl> b = 4
nix-repl> if a > b then "yes" else "no"
"no"
Нельзя записывать только ветку then
без ветки else
, потому что у выражения при любом раскладе должен быть результат.
Выражения 'let'
Выражения 'let' используются, чтобы определить локальные переменные для других (внутренних) выражений.
nix-repl> let a = "foo"; in a
"foo"
Синтаксис такой: сначала определяем переменные, затем пишем ключевое слово in
, затем выражение, в котором можно ссылаться на определённые переменные.
Значением всего выражения let
будет значение выражения после in
.
nix-repl> let a = 3; b = 4; in a + b
7
Попробуем записать два выражения let
, одно внутри другого:
nix-repl> let a = 3; in let b = 4; in a + b
7
Помните, что с помощью let
нельзя присвоить переменной другое значение.
Однако, можно перекрывать внешние переменные:
nix-repl> let a = 3; a = 8; in a
error: attribute `a' at (string):1:12 already defined at (string):1:5
nix-repl> let a = 3; in let a = 8; in a
8
Нельзя ссылаться на переменные в выражении let
снаружи:
nix-repl> let a = (let c = 3; in c); in c
error: undefined variable `c' at (string):1:31
Можно ссылаться на переменные в выражении let
при определении другие переменные, как в рекурсивных наборах.
nix-repl> let a = 4; b = a + 5; in b
9
Общее правило: избегайте ситуаций, когда вам надо сослаться на внешнюю переменную, но переменная с таким же именем есть в текущем выражении let
.
Это же правило действует в отношении рекурсивных наборов.
Выражения 'with'
Это непривычный тип выражений — его нечасто можно встретить в других языках.
Можно считать, что это расширенная версия оператора using
из C++, или from module import*
из Python.
Конструкция with
включает атрибуты набора в область видимости.
nix-repl> longName = { a = 3; b = 4; }
nix-repl> longName.a + longName.b
7
nix-repl> with longName; a + b
7
Оператор получает набор атрибутов и включает их в область видимости вложенного выражения.
Естественно, в область видимости попадают только корректные идентификаторы.
Переменные из внешней области видимости с совпадающими именами не перекрываются.
Если надо, вы всегда можете обратиться к атрибуту через набор:
nix-repl> let a = 10; in with longName; a + b
14
nix-repl> let a = 10; in with longName; longName.a + b
7
Ленивые вычисления
Nix вычисляет выражения только тогда, когда ему нужен результат.
Эта особенность языка активно используется при описании пакетов.
nix-repl> let a = builtins.div 4 0; b = 6; in b
6
Здесь значение a
не требуется, поэтому ошибка деления на ноль не возникает — выражение просто не вычисляется.
Из-за этой особенности языка, пакеты можно определять по мене необходимости, но в случае необходимости получать доступ к нужным пакетам можно очень быстро.
В следующей пилюле
...поговорим о функциях и импорте.
В этой пилюле я старался избегать функций, иначе пост стал бы слишком большим.