Содержание


Функциональный менеджер пакетов Nix

Часть 2. Специализированный язык

Comments

Серия контента:

Этот контент является частью # из серии # статей: Функциональный менеджер пакетов Nix

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Функциональный менеджер пакетов Nix

Следите за выходом новых статей этой серии.

1. Введение

Язык Nix-выражений является чистым, ленивым, функциональным языком. Чистота означает, что операции в языке не производят побочных эффектов (например, в языке нет операции присваивания). Ленивость языка проявляется в том, что аргументы функции вычисляются только в том случае, если понадобится их результат. Функция в языке является полноправным значением, которое может быть передано другим функциям в качестве аргумента или возвращено в качестве результата. Язык предназначен для описания окружения сборки пакета, процесса сборки, зависимостей пакета и компоновки пакетов в общую коллекцию.

Протестировать выражения языка можно с помощью утилиты nix-instantiate. Она предназначена для импорта сборок в хранилище Nix из выражений. В режиме --eval-only утилита вычисляет переданное ей выражение и выводит результат:

$ nix-instantiate --eval-only -
"Hello, World!"
^D
Str("Hello, World!",[])

После ввода выражения нажимаем Ctrl+D. Результат возвращается в абстрактном синтаксическом представлении.

2. Типы данных

2.1. Строки

Строки можно записать тремя способами.

При обычном способе содержимое строки заключается в двойные кавычки. Содержимое может включать в себя несколько строк. Специальные символы ", \ и последовательность символов ${ должны экранироваться обратным слешем. Символы новой строки, перевода каретки и табуляции записываются как \n, \r и \t, соответственно.

В строку можно вставлять результат вычисления выражения Nix с помощью конструкции ${ ... }, называемой квазицитированием. Результат должен быть строкой, путем или объектом хранилища. Например, вместо такой записи:

"System: " + builtins.currentSystem

можно записать:

"System: ${builtins.currentSystem}"

Получится одно и то же:

"System: x86_64-linux"

(функция builtins.currentSystem возвращает идентификатор платформы, на которой производится вычисление).

Такая строка:

"-system-zlib -system-libpng -system-libjpeg 
	${if threadSupport then "-thread" else "-no-thread"}"

в зависимости от значения параметра threadSupport будет содержать либо "thread", либо "-no-thread".

Строки и выражения Nix могут быть взаимно вложенными.

Другой тип строки может содержать отступы. Такие строки обрамляются двойными символами одинарной кавычки:

''
  This is the first line.
  This is the second line.
    This is the third line.
''

Получится:

"This is the first line.\nThis is the second line.\n  This is the third line.\n"

В каждой строке слева убирается одинаковое количество пробелов, равное минимальному отступу из всех строк.

В таких строках последовательность ${ экранируется как ''${, последовательность двух одинарных кавычек – еще одной одинарной кавычкой '''. Символы новой строки, перевода каретки и табуляции записываются как ''\n, ''\r, ''\t.

Последний тип строк – это адреса URI, определенные в приложении B RFC 2396. Они записываются без кавычек, как есть. Например, строка "http://example.org/foo.tar.bz2" может быть записана просто как http://example.org/foo.tar.bz2.

2.2. Числа

Целые числа записываются как 123.

2.3. Пути

Пути записываются как /bin/sh, ./builder.sh. Путь должен содержать хотя бы один слеш вначале, чтобы он определился как путь. Если путь относительный, то на этапе вычисления выражения он преобразуется в абсолютный.

2.4. Булевы значения

Принимают значения true либо false.

2.5. Списки

Списки состоят из элементов, разделенных пробелами и заключенных в квадратные скобки, например:

[ 123 ./az.nix "buki" (f {x=y;}) ]

Список содержит четыре элемента, последним из которых является результат вычисления функции f.

2.6. Наборы атрибутов

Набор атрибутов формируется из набора пар название/значение, заключенных в фигурные скобки. Значением является выражение языка, заканчивающееся точкой с запятой.

{ a = 123;
   name = "sample";
   b = f {x = 178};
}

определяет атрибуты с именами a, name, b.

Порядок элементов не учитывается. Имя атрибута должно быть уникально в наборе.

Атрибут может быть выбран через оператора точка. Например:

{ a = "Az"; b = "Buki"; }.a
=>
"Az"

3. Конструкции языка

3.1. Рекурсивный набор атрибутов

Рекурсивный набор атрибутов формируется так же, как обычный, только в этом атрибуты могут ссылаться друг на друга. Например:

rec {
  x = y;
  y = 123;
}.x
=>
123

3.2. Конструкция Let

С помощью этой конструкции определяются локальные идентификаторы. Например,

let
  x = "az";
  y = "buki";
in x + y

вычислится в "azbuki".

3.3. Наследование атрибутов

Создавая наборы атрибутов, часто приходится копировать атрибуты из локального контекста:

let
  x = 123;
in 
  {
    x = x;
    y = 456;
  }

Для этого лучше использовать ключевое слово inherit:

let
  x = 123;
in 
  {
    inherit x;
    y = 456;
  }

В обоих случаях получится {x = 123; y = 456;}.

Также возможно копировать атрибуты из других наборов атрибутов:

let
  test = { x = 123;};
in
  {
     inherit (test) x;
     y = 456;
   }

Результат будет тот же: {x = 123; y = 456;}.

3.4. Функции

Функция имеет вид:

аргумент1: [аргумент2: [..]] тело,

где каждый из аргументов может быть либо идентификатором, либо набором атрибутов.

В этом примере аргументы задаются идентификаторами:

let negate = x: !x;
     concat = x: y: x + y;
in if negate false then concat "az" "buki" else ""
=>
"azbuki"

Если функции concat передать только один аргумент, то она вернет функцию, принимающую другой аргумент:

let concat = x: y: x+y;
     azadd = concat "az";
in azadd "buki"

Например, функция, где аргумент задается набором атрибутов:

{x, y, z}: z + y + x

Вызов такой функции будет выглядеть так:

let reconcat = {x,y,z}: z + y +x;
in reconcat {x="Az"; y="Buki"; z="Vedi";}
=>
"VediBukiAz"

Неопределенное количество атрибутов в аргументе задается многоточием:

{x, y, z, ...}: z + y + x

Значение по умолчанию для атрибута можно задать через знак вопроса:

{x, y ? "Az", z ? "Buki"}: z + y + x

Еще один вариант задания атрибутов определяет идентификатор для набора атрибутов: идентификатор@{атрибуты, ...}. Например:

let concat = args@{x, ...}: x + "-" + args.a + args.b;
in concat {x="az"; a="buki"; b="vedi";}
=>
"az-bukivedi"

В предыдущих примерах определенные функции назначались именованным атрибутам и вызывались через них, но это не обязательно:

(x: y: z: x + y +z) "Az" "Buki" "Vedi"

Здесь определяется безымянная функция, и тут же ей передаются аргументы.

3.5. Условная конструкция

Условная конструкция выглядит так:

if e1 then e2 else e3

где выражение e1 должно вычисляться в булево значение (true или false), в зависимости от этого вычисляется одно из выражений e2 или e3 и возвращается результат.

3.6. Утверждения

Утверждения обычно используются для проверки условий сборки пакета, таких как зависимости, опции.

Утверждения задаются следующим образом:

assert e1; e2

где выражение e1 должно принимать булево значение (true или false). Если e1 принимает значение true, то вычисляется выражение e2 и возвращается его значение, иначе вычисление прерывается и выводится сообщение об ошибке с указанием места возникновения.

3.7. Конструкция With

Конструкция

with e1; e2

делает доступным лексический контекст выражения e1 в контексте выражения e2. Например:

let as = {x = "Az"; y = "Buki";};
in with as; x + y

Наиболее частое использование этой конструкции совместно с функцией import

with (import ./definitions.nix); ...

делает доступными все атрибуты, определенные в definitions.nix, как будто они определены локально в выражении rec {...}.

3.8. Комментарии

Однострочные комментарии предваряет символ #. Многострочные комментарии заключаются в /* ... */.

4. Операторы

Список операций языка в порядке убывания их приоритета:

* e . id

Выбор атрибута id из набора атрибутов e. Вычисление прерывается, если атрибут не представлен в наборе.

* e1 e2

Вызов функции e1 с аргументом e2.

* e ? id

Проверка на присутствие атрибута id в наборе e. Возвращает true или false.

* e1 ++ e2

Конкатенация списков.

* e1 + e2

Конкатенация строк или путей.

* ! e

Логическое отрицание

* e1 // e2

Возвращает набор атрибутов, содержащий атрибуты обоих наборов. В случае совпадения имен атрибутов значение берется из набора e2.

* e1 == e2

Проверка на равенство.

* e1 != e2

Проверка на неравенство.

* e1 && e2

Логическое И.

* e1 || e2

Логическое ИЛИ.

* e1 -> e2

Логическое следование.

5. Встроенные функции

Ниже перечислены встроенные функции языка, сгруппированные по их назначению.

5.1. Типы

*
builtins.isAttrs e
builtins.isList e
builtins.isFunction e
builtins.isString e
builtins.isInt e
builtins.isBool e

Проверка значения на принадлежность типу: набор атрибутов, список, функция, строка, целое, булево – соответственно. Возвращает true или false.

* isNull e

Возвращает true, если значение e null, иначе false.

* toString e

Преобразует значение выражения e в строковое представление.

5.2. Набор атрибутов

* builtins.getAttr s attrs

Возвращает значение атрибута с именем s из набора attrs. Вычисление прерывается, если атрибут s не найден в наборе.

* builtins.hasAttr s attrs

Возвращает true, если атрибут с именем s присутствует в наборе, иначе возвращает false. Это динамическая версия операции ?, здесь s является выражением, возвращающим строку, а не идентификатором атрибута.

* builtins.attrNames attrs

Возвращает отсортированный список имен атрибутов из набора attrs. Например, builtins.attrNames {y = 1; x = "Az";} вернет [ "x" "y" ]. Нет функции attrValues, но её можно легко определить самостоятельно:

attrValues = attrs: map (name: builtins.getAttr name attrs) (builtins.attrNames attrs);
* builtins.listToAttrs e

Формирует набор атрибутов из списка, каждый элемент которого есть набор атрибутов, в котором в строковом виде указаны идентификатор (name) и значение (value) атрибута. Например,

builtins.listToAttrs [
  {name = "Az"; value = 123;}
  {name = "Buki"; value = 456;}
]

формирует:

{ Az = 123; Buki = 456; }
* removeAttrs attrs list

Убирает из набора атрибутов attrs атрибуты, имена которых указаны в списке list. Например,

removeAttrs { x = 1; y = 2; z = 3; } ["a" "x" "z"]

вернет

{y = 2;}.
* builtins.getAttr s attrs

Возвращает значение атрибута с именем s из набора attrs. Вычисление прерывается, если атрибут s не найден в наборе.

5.3. Списки

* builtins.head list

Возвращает первый элемент списка list. Вычисление прерывается, если аргумент list не является списком, либо список пуст.

* builtins.tail list

Возвращает элементы списка list начиная со второго и до последнего. Вычисление прерывается, если аргумент list не является списком либо список пуст.

* builtins.length e

Возвращает количество элементов списка e.

5.4. Строки

* builtins.stringLength e

Возвращает длину строки e. Если это не строка, то вычисление прерывается.

* builtins.substring start len s

Возвращает подстроку строки s с позиции start (отсчет с нуля), длиной len. Значение start не должно быть отрицательным. Если start больше длины строки, то возвращается пустая строка. Если start+len выходит за пределы строки, то берется только оставшееся до конца содержимое исходной строки.

5.5. Арифметические функции

* builtins.add e1 e2

Сумма целых чисел e1 и e2.

* builtins.sub e1 e2

Разность целых чисел e1 и e2.

* builtins.div e1 e2

Деление нацело целых чисел e1 и e2.

* builtins.mul e1 e2

Произведение целых чисел e1 и e2.

* builtins.lessThan e1 e2

Возвращает true, если целое e1 меньше e2, иначе true.

Для этих функций вычисление прерывается, если значения e1, e2 не являются целыми.

5.6. Модули

* import path

Функция загружает, вычисляет и возвращает значение выражения из файла, находящегося по указанному пути path. Вычисление прерывается, если указанный файл не существует, либо в нем некорректное Nix-выражение.

Загружаемое из файла выражение не должно использовать идентификаторы из контекста, в котором он импортируется. Например, при выполнении выражения:

rec {
  x = 123;
  y = import ./foo.nix;
}

когда foo.nix содержит

x + 456,

произойдет ошибка, потому что x нет в контексте foo.nix. Чтобы x был доступен в foo.nix, нужно передать его аргументом функции:

rec {
  x = 123;
  y = import ./foo.nix x;
}

где foo.nix:

x: x + 456.

5.7. Функции высшего порядка

* map f list

Применяет функцию f к каждому элементу списка list. Например,

map (x: "Az" + x) ["Buki" "Vedi" "Glagoli"]
=>
[ "AzBuki" "AzVedi" "AzGlagoli" ]

5.8. Системные функции

* baseNameOf s

Подобно команде GNU, basename возвращает подстроку за последним слешем в строке, представляющей путь.

* dirOf s

Подобно команде GNU, dirname возвращает подстроку до последнего слеша в строке, представляющей путь.

* builtins.currentSystem

Возвращает идентификатор платформы, на которой производится вычисление, например, "i686-linux" или "powerpc-darwin".

* builtins.filterSource e1 e2

Возвращает отфильтрованный список файлов из дерева каталогов e2. Фильтрация производится на основе функции e1, применяемой к каждому файлу, каталогу, символической ссылке из дерева каталогов e2. Функция e1 принимает два аргумента, первый – полный путь к файлу, второй – тип файла: "regular" – обычный файл, "directory" – каталог, "symlink" – ссылка или "unknown" – файлы другого типа (файлы устройств, fifo). Если функция e1 возвращает true, то файл копируется в хранилище, иначе – игнорируется. Например, требуется выбрать исходники из каталога, находящегося под управлением Subversion, т. е. нужно исключить служебные каталоги .svn:

stdenv.mkDerivation {
  ...
 src = builtins.filterSource
    (path: type: type != "directory" || baseNameOf path != ".svn")
    ./source-dir;
  ...
}
* builtins.pathExists path

Возвращает true, если файл присутствует по указанному пути path, иначе – false.

* builtins.readFile path

Возвращает в виде строки содержимое файла по указанному пути path.

* builtins.toFile name s

Записывает содержимое строки s в файл с именем name и возвращает путь к нему в хранилище Nix.

* builtins.toPath s

Преобразует строку s в путь. Строка содержит представление абсолютного пути. Путь не обязательно должен существовать.

5.9. Отладка и исключения

* abort s

Прерывает вычисление выражения и выводит строку s.

* throw s

Вызов пользовательского исключения с выводом сообщения s. В отличие от функции abort, исключение может игнорироваться в некоторых случаях, например, при выполнении nix-env -qa.

* builtins.trace e1 e2

Выводит результат вычисления e1 в синтаксическом представлении на stderr и возвращает e2. Обычно используется для отладки.

6. Заключение

Следующие статьи цикла покажут, как с помощью языка выражений Nix создать описание сборки пакета либо конфигурации NixOS.


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux
ArticleID=480527
ArticleTitle=Функциональный менеджер пакетов Nix: Часть 2. Специализированный язык
publish-date=04062010