Введение
Чтобы попробовать примеры кода отсюда, вам необходим установленный Nix. Введите в консоли nix repl
и пробуйте!
Если вы хотите запускать фалы с кодом, вам пригодится команда nix-instantiate --eval file.nix
.
В некоторых случаях такой запуск файла может дать вывод наподбие { a = <CODE>; }
. Если это происходит, перед флагом --eval
добавьте флаг --strict
.
Nix - чисто функциональный язык программирования. В нем нет понятия последовательного выполнения шагов - любые зависимости от операций выполняются только через данные, полученные от других операций. Говоря простым языком, порядок не важен. Например:
Здесь a
и b
- просто выражения. Nix смотрит: нам нужен b
. Чтобы вычислить его, нужно знать a
. Он сам понимает это и вычисляет только то, что нужно и только тогда, когда нужно.
А вот в императивных языках программирования все по-другому: ты пишешь инструкции одну за другой, и они выполняются по порядку. Рассмотрим пример на Golang:
Сначала выполнится первая строка, затем вторая, и так далее. Порядок важен.
Разберем пример потруднее. Здесь используется функция builtins.throw
- вызывает ошибку, если до нее дойдет вычисление.
attrs
- набор атрибутов (что то вроде словаря или объекта), у него есть два поля - a
и b
. Но в основной части с in
вы используем только a
, поэтому "ошибочное" значение b
не вычисляется.
Если мы добавим b
в вычисляемую область, мы увидим вывод error: Оу нет!
.
Помимо этого, любой фрагмент Nix кода является выражением, возращающим значение. Оценка (вычисление) выражения дает одну структуру данных, а не последовательность операций. Каждый файл Nix вычисляется в одно выражение.
Также Nix является "ленивым" языком. Мы уже рассмотрели выше пример с throw
, когда он не вычисляется, ведь не используется в программе. Это и называется ленивостью.
Nix создан для конкретной цели - для взаимодействия с менеджером пакетов Nix. Хотя иногда его используют для других задач, он не является языком общего назначения.
Языковые конструкции
Здесь я расскажу о конструкциях в Nix. Это небольшой язык, и большинство из них очевидны.
Основные типы данных
Операторы
Оператор | Описание |
---|---|
+ , - , * , / | Стандартные арифметические операции |
+ | Конкатенация строк |
++ | Объединение списков |
== , != | Логическое равенство/неравенство |
> , >= , < , <= | Логические операции сравнения |
&& , || | Логические AND и OR |
-> | Логическая импликация |
! | Отрицание |
set.attr | Доступ к атрибуту attr из набора set |
set ? attribute | Проверяет, содержит ли набор атрибут |
left // right | Объединяет два набора атрибутов |
Оператор объединения //
Этот оператор широко используется в Nix коде, поэтому вам желательно изучить его. В других языках таких операторов почти не бывает.
Он объединяет два набора атрибутов, переданных ему:
Значение справа от оператора имеют больший приоритет:
Этот оператор не умеет рекурсивно объединять списки:
Вместо этого используйте lib.mkMerge
:
Переменные
В Nix переменные вводятся с помощью выражения let ... in
. Они неизменяемы и доступны только в области действия этого выражения. Глобальных переменных нет.
Функции
Все функции в Nix являются лямбда-функциями. Это означает, что они обрабатываются как данные. Давать им имена можно с помощью присваивания их переменным или устанавливая их в качестве значений набора атрибутов.
Объявление функции - это просто единственный аргумент, за которым следует двоеточие и тело функции:
Функцию можно тут же вызвать с помощью:
Несколько аргументов (каррирование)
Технически любая функция Nix может принимать только один аргумент. Но иногда функция требует нескольких - и это достигается через каррирование:
На самом деле это не одна функция, а две. Первая принимает age
и подставляет в строку, а вторая принимате name
и подставляет в строку, которую вернула первая функция.
Еще одно преимущество таких функций - возможность передать один параметр и получить функцию, которую можно записать в переменную, то есть частично применить:
Несколько аргументов (наборы атрибутов)
Другой способ указать несколько аргументов - передать набор атрибутов, который включает в себя все аргументы:
Используя этот метод, мы можем задать значения по умолчанию:
Помимо этого, если необходимо дать функции возможность принимать неограниченное количество аргументов, можно воспользоваться ...
:
Также можно назвать весь набор каким нибудь именем с помощью такого синтаксиса:
Условная конструкция
Nix имеет простую поддержку условных конструкций. Не забудьте что if
- тоже выражение, следовательно наличие then
и else
обязательно.
Проверка assert
Для проверки утверждений в Nix есть специальная конструкция assert
:
Ключевое слово inherit
Это очень полезное и простое ключевое слово, которое используется для привязки переменной из родительской области видимости. Проще говоря, inherit foo;
это то же самое, что foo = foo;
.
Помимо этого inherit поддерживает привязку сразу нескольких переменных, а также привязку переменных из наборов атрибутов:
Оператор with
Этот оператор крайне полезен. Он импортирует все атрибуты из набора в переменные с соответствующими именами:
Импорты
Файлы Nix могут импортировать друг друга с помощью встроенной функции import
:
Функция import
оценивает файл и возвращает его Nix значение. Часто файлы Nix начинаются с заголовка функции для передачи параметров в остальную часть файла, поэтому вы часто можете видеть импорт в формате import ./lib.nix { ... };
.
Кстати, у Nix есть интересная переменная окружения NIX_PATH
, которая содержит псевдонимы для путей к файлам, содержащих выражения Nix. По умолчанию в ней присутствует несколько каналов (nixpkgs
и возможно nixos-unstable
). К ним можно получить доступ с помощью следующего синтаксиса:
Функция map
Эта функция применяет переданную ей функцию к каждому элементу переданного ей списка:
Выражение or
В Nix есть ключевое слово or
, которое может использоваться для безопасного доступа к атрибуту. Если он не установлен, будет возвращено значение по умолчанию:
Стандартные библиотеки
В Nix существует 3 стандартных библиотеки, и желательно знать все три.
builtins
Nix поставляется с несколькими встроенными функциями, они работают независимо от чего-либо. Большинство из них реализованы в самом интерпретаторе Nix, так что они достаточно быстры по сравнению с функциями, написанными на Nix.
В руководстве Nix есть раздел, в котором перечислены все builtins
и их использование.
Вот одни из самых часто используемых функций отсюда:
derivation
- стандартная деривация (см. Деривации)toJSON
/fromJSON
- перевод аттерсетов/списков в JSON формат и обратноtoString
- преобразование в строкуtoPath
/fromPath
- перевод строк в пути и обратно
Здесь также есть несколько функций, ломающих чистоту вычислений Nix:
fetchGit
- скачивает Git репозиторий, по умолчанию используя конфигурацию git/sshfetchTarball
- скачивает и извлекает архивы без указания хешей
pkgs.lib
Nixpkgs помимо обычных пакетов также содержит дочерний набор атрибуто lib
, который содержит огромное количество полезных функций. Полный список с документацией по каждой вы можете найти здесь.
pkgs
сам по себе
Сам Nixpkgs помимо пакетов и lib
аттерсета содержит функции, с которыми вы можете столкнуться при создании новых пакетов Nix.
К сожалению, поисковиков именно по пакетам из pkgs
нет. Вы может найти их на noogle.dev, в Nixpkgs Reference Manual, но чистого списка функций не будет. Поэтому я написал специально для вас небольшую программу, которая ищет все функции в pkgs
:
Для запуска создайте Nix файл с любым названием и запустите с помощью nix eval -f <filename>.nix
.
Деривации
При оценке Nix выражения вы можете получить одну и более дериваций. Они описывают действия для сборки, которые при запуске помещают выходные данные в /nix/store
.
Встроенная функция derivation
отвечает за создание низкоуровненвых производных. Обычно при опакечивании программ используются более высокоуровненвые, такие как stdenv.mkDerivation
.
Деривации - отдельная большая тема, так что про нее я расскажу в другой статье.