Язык Nix
Идентификаторы
Идентификаторы в 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 распознаётся как идентификатор, а не как вычитание.
Строки
Строки заключаются в двойные кавычки (") или в пару одинарных кавычек ('').
nix-repl> "foo"
"foo"
nix-repl> ''foo''
"foo"
В других языках, например, в 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. Ключи могут быть только строками. Если ключи являются правильными идентификаторами, их можно записывать без кавычек.
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; }
Такая возможность полезна при описании пакетов, которые часто имеют рекурсивную природу.
Набор аргументов - набор ключей атрибутов использующийся в качестве аргументов вызова функции.
Одна из самых мощных возможностей Nix — сопоставление с образцом параметра, который имеет тип набор атрибутов. Напишем альтернативную версию mul = a: b: a*b сначала используя набор аргументов, а затем — сопоставление с образцом.
nix-repl> mul = s: s.a*s.b
nix-repl> mul { a = 3; b = 4; }
12
nix-repl> mul = { a, b }: a*b
nix-repl> mul { a = 3; b = 4; }
12
В первом случае мы определили функцию, которая принимает один параметр-набор. Затем мы взяли атрибуты a и b из этого набора. Заметьте, как элегантно выглядит запись вызова без скобок. В других языках нам пришлось бы написать mul({ a=3; b=4; }).
Во втором случае мы определили набор аргументов. Это похоже на определение набора атрибутов, только без значений. Мы требуем, чтобы переданный набор содержал ключи a и b . Затем мы можем использовать эти a и b непосредственно в теле функции.
nix-repl> mul = { a, b }: a*b
nix-repl> mul { a = 3; b = 4; c = 6; }
error: anonymous function at (string):1:2 called with unexpected argument `c', at (string):1:1
nix-repl> mul { a = 3; }
error: anonymous function at (string):1:2 called without required argument `b', at (string):1:1
Функция принимает набор ровно с теми атрибутами, которые были указаны при её определении.
Атрибуты по умолчанию и вариативные атрибуты
В наборе аргументов можно указывать значения атрибутов умолчанию:
nix-repl> mul = { a, b ? 2 }: a*b
nix-repl> mul { a = 3; }
6
nix-repl> mul { a = 3; b = 4; }
12
Функция может принимать больше атрибутов, чем ей нужно. Такие атрибуты называются вариативными:
nix-repl> mul = { a, b, ... }: a*b
nix-repl> mul { a = 3; b = 4; c = 2; }
Здесь вы не можете получить доступ к атрибуту c. Но вы сможете обратиться к любым атрибутам, дав имя всему набору с помощью @-образца:
nix-repl> mul = s@{ a, b, ... }: a*b*s.c
nix-repl> mul { a = 3; b = 4; c = 2; }
24
Написав name@ перед образцом, вы даёте имя name всему набору атрибутов.
Преимущества использования наборов аргументов:
- Из-за того, что аргументы именованы, вы не должны запоминать их порядок. В качестве аргументов можно передать набор, что создаёт совершенно новый уровень гибкости и удобства.
Недостатки:
- Частичное применение не работает с набором аргументов. Вы должны определить набор атрибутов целиком, нельзя определить только его часть.
Наборы атрибутов похожи на **kwargs из языка Python.
Выражение 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 не требуется, поэтому ошибка деления на ноль не возникает — выражение просто не вычисляется. Из-за этой особенности языка, пакеты можно определять по мере необходимости, при этом доступ к ним осуществляется очень быстро.
Выражение inherit
Выражение inherit используется чтобы сопоставить названия атрибутов с их значениями в наборе атрибутов.
Выражение inherit gcc coretutils; соответствует набору выражений gcc = gcc; coreutils = coreutils, выражение inherit (pkgs) gcc coreutils; - gcc = pkgs.gcc; coreutils = pkgs.coreutils;
Этот синтаксис имеет смысл только внутри наборов. Это удобный способ избежать повторения одного и того же имени и для атрибута, и для значения в области видимости.
Оператор //
Оператор // принимает на вход два набора. Результатом является их объединение. В случае конфликта имён атрибутов, используется значение из правого набора.
nix-repl> { a = "b"; } // { c = "d"; }
{ a = "b"; c = "d"; }
nix-repl> { a = "b"; } // { a = "c"; }
{ a = "c"; }
No comments to display
No comments to display