فا نیکسی

مبانی زبان Nix

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

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

  • Nixpkgs

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

  • NixOS

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

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

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

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

نمای کلی

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

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

  • زبان: نحو و معناشناسی
  • کتابخانه‌ها: builtins و pkgs.lib
  • ابزارهای توسعه‌دهنده: تست، اشکال‌زدایی، لینت کردن، قالب‌بندی، ...
  • مکانیزم‌های ساخت عمومی: stdenv.mkDerivation، کمک‌رسان‌های ساخت، ...
  • مکانیزم‌های ترکیب و پیکربندی: override، overrideAttrs، اورلی‌ها، 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 موجود در آن را ارزیابی می‌کند. مقدار حاصل به عنوان خروجی چاپ می‌شود.

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

نکته

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

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

نکته

زبان Nix از ارزیابی تنبل استفاده می‌کند و 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; }; }; }

نکاتی درباره‌ی فاصله سفید

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

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

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

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 می‌توانند به نام‌های تازه‌اعلام‌شده دسترسی داشته باشند. این اتصالات دارای محدوده محلی (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)

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

مثال:

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

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

مثال:

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

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

مثال:

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

with ...; ...

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

مثال:

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 میانبری برای انتساب مقدار یک نام از محدوده‌ی موجود به همان نام در یک محدوده‌ی تو در تو است. این امکان برای راحتی کار فراهم شده است تا از تکرار چندین‌باره‌ی یک نام جلوگیری شود.

مثال:

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 تو در تو را خواهید دید که از نام‌های محدوده بیرونی (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"

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

هشدار

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

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

در چنین مواردی، استفاده از نام‌های حاصل از عبارت 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

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

مثال‌های زیر فرض می‌کنند فایل نیکس حاوی آن در /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، سومین راه برای اختصاص دادن نام‌ها به مقادیر هستند. قابل توجه است که مقادیر از پیش مشخص نیستند: نام‌ها در واقع جاهای خالی (placeholder) هستند که هنگام فراخوانی توابع پر می‌شوند.

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

  • تک‌آرگومان
  x: x + 1
  • آرگومان‌های متعدد از طریق تو در تو سازی
    x: y: x + y
    ```

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

```nix
  { a, b }: a + b
  • با صفت‌های پیش‌فرض
    { a, b ? 0 }: a + b
    ```

- با اجازه داشتن صفات اضافی

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

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

```nix
  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|

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

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

آرگومان‌های ساختارشکسته شده می‌توانند مقادیر پیش‌فرضی برای صفت‌ها داشته باشند.

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

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

مثال:

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 یک مسیر به یک فایل Nix را دریافت می‌کند، آن را می‌خواند تا عبارت Nix موجود در آن را ارزیابی کند، و مقدار حاصل را برمی‌گرداند. اگر مسیر به یک پوشه اشاره کند، به جای آن فایل 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 باشد، توابع درون‌ریزی‌شده (import) را می‌توان بلافاصله روی آرگومان‌ها اعمال کرد.

هر زمان که توکن‌های اضافی را پس از فراخوانی 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 با همین نام هستند.

ناخالصی‌ها

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

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

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

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

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

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

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

نکته

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

مسیرها

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

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

مثال:

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

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

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

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

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

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

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

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

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

زبان 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 می‌آیند.

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

توضیح تفصیلی این مثال، عبارت 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 - توضیحی مفصل درباره‌ی derivationها و نحوهٔ ساخت Nixpkgs از اصول اولیه

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

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

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

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

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

مشارکت‌کنندگان