فا نیکسی

مبانی زبان Nix

زبان Nix برای ایجاد و ترکیب راحت درایویشن‌ها (derivations) طراحی شده است؛ توضیحات دقیقی درباره اینکه چگونه محتویات فایل‌های موجود برای به‌دست‌آوردن فایل‌های جدید استفاده می‌شوند. این زبان یک زبان برنامه‌نویسیِ مخصوص حوزه (domain-specific)، کاملاً تابعی، ارزیابی‌شونده با تنبلی (lazily evaluated) و با نوع‌داده‌ی پویا (dynamically typed) است.

توجه: کاربردهای قابل‌توجه زبان Nix

  • Nixpkgs

    بزرگ‌ترین و به‌روزترین توزیع نرم‌افزاری در جهان که با زبان Nix نوشته شده است.

  • NixOS

    یک توزیع لینوکس که می‌تواند به شکلی کاملاً اعلانی پیکربندی شود و بر پایه Nix و Nixpkgs ساخته شده است.

    سیستم پیکربندی ماژولار زیربنایی آن با زبان Nix نوشته شده و از بسته‌های Nixpkgs استفاده می‌کند. محیط سیستم‌عامل و سرویس‌هایی که ارائه می‌دهد با زبان Nix پیکربندی می‌شوند.

ممکن است به‌سرعت با عبارت‌های زبان Nixی مواجه شوید که بسیار پیچیده به‌نظر می‌رسند. همانند هر زبان برنامه‌نویسی دیگری، حجم کدِ موردنیاز در زبان Nix به‌دقت با پیچیدگی مسئله‌ای که قرار است حل کند مطابقت دارد و بازتاب‌دهنده‌ی میزان درک مسئله و راه‌حل آن است. ساخت نرم‌افزار یک اقدام پیچیده است، و Nix با زبان Nix هم این پیچیدگی را آشکار می‌کند و هم امکان مدیریت آن را فراهم می‌سازد.

با این حال، خود زبان Nix تنها دارای چند مفهوم پایه است که در این آموزش معرفی خواهند شد و می‌توانند به دلخواه با یکدیگر ترکیب شوند. آنچه ممکن است پیچیده به‌نظر برسد ناشی از خود زبان نیست، بلکه از نحوه استفاده از آن سرچشمه می‌گیرد.

نمای کلی

این یک مقدمه برای خواندن زبان Nix است، با این هدف که بتوانید سایر آموزش‌ها و مثال‌ها را دنبال کنید.

استفاده از زبان Nix در عمل شامل موارد متعددی است:

  • زبان: نحو و معناشناسی
  • کتابخانه‌ها: builtins و pkgs.lib
  • ابزارهای توسعه‌دهنده: آزمایش، اشکال‌زدایی، لینت کردن، قالب‌بندی، ...
  • مکانیسم‌های ساخت عمومی: stdenv.mkDerivation، کمک‌رسان‌های ساخت، ...
  • مکانیسم‌های ترکیب و پیکربندی: override، overrideAttrs، پوشش‌ها (overlays)، callPackage، ...
  • مکانیسم‌های بسته‌بندی مخصوص بوم‌سازگان: buildGoModule، buildPythonApplication، ...
  • سیستم ماژول NixOS: config، option، ...

این آموزش تنها مهم‌ترین ویژگی‌های زبان را پوشش می‌دهد، به‌طور خلاصه کتابخانه‌ها را مورد بحث قرار می‌دهد و در پایان شما را به مطالب مرجع و منابع مربوط به سایر کامپوننت‌ها هدایت می‌کند.

چه چیزی خواهید آموخت؟

این آموزش باید شما را قادر سازد تا کدهای معمولی زبان Nix را بخوانید و ساختار آن را درک کنید. هدف آن برجسته‌کردن بخش‌هایی است که ممکن است زبان Nix با زبان‌هایی که به آن‌ها عادت دارید متفاوت باشد.

از این رو، رایج‌ترین و متمایزترین الگوها را در زبان Nix نشان می‌دهد:

مهم

این آموزش تمام ویژگی‌های زبان Nix را با جزئیات توضیح نمی‌دهد و وارد جزئیات قوانین نحوی نمی‌شود. برای نمونه، این آموزش از سازه‌های رایجی مانند if ... then ... else ... عبور می‌کند.

برای مرجع کامل زبان، به راهنمای Nix مراجعه کنید.

به چه چیزهایی نیاز دارید؟

  • آشنایی با توسعه نرم‌افزار
  • آشنایی با شل یونیکس، جهت خواندن مثال‌های خط فرمان
  • یک مورد نصب Nix برای اجرای مثال‌ها

چقدر طول می‌کشد؟

  • بدون تجربه در برنامه‌نویسی تابعی: ۲ ساعت
  • آشنا با برنامه‌نویسی تابعی: ۱ ساعت
  • مسلط به برنامه‌نویسی تابعی: ۳۰ دقیقه

تمام مثال‌ها را اجرا کنید. با آن‌ها بازی کنید تا فرض‌های خود را اعتبارسنجی کرده و آموخته‌های خود را آزمایش کنید. اگر می‌خواهید مطمئن شوید که مثال‌ها را به طور کامل درک کرده‌اید، توضیحات مفصل را بخوانید.

چگونه مثال‌ها را اجرا کنیم؟

  • یک قطعه کد از زبان Nix یک عبارت نیکس (Nix expression) است.
  • ارزیابی یک عبارت Nix منجر به تولید یک مقدار Nix می‌شود.
  • محتوای یک فایل Nix (با پسوند فایل .nix) یک عبارت Nix است.

نکته

ارزیابی (evaluate) به معنای تبدیل یک عبارت به یک مقدار مطابق با قوانین زبان است.

این آموزش حاوی مثال‌های زیادی از عبارت‌های Nix است. هرکدام با نتیجه‌ی ارزیابی مورد انتظار دنبال می‌شوند.

مثال زیر یک عبارت Nix است که دو عدد را با هم جمع می‌کند:

1 + 2
3

ارزیابی تعاملی

برای ارزیابی تعاملی عبارت‌های Nix (با تایپ کردن آن‌ها در خط فرمان)، از nix repl استفاده کنید:

$ nix repl
Welcome to Nix 2.13.3. Type :? for help.

nix-repl> 1 + 2
3

نکته

زبان Nix از ارزیابی تنبل (lazy evaluation) استفاده می‌کند، و nix repl به صورت پیش‌فرض مقادیر را تنها در زمان نیاز محاسبه می‌کند.

برخی از مثال‌ها برای وضوح بیشتر، یک ساختار دادهٔ کاملاً ارزیابی‌شده را نشان می‌دهند. اگر خروجی شما با مثال مطابقت ندارد، سعی کنید پیشوند :p را به عبارت ورودی اضافه کنید.

مثال:

nix-repl> { a.b.c = 1; }
{ a = { ... }; }

nix-repl> :p { a.b.c = 1; }
{ a = { b = { c = 1; }; }; }

برای خروج از nix repl عبارت :q را تایپ کنید.

ارزیابی فایل‌های Nix

برای ارزیابی عبارت موجود در یک فایل Nix از nix-instantiate --eval استفاده کنید.

$ echo 1 + 2 > file.nix
$ nix-instantiate --eval file.nix
3

توضیح تفصیلی دستور اول مقدار 1 + 2 را در یک فایل به نام file.nix در پوشه جاری می‌نویسد. محتویات فایل file.nix اکنون 1 + 2 است که می‌توانید آن را با بررسی کنید

$ cat file.nix
1 + 2

دستور دوم nix-instantiate را با گزینه --eval روی file.nix اجرا می‌کند؛ این دستور فایل را خوانده و عبارت Nix موجود در آن را ارزیابی (evaluate) می‌کند. مقدار حاصل به عنوان خروجی چاپ می‌شود.

گزینه --eval برای ارزیابی فایل و انجام ندادن هیچ کار دیگری الزامی است. اگر --eval حذف شود، nix-instantiate انتظار دارد که عبارت موجود در فایل داده‌شده به یک مقدار خاص به نام درایویشن (derivation) ارزیابی شود که در انتهای این آموزش در بخش derivations به آن پرداخته شده است.

نکته

اگر هیچ نام فایلی مشخص نشده باشد، nix-instantiate --eval تلاش می‌کند تا از default.nix بخواند.

$ echo 1 + 2 > default.nix
$ nix-instantiate --eval
3

نکته

زبان Nix از ارزیابی تنبل (lazy evaluation) استفاده می‌کند، و nix-instantiate به‌طور پیش‌فرض مقادیر را تنها در زمان نیاز محاسبه می‌کند.

برخی از مثال‌ها برای وضوح بیشتر، یک ساختار دادهٔ کاملاً ارزیابی‌شده را نشان می‌دهند. اگر خروجی شما با مثال مطابقت ندارد، سعی کنید گزینهٔ --strict را به nix-instantiate اضافه کنید.

مثال:

$ echo "{ a.b.c = 1; }" > file.nix
$ nix-instantiate --eval file.nix
{ a = <CODE>; }
$ echo "{ a.b.c = 1; }" > file.nix
$ nix-instantiate --eval --strict file.nix
{ a = { b = { c = 1; }; }; }

نکاتی درباره‌ی فضای خالی (Whitespace)

فضای خالی در صورت نیاز برای جداسازی توکن‌های واژگانی استفاده می‌شود. در غیر این صورت، اهمیتی ندارد.

شکستن خطوط، تورفتگی‌ها و فضاهای خالی اضافی صرفاً برای راحتی خواننده هستند.

موارد زیر معادل یکدیگرند:

let
 x = 1;
 y = 2;
in x + y
3
let x=1;y=2;in x+y
3

نام‌ها و مقادیر

مقادیر در زبان Nix می‌توانند انواع داده‌های اولیه، فهرست‌ها، مجموعه‌های ویژگی و توابع باشند.

نمونه‌هایی از انواع داده‌های اولیه و فهرست‌ها در زمینه مجموعه‌های ویژگی ظاهر می‌شوند. در ادامه این بخش، با ویژگی‌های خاص رشته‌های کارکتر آشنا خواهید شد: درون‌گذاری رشته، مسیرهای سیستم‌فایل و رشته‌های تورفته. توابع به‌طور جداگانه پوشش داده شده‌اند.

مجموعه‌های ویژگی و عبارت‌های let برای انتساب نام‌ها به مقادیر استفاده می‌شوند. انتساب‌ها با یک علامت مساوی (=) نشان داده می‌شوند.

هر زمان که در کد زبان Nix با یک علامت مساوی (=) روبه‌رو شدید:

  • در سمت چپ آن، نام انتساب‌داده‌شده قرار دارد.
  • در سمت راست آن، مقدار قرار دارد که با یک نقطهامیر (;) محدود شده است.

مجموعه ویژگی {'{'} ... {'}'}

یک مجموعه ویژگی، مجموعه‌ای از جفت‌های نام-مقدار است که در آن نام‌ها باید یکتا باشند.

مثال زیر تمام انواع داده‌های اولیه، فهرست‌ها و مجموعه‌های ویژگی را نشان می‌دهد.

نکته

اگر با JSON آشنا هستید، زبان Nix را به عنوان JSON به همراه توابع تصور کنید.

انواع داده‌های زبان Nix بدون توابع دقیقا مانند همتایان خود در JSON کار می‌کنند و بسیار شبیه به نظر می‌رسند.

2

Nix

{
 string = "hello";
 integer = 1;
 float = 3.141;
 bool = true;
 null = null;
 list = [ 1 "two" false ];
 attribute-set = {
 a = "hello";
 b = 2;
 c = 2.718;
 d = false;
 }; # comments are supported
}

JSON

{
 "string": "hello",
 "integer": 1,
 "float": 3.141,
 "bool": true,
 "null": null,
 "list": [1, "two", false],
 "object": {
 "a": "hello",
 "b": 1,
 "c": 2.718,
 "d": false
 }
}

نکته

مجموعه ویژگی بازگشتی rec {'{'} ... {'}'}

گاهی اوقات مجموعه‌های ویژگی را مشاهده خواهید کرد که کلمه کلیدی rec در ابتدای آن‌ها قرار دارد. این کار امکان دسترسی به صفت‌ها را از درون خود مجموعه فراهم می‌کند.

مثال:

rec {
 one = 1;
 two = one + 1;
 three = two + 1;
}
{ one = 1; three = 3; two = 2; }

نکته

عناصر موجود در یک مجموعه ویژگی را می‌توان به هر ترتیبی اعلام کرد و آن‌ها در هنگام ارزیابی مرتب می‌شوند.

مثال نقض:

{
 one = 1;
 two = one + 1;
 three = two + 1;
}
error: undefined variable 'one'

 at «string»:3:9:

 2| one = 1;
 3| two = one + 1;
 | ^
 4| three = two + 1;

let ... in ...

همچنین تحت عنوان «عبارت let» یا «اتصال let» نیز شناخته می‌شود.

عبارت‌های let امکان اختصاص نام به مقادیر را برای استفاده‌ی مکرر فراهم می‌کنند.

مثال:

let
 a = 1;
in
a + a
2

توضیح تفصیلی انتساب‌ها بین کلیدواژه‌های let و in قرار می‌گیرند. در این مثال، ما a = 1 را انتساب می‌دهیم.

بعد از in عبارتی می‌آید که در آن انتساب‌ها معتبر هستند، یعنی جایی که می‌توان از نام‌های انتساب‌داده‌شده استفاده کرد. در این مثال، عبارت a + a است، جایی که a به a = 1 اشاره دارد.

با جایگزین کردن نام‌ها با مقادیر انتساب‌یافته‌شان، عبارت a + a به مقدار 2 ارزیابی می‌شود.

نام‌ها را می‌توان به هر ترتیبی انتساب داد و عبارت‌های سمت راست انتساب (=) می‌توانند به سایر نام‌های انتساب‌داده‌شده اشاره کنند.

مثال:

let
 b = a + 1;
 a = 1;
in
a + b
3

توضیح تفصیلی انتساب‌ها بین کلیدواژه‌های let و in قرار می‌گیرند. در این مثال، مقادیر a = 1 و b = a + 1 را انتساب می‌دهیم.

ترتیب انتساب‌ها اهمیتی ندارد. بنابراین، مثال زیر که در آن انتساب‌ها با ترتیب معکوس قرار دارند، معادل است:

let
 a = 1;
 b = a + 1;
in
a + b
3

توجه داشته باشید که a در b = a + 1 به a = 1 اشاره دارد.

پس از in عبارتی می‌آید که تخصیص‌ها در آن معتبر هستند. در این مثال، عبارت a + b است، که در آن a به a = 1 و b به b = a + 1 اشاره می‌کند.

با جایگزین کردن نام‌ها با مقادیر تخصیص‌یافته‌شان، a + b به مقدار 3 ارزیابی می‌شود.

این موضوع مشابه مجموعه ویژگی بازگشتی (rec attrset) است: در هر دو، ترتیب تخصیص‌ها اهمیتی ندارد و نام‌های سمت چپ می‌توانند در عبارت‌های سمت راست علامت تخصیص (=) استفاده شوند.

مثال:

2

let ... in ...

let
 b = a + 1;
 c = a + b;
 a = 1;
in { c = c; a = a; b = b; }
{ a = 1; b = 2; c = 3; }

rec {'{'} ... {'}'}

rec {
 b = a + 1;
 c = a + b;
 a = 1;
}
{ a = 1; b = 2; c = 3; }

تفاوت در این است که در حالی که یک مجموعه ویژگی بازگشتی به یک مجموعه ویژگی ارزیابی می‌شود، هر عبارتی می‌تواند پس از کلیدواژه in قرار بگیرد.

در مثال زیر ما از عبارت let برای تشکیل یک فهرست استفاده می‌کنیم:

let
 b = a + 1;
 c = a + b;
 a = 1;
in [ a b c ]
[ 1 2 3 ]

تنها عبارت‌های درون خود عبارت let می‌توانند به نام‌های تازه‌تعریف‌شده دسترسی داشته باشند. این اتصال‌ها (bindings) دارای محدوده محلی (local scope) هستند.

مثال نقض:

{
 a = let x = 1; in x;
 b = x;
}
error: undefined variable 'x'

 at «string»:3:7:

 2| a = let x = 1; in x;
 3| b = x;
 | ^
 4| }

دسترسی به صفت (attribute)

برای دسترسی به صفات (attributes) درون یک مجموعه ویژگی، از یک نقطه (.) به همراه نام صفت استفاده می‌شود.

مثال:

let
 attrset = { x = 1; };
in
attrset.x
1

دسترسی به صفت‌های تو در تو به همین شکل کار می‌کند.

مثال:

let
 attrset = { a = { b = { c = 1; }; }; };
in
attrset.a.b.c
1

از علامت نقطه (.) می‌توان برای انتساب صفت (attribute)ها نیز استفاده کرد.

مثال:

{ a.b.c = 1; }
{ a = { b = { c = 1; }; }; }

with ...; ...

عبارت with امکان دسترسی به صفت‌ها (attributes) را بدون نیاز به ارجاع مکرر به مجموعه ویژگی آن‌ها فراهم می‌کند.

مثال:

let
 a = {
 x = 1;
 y = 2;
 z = 3;
 };
in
with a; [ x y z ]
[ 1 2 3 ]

عبارت

with a; [ x y z ]

معادل است با

[ a.x a.y a.z ]

صفات ارائه‌شده از طریق with تنها در حوزهٔ دید (scope) عبارتِ پس از نقطهٔ ویرگول (;) قرار دارند.

مثال نقض:

let
 a = {
 x = 1;
 y = 2;
 z = 3;
 };
in
{
 b = with a; [ x y z ];
 c = x;
}
error: undefined variable 'x'

 at «string»:10:7:

 9| b = with a; [ x y z ];
 10| c = x;
 | ^
 11| }

inherit ...

عبارت inherit میانبری برای انتساب مقدار یک نام از یک محدوده (scope) موجود به همان نام در یک محدوده تو در تو است. این امکان برای راحتی کار و جلوگیری از تکرار چندباره‌ی یک نام فراهم شده است.

مثال:

let
 x = 1;
 y = 2;
in
{
 inherit x y;
}
{ x = 1; y = 2; }

قطعه

inherit x y;

معادل است با

x = x; y = y;

inherit (...) ...

همچنین این امکان وجود دارد که نام‌ها را از یک مجموعه ویژگی مشخص با قرار دادن نام آن در داخل پرانتز inherit کنید.

مثال:

let
 a = { x = 1; y = 2; };
in
{
 inherit (a) x y;
}
{ x = 1; y = 2; }

این قطعه

inherit (a) x y;

معادل است با

x = a.x; y = a.y;

دستور inherit در درون عبارت‌های let نیز کار می‌کند.

مثال:

let
 a = { x = 1; y = 2; };
 inherit (a) x y;
in [ x y ]
[ 1 2 ]

توضیح مفصل اگرچه این مثال ساختگی است، اما در کدهای پیچیده‌تر به طور مرتب let expressions تو در تویی را خواهید دید که نام‌ها را از محدوده بیرونی (outer scope) خود مجدداً استفاده می‌کنند.

در اینجا ما از مجموعه ویژگی a = {'{'} x = 1; y = 2; {'}'} استفاده می‌کنیم تا چیز غیربدیهی‌ای برای به ارث بردن از مجموعه ویژگی داشته باشیم. عبارت let با استفاده از ( ) مقادیر x و y را از a به ارث می‌برد که معادل نوشتن این است:

let
 x = a.x;
 y = a.y;
in

حوزهٔ درونی جدید اکنون شامل x و y است که در فهرست [ x y ] استفاده می‌شوند.

درون‌گذاری رشته ${'{'} ... {'}'}

که قبلاً با نام «antiquotation» شناخته می‌شد.

مقدار یک عبارت Nix را می‌توان با استفاده از علامت دلار و آکولادها (${'{'} {'}'}) درون یک رشته متنی قرار داد.

مثال:

let
 name = "Nix";
in
"hello ${name}"
"hello Nix"

فقط رشته‌های کارکتر یا مقادیری که می‌توانند به عنوان یک رشته‌ی کارکتری نمایش داده شوند، مجاز هستند.

مثال نقض:

let
 x = 1;
in
"${x} + ${x} = ${x + x}"
error: cannot coerce an integer to a string

 at «string»:4:2:

 3| in
 4| "${x} + ${x} = ${x + x}"
 | ^
 5|

عبارت‌های درون‌گذاری‌شده می‌توانند به هر میزانی تو در تو شوند.

(این کار می‌تواند خواندن کد را دشوار کند. در عمل از آن بپرهیزید.)

مثال:

let
 a = "no";
in
"${a + " ${a + " ${a}"}"}"
"no no no"

توضیح تفصیلی هر عبارت Nix که مقدار آن به صورت یک رشته قابل نمایش باشد را می‌توان در داخل ${'{'} {'}'} استفاده کرد.

علامت + در عبارت بالا عملگر الحاق رشته است که دو رشته را گرفته و یک رشته‌ی جدید تولید می‌کند.

عبارت موجود در این مثال عمداً پیچیده‌به‌نظررسنده طراحی شده است تا نشان دهد درون‌گذاری رشته‌های تو‌در‌تو به هر میزانی امکان‌پذیر است، اما معمولاً خواندن آن‌ها دشوار است.

این عبارت نشان‌دهنده‌ی رشته‌ای است که شامل درون‌گذاری حاصل از الحاق مقدار a با رشته‌ای است که با یک فاصله شروع شده و به دنبال آن یک رشته‌ی درون‌گذاری‌شده‌ی دیگر قرار دارد. آن دومین رشته‌ی درون‌گذاری‌شده نیز به نوبه‌ی خود نتیجه‌ی الحاق مقدار a و باز هم رشته‌ی دیگری است که با یک فاصله شروع شده و به دنبال آن درون‌گذاری a قرار می‌گیرد.

مثال:

let
 a = "one";
 b = "two";
in
"${a + b}"
"onetwo"

توابع توکار در یک بخش بعدی مورد بحث قرار می‌گیرند.

هشدار

ممکن است با رشته‌هایی روبه‌رو شوید که از علامت دلار ($) پیش از یک نام انتساب‌یافته استفاده می‌کنند، اما فاقد آکولاد ({'{'} {'}'}) هستند:

این موارد رشته‌های درون‌گذاری‌شده نیستند، بلکه معمولاً نشانگر متغیرها در یک اسکریپت شل (shell script) هستند.

در چنین مواردی، استفاده از نام‌های موجود در عبارت Nix پیرامون، صرفاً یک تصادف است.

مثال:

let
 out = "Nix";
in
"echo ${out} > $out"
"echo Nix > $out"

رشته‌های تورفته

همچنین با نام «رشته‌های چندخطی» نیز شناخته می‌شوند.

زبان Nix یک نحو کاربردی برای رشته‌های کارکتری فراهم می‌کند که چندین خط را در بر می‌گیرند و تورفتگی مشترکی دارند.

رشته‌های تورفته با دو علامت نقل‌قول تکی ('' '') نشان داده می‌شوند.

مثال:

''
multi
line
string
''
"multi\nline\nstring\n"

مقادیر مساوی از فضای خالی اضافه‌شده در ابتدا، از نتیجه حذف می‌شوند.

مثال:

''
 one
 two
 three
''
"one\n two\n three\n"

نکته

رشته‌های تورفته همچنین از درون‌گذاری رشته پشتیبانی می‌کنند. برای جزئیات، مستندات مربوط به ادبیات‌های رشته‌ای در زبان Nix را بررسی کنید.

مسیرهای سیستم‌فایل

زبان Nix نحو مناسبی را برای مسیرهای سیستم‌فایل ارائه می‌دهد.

مسیرهای مطلق همیشه با یک اسلش (/) شروع می‌شوند.

مثال:

/absolute/path
/absolute/path

مسیرها زمانی نسبی محسوب می‌شوند که شامل حداقل یک اسلش (/) باشند اما با آن شروع نشوند. آن‌ها به مسیری نسبت به فایلی ارزیابی می‌شوند که حاوی عبارت است.

مثال‌های زیر فرض می‌کنند که فایل Nix حاوی آن در پوشه‌ی /current/directory قرار دارد (یا nix repl در /current/directory اجرا می‌شود).

مثال:

./relative
/current/directory/relative

مثال:

relative/path
/current/directory/relative/path

یک نقطه (.) پوشه فعلی را در مسیر داده‌شده نشان می‌دهد.

شما اغلب عبارت زیر را خواهید دید که پوشه یک فایل Nix را مشخص می‌کند.

مثال:

./.
/current/directory

توضیح مفصل از آنجایی که مسیرهای نسبی باید حاوی یک اسلش (/) باشند اما نباید با آن شروع شوند، و نقطه (.) نشان‌دهنده عدم تغییر پوشه است، ترکیب ./. پوشه فعلی را به عنوان یک مسیر نسبی مشخص می‌کند.

دو نقطه (..) نشان‌دهنده پوشه والد هستند.

مثال:

../.
/current

نکته

مسیرها می‌توانند در عبارت‌های درون‌گذاری‌شده استفاده شوند؛ این یک عملیات ناخالص است که در بخشی دیگر به‌تفصیل بررسی شده است.

مسیرهای جستجو

همچنین تحت عنوان «سینتکس علامت بزرگ‌تر و کوچک‌تر» نیز شناخته می‌شوند.

مثال:

<nixpkgs>
/nix/var/nix/profiles/per-user/root/channels/nixpkgs

مقدار یک مسیر جستجو یک مسیر سیستم‌فایل است که به مقدار builtins.nixPath بستگی دارد.

در عمل، <nixpkgs> به مسیر سیستم‌فایل مربوط به نسخی از Nixpkgs اشاره دارد.

برای مثال، <nixpkgs/lib> به زیرپوشه‌ی lib از آن مسیر سیستم‌فایل اشاره می‌کند:

<nixpkgs/lib>
/nix/var/nix/profiles/per-user/root/channels/nixpkgs/lib

با وجود اینکه با نمونه‌های زیادی از این دست مواجه خواهید شد، اما از مسیرهای جستجو در کد عملیاتی (production) خودداری کنید، زیرا آن‌ها ناخالصی‌هایی هستند که قابل بازتولید نیستند.

توابع

توابع در زبان Nix همه‌جا حضور دارند و مستحق توجه ویژه‌ای هستند.

یک تابع همیشه دقیقاً یک آرگومان می‌پذیرد. آرگومان و بدنه تابع توسط یک علامت دونقطه (:) از یکدیگر جدا می‌شوند.

در هر کجای کد زبان Nix که علامت دونقطه (:) را پیدا کردید:

  • در سمت چپ آن، آرگومان تابع قرار دارد.
  • در سمت راست آن، بدنه تابع قرار دارد.

آرگومان‌های تابع، علاوه بر مجموعه‌های ویژگی و عبارت‌های let، سومین روش برای اختصاص دادن نام‌ها به مقادیر هستند. نکته قابل‌توجه این است که مقادیر از پیش مشخص نیستند: نام‌ها در واقع جاهای خالی (placeholders) هستند که هنگام فراخوانی تابع مقداردهی می‌شوند.

تعاریف توابع در زبان Nix می‌توانستند به اشکال مختلفی ظاهر شوند. هر یک از آن‌ها در ادامه توضیح داده شده‌اند و در اینجا یک نمای کلی آورده شده است:

  • تک‌آرگومان
 x: x + 1
  • آرگومان‌های متعدد از طریق تو در تو قرار دادن (nesting)
 x: y: x + y
  • آرگومان مجموعه ویژگی
 { a, b }: a + b
  • با صفات پیش‌فرض
 { a, b ? 0 }: a + b
  • با امکان داشتن صفات اضافی
 { a, b, ...}: a + b
  • آرگومان مجموعه ویژگی نام‌گذاری‌شده
 args@{ a, b, ... }: a + b + args.c

یا

 { a, b, ... }@args: a + b + args.c

توابع در زبان Nix هیچ نامی ندارند. آن‌ها ناشناس هستند و چنین تابعی لامبدا نامیده می‌شود.[^lambda]

[^lambda]: اصطلاح لامبدا مخففی برای انتزاع لامبدا در حساب لامبدا است.

مثال:

x: x + 1
<LAMBDA>

عبارت <LAMBDA> نشان می‌دهد که مقدار حاصل، یک تابع ناشناس است.

مانند هر مقدار دیگری، توابع را می‌توان به یک نام اختصاص داد.

مثال:

let
 f = x: x + 1;
in f
<LAMBDA>

فراخوانی توابع

که با نام «اعمال تابع» نیز شناخته می‌شود.

فراخوانی یک تابع با یک آرگومان به معنای نوشتن آن آرگومان پس از تابع است.

مثال:

let
 f = x: x + 1;
in f 1
2

مثال:

let
 f = x: x.a;
in
f { a = 1; }
1

مثال بالا تابع f را روی یک مجموعه ویژگی صریح فراخوانی می‌کند. همچنین می‌توان آرگومان‌ها را با نام ارسال کرد.

مثال:

let
 f = x: x.a;
 v = { a = 1; };
in
f v
1

از آنجا که تابع و آرگومان با فضای خالی از یکدیگر جدا می‌شوند، گاهی اوقات برای دستیابی به نتیجه‌ی دلخواه، استفاده از پرانتزها (( )) ضروری است.

مثال:

(x: x + 1) 1
2

توضیح تفصیلی این عبارت یک تابع ناشناس x: x + 1 را روی آرگومان 1 اعمال می‌کند. تابع باید درون پرانتز نوشته شود تا از آرگومان متمایز گردد.

مثال:

عناصر فهرست نیز با فاصله سفید از یکدیگر جدا می‌شوند، بنابراین موارد زیر با هم متفاوتند:

let
 f = x: x + 1;
 a = 1;
in [ (f a) ]
[ 2 ]
let
 f = x: x + 1;
 a = 1;
in [ f a ]
[ <LAMBDA> 1 ]

اولین مثال به این شکل خوانده می‌شود: f را روی a اعمال کن و نتیجه را در یک لیست قرار بده. لیست حاصل دارای یک عنصر است.

دومین مثال به این شکل خوانده می‌شود: f و a را در یک لیست قرار بده. لیست حاصل دارای دو عنصر است.

آرگومان‌های متعدد

که با نام توابع «curried» نیز شناخته می‌شوند.

توابع Nix دقیقاً یک آرگومان می‌پذیرند. آرگومان‌های متعدد را می‌توان با توابع تودرتو مدیریت کرد.

چنین تابع تودرتویی را می‌توان مانند تابعی که چندین آرگومان می‌پذیرد استفاده کرد، اما انعطاف‌پذیری بیشتری ارائه می‌دهد.

مثال:

x: y: x + y
<LAMBDA>

تابع فوق معادل است با

x: (y: x + y)
<LAMBDA>

این تابع یک آرگومان می‌گیرد و تابع دیگری به صورت y: x + y با مقدار x برابر با مقدار آن آرگومان برمی‌گرداند.

مثال:

let
 f = x: y: x + y;
in
f 1
<LAMBDA>

اعمال تابعی که از نتیجه‌ی f 1 به‌دست می‌آید روی آرگومانی دیگر، بدنه داخلی x + y را نتیجه می‌دهد (که در آن x روی 1 و y روی آرگومان دیگر تنظیم شده است) و اکنون می‌توان آن را به طور کامل ارزیابی کرد.

let
 f = x: y: x + y;
in
f 1 2
3

آرگومان مجموعه ویژگی

همچنین به عنوان «آرگومان‌های کلیدواژه‌ای» یا «تخریب‌سازی» (destructuring) شناخته می‌شود.

توابع Nix را می‌توان به گونه‌ای اعلام کرد که به عنوان آرگومان، به یک مجموعه ویژگی با ساختاری خاص نیاز داشته باشند.

این کار با فهرست کردن نام‌های ویژگی‌های مورد انتظار که با کاما (,) از هم جدا شده و در آکولادها ({'{'} {'}'}) قرار گرفته‌اند، نشان داده می‌شود.

مثال:

{a, b}: a + b
<LAMBDA>

آرگومان، صفات دقیقی را تعریف می‌کند که باید در آن مجموعه وجود داشته باشند. جا انداختن یا ارسال صفات اضافی یک خطا محسوب می‌شود.

مثال:

let
 f = {a, b}: a + b;
in
f { a = 1; b = 2; }
3

مثال نقض:

let
 f = {a, b}: a + b;
in
f { a = 1; b = 2; c = 3; }
error: 'f' at (string):2:7 called with unexpected argument 'c'

 at «string»:4:1:

 3| in
 4| f { a = 1; b = 2; c = 3; }
 | ^
 5|

مقادیر پیش‌فرض

که با نام «آرگومان‌های پیش‌فرض» نیز شناخته می‌شوند.

آرگومان‌های تخریب‌شده (destructured) می‌توانند مقادیر پیش‌فرضی برای صفت (attribute)ها داشته باشند.

این کار با جدا کردن نام صفت و مقدار پیش‌فرض آن توسط یک علامت سوال (?) نشان داده می‌شود.

اگر صفت‌ها در آرگومان دارای مقدار پیش‌فرض باشند، وجودشان الزامی نیست.

مثال:

let
 f = {a, b ? 0}: a + b;
in
f { a = 1; }
1

مثال:

let
 f = {a ? 0, b ? 0}: a + b;
in
f { } # empty attribute set
0

صفات اضافی

صفات اضافی با استفاده از یک علامت سه نقطه (...) مجاز هستند:

{a, b, ...}: a + b

برخلاف مثال نقض قبلی، ارسال آرگومان حاوی صفات اضافی خطا محسوب نمی‌شود.

مثال:

let
 f = {a, b, ...}: a + b;
in
f { a = 1; b = 2; c = 3; }
3

آرگومان مجموعه ویژگی نام‌گذاری‌شده

همچنین شناخته‌شده با عنوان‌های «الگوی @»، «نحو @» یا «نحو at».

یک آرگومان مجموعه ویژگی می‌تواند نامی دریافت کند تا به‌عنوان یک کل قابل دسترس باشد.

این کار با پیشوند یا پسوند کردن نام به آرگومان مجموعه ویژگی، که با علامت at (@) جدا شده‌اند، نشان داده می‌شود.

مثال:

{a, b, ...}@args: a + b + args.c
<LAMBDA>

یا

args@{a, b, ...}: a + b + args.c
<LAMBDA>

مثال:

let
 f = {a, b, ...}@args: a + b + args.c;
in
f { a = 1; b = 2; c = 3; }
6

کتابخانه‌های تابع

علاوه بر عملگرهای توکار (+، ==، && و غیره)، دو کتابخانه پرکاربرد وجود دارد که با یکدیگر می‌توانند به عنوان استاندارد زبان Nix در نظر گرفته شوند. برای درک و ناوبري کدهای زبان Nix باید با هر دوی آن‌ها آشنا باشید.

برای آشنایی با امکانات موجود، نگاهی اجمالی به آن‌ها بیندازید.

builtins

همچنین تحت عنوان «عملیات‌های اولیه» یا «primops» شناخته می‌شود.

Nix دارای توابع زیادی است که به صورت توکار در زبان تعبیه شده‌اند. آن‌ها به زبان C++ به عنوان بخشی از مفسر زبان Nix پیاده‌سازی شده‌اند.

نکته

راهنمای Nix تمام توابع توکار را فهرست کرده و نحوه استفاده از آن‌ها را نشان می‌دهد.

این توابع تحت ثابت builtins در دسترس هستند.

مثال:

builtins.toString
<PRIMOP>

import

بیشتر توابع توکار فقط از طریق builtins قابل دسترسی هستند. یک استثنای قابل‌توجه تابع import است که در سطح بالا نیز در دسترس قرار دارد.

تابع import یک مسیر به یک فایل نیکس را دریافت می‌کند، آن را می‌خواند تا عبارت نیکس موجود در آن را ارزیابی کند، و مقدار حاصل را برمی‌گرداند. اگر مسیر به یک پوشه اشاره کند، به جای آن از فایل default.nix موجود در آن پوشه استفاده می‌شود.

مثال:

$ echo 1 + 2 > file.nix
import ./file.nix
3

توضیح تفصیلی دستور شل قبلی محتوای 1 + 2 را در فایل file.nix در پوشه جاری می‌نویسد.

عبارت Nix فوق به این فایل به عنوان ./file.nix اشاره می‌کند. دستور import فایل را می‌خواند و به عبارت Nix موجود در آن ارزیابی می‌شود.

اگر مسیر سیستم‌فایل وجود نداشته باشد، خطایی رخ خواهد داد.

پس از خواندن file.nix، عبارت Nix معادل محتوای فایل خواهد بود:

1 + 2
3

از آنجا که یک فایل Nix می‌تواند حاوی هر عبارت Nix باشد، توابع درون‌ریزی‌شده (imported) را می‌توان بلافاصله روی آرگومان‌ها اعمال کرد.

هر زمان که توکن‌های اضافی را پس از فراخوانی import پیدا کنید، مقدار بازگردانده‌شده یک تابع خواهد بود. هر چیزی که در ادامه می‌آید، آرگومان‌های آن تابع است.

مثال:

$ echo "x: x + 1" > file.nix
import ./file.nix 1
2

توضیح جزئیات دستور شل قبلی محتوای x: x + 1 را در پوشه جاری در فایل file.nix می‌نویسد.

عبارت Nix بالا به این فایل به صورت ./file.nix ارجاع می‌دهد. دستور import ./file.nix فایل را می‌خواند و به عبارت Nix موجود در آن ارزیابی می‌شود.

اگر مسیر سیستم‌فایل وجود نداشته باشد، خطایی رخ می‌دهد.

پس از خواندن فایل، عبارت Nix یعنی import ./file.nix معادل محتوای فایل خواهد بود:

(x: x + 1) 1
2

این کار تابع x: x + 1 را روی آرگومان 1 اعمال می‌کند و در نتیجه به 2 ارزیابی می‌شود.

نکته

پرانتزها برای جدا کردن تعریف تابع از فراخوانی تابع الزامی هستند.

pkgs.lib

مخزن nixpkgs شامل یک مجموعه ویژگی به نام lib است که تعداد زیادی از توابع مفید را فراهم می‌کند. آن‌ها به زبان Nix پیاده‌سازی شده‌اند، برخلاف builtins که بخشی از خود زبان هستند.

نکته

راهنمای Nixpkgs تمام توابع کتابخانه‌ای Nixpkgs را فهرست می‌کند.

این توابع معمولاً از طریق pkgs.lib قابل دسترسی هستند، زیرا مجموعه ویژگی Nixpkgs طبق قرارداد pkgs نام‌گذاری می‌شود.

مثال:

let
 pkgs = import <nixpkgs> {};
in
pkgs.lib.strings.toUpper "lookup paths considered harmful"
LOOKUP PATHS CONSIDERED HARMFUL

توضیح تفصیلی این یک مثال پیچیده‌تر است، اما تا اینجای کار دیگر باید با تمام اجزای آن آشنا شده باشید.

نام pkgs به عنوان عبارتی که از یک فایل import شده است، اعلام می‌شود. مسیر آن فایل با مقدار مسیر جستجو یعنی <nixpkgs> تعیین می‌شود، که آن هم به نوبه خود توسط متغیر محیطی $NIX_PATH در زمان ارزیابی این عبارت تعیین می‌شود. از آنجا که این عبارت یک تابع است، برای ارزیابی به یک آرگومان نیاز دارد، و در این مورد، ارسال یک مجموعه ویژگی خالی {'{'}{'}'} کافی است.

اکنون که pkgs در محدوده let ... in ... قرار دارد، می‌توان به صفات آن دسترسی پیدا کرد. از راهنمای Nixpkgs می‌توان فهمید که تابعی تحت lib.strings.toUpper وجود دارد.

برای اختصار، این مثال از یک مسیر جستجو برای به دست آوردن نسخه‌ای از Nixpkgs استفاده می‌کند. تابع toUpper به اندازه کافی ساده است که می‌توان انتظار داشت نتایج متفاوتی برای نسخه‌های مختلف Nixpkgs تولید نکند. با این حال، نرم‌افزارهای پیچیده‌تر احتمالاً از چنین مشکلاتی رنج خواهند برد. بنابراین، یک مثال کاملاً بازتولیدپذیر به شکل زیر خواهد بود:

let
 nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/archive/06278c77b5d162e62df170fec307e83f1812d94b.tar.gz";
 pkgs = import nixpkgs {};
in
pkgs.lib.strings.toUpper "always pin your sources"
ALWAYS PIN YOUR SOURCES

برای جزئیات بیشتر به pinning-nixpkgs مراجعه کنید.

همچنین اغلب خواهید دید که pkgs به‌عنوان یک آرگومان به یک تابع پاس داده می‌شود. طبق قرارداد، می‌توان فرض کرد که این آرگومان به مجموعه ویژگی Nixpkgs اشاره دارد که دارای صفت lib است:

{ pkgs, ... }:
pkgs.lib.strings.removePrefix "no " "no true scotsman"
<LAMBDA>

برای اینکه این تابع یک نتیجه تولید کند، می‌توانید آن را در یک فایل (مثلاً file.nix) بنویسید و از طریق nix-instantiate یک آرگومان به آن پاس دهید:

$ nix-instantiate --eval file.nix --arg pkgs 'import <nixpkgs> {}'
"true scotsman"

اغلب اوقات در پیکربندی‌های NixOS و همچنین درون مجموعه‌ی بسته‌های نیکس (Nixpkgs) مشاهده خواهید کرد که lib مستقیماً ارسال می‌شود. در آن صورت می‌توان فرض کرد که این lib معادل pkgs.lib است، در شرایطی که تنها pkgs در دسترس باشد.

مثال:

{ lib, ... }:
let
 to-be = true;
in
lib.trivial.or to-be (! to-be)
<LAMBDA>

برای اینکه این تابع یک نتیجه تولید کند، می‌توانید آن را در یک فایل (مثلاً file.nix) بنویسید و از طریق nix-instantiate یک آرگومان به آن ارسال کنید:

$ nix-instantiate --eval file.nix --arg lib '(import <nixpkgs> {}).lib'
true

گاهی اوقات هم pkgs و هم lib به عنوان آرگومان منتقل می‌شوند. در آن صورت، می‌توان فرض کرد که pkgs.lib و lib معادل یکدیگر هستند. این کار برای بهبود خوانایی با جلوگیری از استفاده‌ی مکرر از pkgs.lib انجام می‌شود.

مثال:

{ pkgs, lib, ... }:
# ... multiple uses of `pkgs`
# ... multiple uses of `lib`

به دلایل تاریخی، برخی از توابع موجود در pkgs.lib معادل builtins با همین نام هستند.

ناخالصی‌ها

تا اینجا این آموزش تنها به بررسی عبارت‌های خالص پرداخته است: تعریف داده‌ها و تبدیل آن‌ها با استفاده از توابع.

در عمل، توصیف درایویشن‌ها (derivation) که ویژگی تعیین‌کننده‌ی زبان Nix است و برنامه‌نویسی تابعی را با سیستم‌فایل ممکن می‌سازد، مستلزم مشاهده‌ی دنیای خارج است. درایویشن‌ها بعداً در همین آموزش بررسی خواهند شد.

تنها یک ناخالصی در زبان Nix وجود دارد که در این‌جا اهمیت دارد: خواندن فایل‌ها از سیستم‌فایل به عنوان ورودی‌های ساخت.

درایویشن‌ها برای توصیف نحوه‌ی اشتقاق فایل‌های جدید، به ورودی‌های ساخت ارجاع می‌دهند. هنگامی که یک درایویشن اجرا می‌شود، فقط به ورودی‌های ساخت صراحتاً اعلام‌شده دسترسی خواهد داشت.

تنها راه برای مشخص کردن ورودی‌های ساخت در زبان Nix به صورت صریح عبارت است از:

  • مسیرهای سیستم‌فایل
  • توابع اختصاصی

Nix و زبان Nix فایل‌ها را با هش محتوای آن‌ها شناسایی می‌کنند. اگر محتوای فایل‌ها از پیش مشخص نباشد، خواندن فایل‌ها در حین ارزیابی عبارت اجتناب‌ناپذیر است.

نکته

Nix از انواع دیگری از عبارت‌های ناخالص پشتیبانی می‌کند؛ مانند مسیرهای جستجو یا ثابت builtins.currentSystem. این موارد در اینجا به تفصیل بررسی نمی‌شوند، زیرا از نظر اصول کارکرد زبان Nix اهمیتی ندارند و به این دلیل که استفاده از آن‌ها به خاطر از بین بردن بازتولیدپذیری توصیه نمی‌شود.

مسیرها

هر زمان که یک مسیر سیستم‌فایل در درون‌گذاری رشته استفاده شود، محتوای آن فایل به عنوان یک اثر جانبی در مکانی خاص در سیستم‌فایل، یعنی انبار Nix، کپی می‌شود.

سپس رشته‌ی ارزیابی‌شده حاوی مسیر انبار Nix اختصاص‌داده‌شده به آن فایل خواهد بود.

مثال:

$ echo 123 > data
"${./data}"
"/nix/store/h1qj5h5n05b5dl5q4nldrqq8mdg7dhqk-data"

توضیح مفصل دستور شل قبلی کاراکترهای 123 را در فایل data در پوشه جاری می‌نویسد.

عبارت Nix بالا به این فایل به عنوان ./data ارجاع می‌دهد و مسیر سیستم‌فایل را به یک رشته درون‌گذاری‌شده ${'{'} ... {'}'} تبدیل می‌کند.

چنین عبارت‌های درون‌گذاری‌شده‌ای باید به چیزی ارزیابی شوند که بتوان آن را به عنوان یک رشته کاراکتری نمایش داد. یک مسیر سیستم‌فایل چنین مقداری است و نمایش رشته کاراکتری آن، مسیر انبار Nix متناظر است:

/nix/store/<hash>-<name>

مسیر انبار نیکس با گرفتن هش محتویات فایل (<hash>) و ترکیب آن با نام فایل (<name>) به دست می‌آید. فایل به عنوان یک اثر جانبی ارزیابی، در پوشه‌ی انبار Nix یعنی /nix/store کپی می‌شود. اگر مسیر سیستم‌فایل وجود نداشته باشد، یک خطا رخ خواهد داد.

برای پوشه‌ها نیز همین اتفاق رخ می‌دهد: کل پوشه (شامل فایل‌ها و پوشه‌های تو در تو) در انبار Nix کپی می‌شود و رشته‌ی ارزیابی‌شده تبدیل به مسیر انبار نیکسِ پوشه می‌گردد.

دریافت‌کننده‌های Nixpkgs

فایل‌هایی که قرار است به عنوان ورودی‌های ساخت استفاده شوند، نیازی نیست حتماً از روی سیستم‌فایل تأمین شوند.

زبان Nix توابع توکار ناخالصی را برای دریافت فایل‌ها از طریق شبکه در طول ارزیابی فراهم می‌کند:

این توابع به یک مسیر سیستم‌فایل در انبار Nix ارزیابی می‌شوند.

مثال:

builtins.fetchurl "https://github.com/NixOS/nix/archive/7c3ab5751568a0bc63430b33a5169c5e4784a0ff.tar.gz"
"/nix/store/7dhgs330clj36384akg86140fqkgh8zf-7c3ab5751568a0bc63430b33a5169c5e4784a0ff.tar.gz"

برخی از آن‌ها راحتی بیشتری را فراهم می‌کنند، مانند استخراج خودکار آرشیوها.

مثال:

builtins.fetchTarball "https://github.com/NixOS/nix/archive/7c3ab5751568a0bc63430b33a5169c5e4784a0ff.tar.gz"
"/nix/store/d59llm96vgis5fy231x6m7nrijs0ww36-source"

نکته

راهنمای Nixpkgs در بخش دریافت‌کننده‌های Nixpkgs توابع کتابخانه‌ای اضافی متعددی را برای دریافت فایل‌ها از طریق شبکه فهرست می‌کند.

اگر درخواست شبکه با شکست مواجه شود، یک خطا محسوب می‌شود.

Derivations

درایویشن‌ها هسته‌ی اصلی هم Nix و هم زبان Nix را تشکیل می‌دهند:

  • از زبان Nix برای توصیف درایویشن‌ها استفاده می‌شود.
  • Nix درایویشن‌ها را اجرا می‌کند تا نتایج ساخت را تولید کند.
  • نتایج ساخت به نوبه خود می‌توانند به عنوان ورودی برای سایر درایویشن‌ها استفاده شوند.

پایه و اساس در زبان Nix برای اعلام یک درایویشن، تابع توکار ناخالص derivation است.

این تابع معمولاً توسط مکانیسم ساخت مجموعه‌ی بسته‌های نیکس (Nixpkgs) یعنی stdenv.mkDerivation پوشانده می‌شود، که بسیاری از پیچیدگی‌های مربوط به رویه‌های ساخت غیربدیهی را پنهان می‌کند.

نکته

احتمالاً هرگز در عمل با derivation مواجه نخواهید شد.

هر زمان که با mkDerivation مواجه شدید، نشان‌دهنده‌ی چیزی است که Nix در نهایت آن را خواهد ساخت.

مثال: یک بسته با استفاده از mkDerivation

نتیجه ارزیابی derivationmkDerivation) یک مجموعه صفت با ساختار خاص و یک ویژگی خاص است: می‌توان از آن در درون‌گذاری رشته استفاده کرد و در این صورت، به مسیر انبار Nix نتیجه‌ی ساخت خود ارزیابی می‌شود.

مثال:

let
 pkgs = import <nixpkgs> {};
in "${pkgs.nix}"
"/nix/store/sv2srrjddrp2isghmrla8s6lazbzmikd-nix-2.11.0"

نکته

خروجی شما ممکن است متفاوت باشد. این امر ممکن است یک هش متفاوت یا حتی نسخه بسته‌ی متفاوتی را تولید کند.

مسیر خروجی یک derivation به‌طور کامل توسط ورودی‌های آن تعیین می‌شود که در این مورد از نسخه‌ای از Nixpkgs می‌آیند.

به همین دلیل است که برای تضمین نتایج قابل‌پیش‌بینی، به جز در مثال‌هایی که صرفاً برای اهداف آموزشی در نظر گرفته شده‌اند، مسیرهای جستجو راavoid نکنید / از مسیرهای جستجو پرهیز کنید.

توضیح تفصیلی این مثال، عبارت Nix را از مسیر جستجو <nixpkgs> درون‌ریزی کرده و تابع حاصل را روی یک مجموعه ویژگی خالی {'{'}{'}'} اعمال می‌کند. خروجی آن به نام pkgs اختصاص می‌یابد.

تبدیل صفت pkgs.nix به یک رشته با استفاده از درون‌گذاری رشته مجاز است، زیرا pkgs.nix یک derivation است. 즉 به عبارت دیگر، در نهایت pkgs.nix به فراخوانی derivation ختم می‌شود.

رشته‌ی حاصل، همان مسیر سیستم‌فایل است که فرآورده‌ی ساخت آن derivation در آنجا قرار خواهد گرفت.

جزئیات عمیق‌ تری درباره‌ی نحوه‌ی کارکرد درونی derivationها وجود دارد، اما در این مرحله کافی است بدانید که چنین عبارت‌هایی به مسیرهای انبار Nix ارزیابی می‌شوند.

درون‌گذاری رشته روی derivationها برای ارجاع به فرآورده‌های ساخت آن‌ها به عنوان مسیرهای سیستم‌فایل به هنگام اعلام derivationهای جدید استفاده می‌شود.

این کار امکان ساخت ترکیب‌های پیچیده‌ی دلخواه از derivationها را با استفاده از زبان Nix فراهم می‌کند.

مثال‌های کاربردی

تا اینجا، مثال‌ها، تصویرسازی‌های مصنوعی از سازوکارهای زبان Nix بوده‌ام.

اکنون باید بتوانید کد زبان Nix مربوط به بسته‌ها و پیکربندی‌های ساده را بخوانید و توضیحات مشابهی برای مثال‌های عملی زیر ارائه دهید.

نکته

هدف تمرین‌های زیر این نیست که بفهمید کد چه چیزی را معنی می‌کند یا چطور کار می‌کند، بلکه این است که بدانید از نظر توابع، مجموعه‌های ویژگی و سایر انواع داده‌های زبان Nix چگونه ساختاریافته است.

محیط شل

{ pkgs ? import <nixpkgs> {} }:
let
 message = "hello world";
in
pkgs.mkShellNoCC {
 packages = with pkgs; [ cowsay ];
 shellHook = ''
 cowsay ${message}
 '';
}

این مثال یک محیط شل (که shellHook را هنگام مقداردهی اولیه اجرا می‌کند) را اعلام می‌کند.

توضیح:

  • این عبارت تابعی است که یک مجموعه ویژگی را به عنوان آرگومان خود می‌پذیرد.
  • اگر آرگومان دارای صفت pkgs باشد، در بدنه تابع استفاده خواهد شد. در غیر این صورت، به طور پیش‌فرض، عبارت Nix موجود در فایل یافت‌شده در مسیر جستجو یعنی <nixpkgs> (که در اینجا یک تابع است) را درون‌ریزی کنید، تابع را با یک مجموعه ویژگی خالی فراخوانی کنید، و از مقدار حاصل استفاده کنید.
  • نام message به مقدار رشته‌ای "hello world" متصل می‌شود.
  • صفت mkShellNoCC از مجموعه pkgs تابعی است که یک مجموعه ویژگی به عنوان آرگومان به آن پاس داده می‌شود. مقدار بازگشتی آن نیز نتیجه‌ی تابع بیرونی است.
  • مجموعه ویژگی ارسال‌شده به mkShellNoCC دارای صفات packages (که روی لیستی با یک عنصر تنظیم شده است: صفت cowsay از pkgs) و shellHook (که روی یک رشته تورفته تنظیم شده است) می‌باشد.
  • رشته تورفته شامل یک عبارت درون‌گذاری‌شده است که مقدار message را برای تولید "hello world" گسترش می‌دهد.

پیکربندی NixOS

{ config, pkgs, ... }: {

 imports = [ ./hardware-configuration.nix ];

 environment.systemPackages = with pkgs; [ git ];

 # ...

}

این نمونه (بخشی از) یک پیکربندی NixOS است.

توضیح:

  • این عبارت تابعی است که یک مجموعه ویژگی را به عنوان آرگومان می‌گیرد. این تابع یک مجموعه ویژگی برمی‌گرداند.
  • آرگومان باید حداقل دارای صفات config و pkgs باشد و ممکن است صفات بیشتری داشته باشد.
  • مجموعه ویژگی بازگردانده‌شده حاوی صفات imports و environment است.
  • imports فهرستی با یک عنصر است: مسیری به فایلی در کنار این فایل Nix، به نام hardware-configuration.nix.

نکته

موضوع imports تابع داخلی ناخالص import نیست، بلکه یک نام صفت معمولی است!

  • environment خود یک مجموعه ویژگی با یک صفت systemPackages است که به فهرستی با یک عنصر ارزیابی خواهد شد: صفت git از مجموعه pkgs.
  • آرگومان config استفاده نشده است (یا نشان داده نشده که استفاده شود).

بسته

{ lib, stdenv, fetchurl }:

stdenv.mkDerivation rec {

 pname = "hello";

 version = "2.12";

 src = fetchurl {
 url = "mirror://gnu/${pname}/${pname}-${version}.tar.gz";
 sha256 = "1ayhp9v4m4rdhjmnl2bq3cibrbqqkgjbl3s7yk2nhlh8vj3ay16g";
 };

 meta = with lib; {
 license = licenses.gpl3Plus;
 };

}

این مثال یک اعلامیه بسته (ساده‌شده) از مجموعه‌ی بسته‌های نیکس (Nixpkgs) است.

توضیح:

  • این عبارت یک تابع است که یک مجموعه ویژگی دریافت می‌کند که باید دقیقاً دارای صفات lib، stdenv و fetchurl باشد.
  • این تابع نتیجه ارزیابی تابع mkDerivation را بازمی‌گرداند که صفت stdenv است و روی یک مجموعه بازگشتی اعمال شده است.
  • مجموعه بازگشتی ارسال‌شده به mkDerivation از صفات pname و version خودش در آرگومان تابع fetchurl استفاده می‌کند. خود fetchurl از آرگومان‌های تابع بیرونی می‌آید.
  • صفت meta خودش یک مجموعه ویژگی است که در آن صفت license مقداری را دارد که به صفت تو در توی lib.licenses.gpl3Plus اختصاص داده شده بود.

منابع

گام‌های بعدی

انجام کارها

اگر می‌خواهید فاصله طولانی‌تری از یادگیری Nix بگیرید، می‌توانید فرآورده‌های ساخت استفاده‌نشده را با دستور زیر از انبار Nix پاک کنید:

$ nix-collect-garbage

اطلاعات بیشتر

اگر مثال‌ها را دنبال کرده باشید، متوجه شده‌اید که خواندن زبان Nix ساختار کد را آشکار می‌کند، اما لزوماً به شما نمی‌گوید که معنای واقعی کد چیست.

اغلب اوقات نمی‌توان از روی کد موجود موارد زیر را تشخیص داد:

  • نوع داده‌ی یک مقدار نام‌گذاری‌شده یا آرگومان تابع.
  • نوع داده‌ای که یک تابع فراخوانی‌شده برای آرگومان خود می‌پذیرد.
  • چه صفاتی در یک مجموعه ویژگی مشخص وجود دارند.

مثال:

{ x, y, z }: (x y) z.a

چگونه می‌دانید...

  • که x تابعی خواهد بود که با دریافت یک آرگومان، یک تابع برمی‌گرداند؟
  • که با فرض اینکه x یک تابع است، y یک آرگومان مناسب برای x خواهد بود؟
  • که با فرض اینکه (x y) یک تابع است، z.a یک آرگومان مناسب برای (x y) خواهد بود؟
  • که z اصلاً یک مجموعه ویژگی است؟
  • که با فرض اینکه z یک مجموعه ویژگی است، دارای صفت a خواهد بود؟
  • که y و z.a از چه نوع داده‌ای خواهند بود؟
  • نوع داده‌ی نتیجه‌ی نهایی چیست؟

و فراخواننده‌ی این تابع چگونه می‌داند که این تابع به یک مجموعه ویژگی با صفات x، y، z نیاز دارد؟

پاسخ دادن به چنین سؤالاتی مستلزم دانستن زمینه‌ای است که قرار است یک عبارت مشخص در آن استفاده شود.

بوم‌سازگان و سبک نگارش کد Nix بر اساس قراردادها هدایت می‌شوند. بیشتر نام‌هایی که در کدهای زبان Nix با آن‌ها مواجه خواهید شد از Nixpkgs می‌آیند:

  • قرص‌های Nix - توضیحی دقیق دربارهٔ درایویشن‌ها و نحوهٔ ساخت Nixpkgs از اصول اولیه

Nixpkgs مکانیزم‌های ساخت عمومی را فراهم می‌کند که به‌طور گسترده استفاده می‌شوند:

  • stdenv - و مهم‌تر از همه mkDerivation
  • کمک‌رسان‌های ساخت - برای ایجاد درایویشن‌ها، از جمله اسکریپت‌های شل و فایل‌های تک‌فایلی

بسته‌های Nixpkgs را می‌توان از طریق مکانیزم‌های متعددی تغییر داد:

  • [بازنشانی‌ها] به‌طور خاص override و overrideAttrs برای تغییر دادن بسته‌های منفرد
  • اورلی‌ها (overlays) برای تولید یک واریانت سفارشی از Nixpkgs با بسته‌های تغییریافته‌ی فردی

بوم‌سازگان‌ها و چارچوب‌های زبانی مختلف برای انطباق با Nixpkgs نیازمندی‌های متفاوتی دارند:

  • زبان‌ها و چارچوب‌ها ابزارهای ارائه‌شده توسط Nixpkgs برای ساخت بسته‌های مخصوص زبان یا چارچوب با Nix را فهرست می‌کند.

توزیع لینوکس NixOS از یک سیستم ماژولار پیکربندی استفاده می‌کند که قراردادهای خاص خود را تحمیل می‌کند.

nix.dev/tutorials/nix-language

نیکسی · یادداشت‌های فارسی Nix local fonts