Belajar Nix
Apa itu Nix?
Membaca kata Nix mungkin muncul berbagai macam istilah, pertama yang terbiasa dengan Linux mungkin lazim dengan istilah Unix-like yang sering disebut sebagai *nix.
Namun, yang akan dipelajari dalam postingan blog ini yaitu package manager / manajer paket perangkat lunak atau aplikasi yang bisa dipasang pada beberapa sistem.
Paket perangkat lunak pada Nix didefinisikan melalui pemrograman fungsional yang lazy. lazy disini bermaksud lazy evaluation, strategi evaluasi ekspresi dimana evaluasi akan di delay sampai value nya benar-benar dibutuhkan.
Nix berawal dari beberapa paper yang berawal dari bahasa dan packages (Nix & nixpkgs) kemudian dilanjut ke sistem operasi NixOS
- Integrating Software Construction and Software Deployment
- Nix: A Safe and Policy-Free System for Software Deployment
- The Purely Functional Software Deployment Model
Persiapan
Pertama yang harus dilakukan adalah menginstal Nix pada sistem yang anda gunakan. Ada halaman official download Nix pada https://nixos.org/download/, yang akan memberi tahu cara instalasi pada sistem yang didukung seperti Linux, MacOS, Windows menggunakan WSL versi 2, dan Docker.
Khususnya untuk pengguna MacOS, saya merekomendasikan mengunakan installer dari Determinate Systems https://github.com/DeterminateSystems/nix-installer, lebih dapat diandalkan dan masih bisa digunakan jika kita melakukan upgrade Sistem Operasi.
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
Selain itu Determinate Systems installer juga menginstall Nix Flakes.
$ nix --version
nix (Nix) 2.18.1
$ nix flake show
path '/Users/sakti' does not contain a 'flake.nix', searching up
error: could not find a flake.nix file
Bahasa Nix
Untuk menyederhanakannya, anda bisa menganalogikan bahasa Nix sebagai JSON dengan fungsi.
nix adalah bahasa domain spesifik
Primitif / literal
Berikut tipe data yang ada pada Nix dan cara menulis nya secara literal/harfiah di kode Nix.
{
string = "hello"; # komentar
integer = 1;
float = 3.141;
# komentar ditengah
bool = true;
null = null;
list = [ 1 "two" false ];
attribute-set = {
a = "hello";
b = 2;
c = 2.718;
d = false;
}; # komentar
}
Untuk mengevaluasi kode diatas bisa menggunakan nix-instantiate
, sebagai contoh kode diatas disimpan sebagai primitif.nix
. Jalankan nix-instantiate --eval --strict primitif.nix
menghasilkan output:
{ attribute-set = { a = "hello"; b = 2; c = 2.718; d = false; }; bool = true; float = 3.141; integer = 1; list = [ 1 "two" false ]; null = null; string = "hello"; }
Operator
Operator yang dapat dipakai di Nix:
Sintaksis | Penjelasan |
---|---|
+, -, *, / |
operasi numerik |
+ |
menggabungkan string |
++ |
menggabungkan list |
== |
pengecekan. persamaan |
>, >=, <, <= |
operasi pembanding |
&& |
logis AND |
|| |
logis OR |
e1 -> e2 |
implikasi logis |
! |
negasi boolean |
set.attr |
akses atribut attr di atribut set set |
set ? attr |
mengetes apakah attr ada dalam atribut set set |
kiri // kanan |
menggabungkan atribut set kiri dan kanan , dengan kanan didahulukan |
{
c = 1 + 2;
} // {
d = 4;
}
menghasilkan
{ c = 3; d = 4; }
untuk mengevaluasi dengan atribut yang ada dalam set dapat menggunakan rekursif rec
, contoh:
rec {
a = 1;
b = 2;
c = a + b;
}
menghasilkan
{ a = 1; b = 2; c = 3; }
Fungsi
Nix mempunyai beberapa fungsi bawaan, seperti:
rec {
value = builtins.toString (10);
json = builtins.toJSON ({
name = "glados";
id = 42;
});
system = builtins.currentSystem;
robots = builtins.fetchurl "https://saktidwicahyono.name/robots.txt";
robots_content = builtins.readFile (robots);
}
jika dievaluasi menghasilkan
{ json = "{\"id\":42,\"name\":\"glados\"}"; robots = "/nix/store/kh9r31gaw9rkjamqh9fdxa90pcgx330h-robots.txt"; robots_content = "User-agent: *\nDisallow: /admin\n\nHost: saktidwicahyono.name\nSitemap: http://saktidwicahyono.name/sitemap.xml\n\n"; system = "aarch64-darwin"; value = "10"; }
Untuk mendeklarasikan fungsi yang kita definisikan sendiri, dapat menggunakan format berikut:
arg: "badan fungsi ${arg}"
dideklarasikan sebagai variable mengunakan variable bindings:
let
salam = ", selamat siang";
halo = nama: "halo ${nama}";
in
halo "doug" + salam
menghasilkan
"halo doug, selamat siang"
Argumen lebih dari satu (currying)
Fungsi dengan beberapa argument dapat menggunakan currying, yang berarti membuat fungsi dengan satu argumen, yang mengembalikan fungsi dengan argumen lain, yang mengembalikan ... dan seterusnya.
let
f = x: y: x * y;
in
f 4 2
fungsi f
merupakan fungsi perkalian yang menerima parameter x
yang mengembalikan fungsi yang menerima parameter y
.
Alternatif lain, menggunakan atribut set sebagai argumen, dengan ini kita dapat menentukan argumen default (sehingga pemanggil dapat menghilangkannya).
let salam = { nama, umur ? 42 }: "${nama} berumur ${toString umur} tahun";
in salam { nama = "Cave"; }
menghasilkan
"Cave berumur 42 tahun"
Selain itu, kita dapat menggunakan elipsis ...
, jika input yang diberikan berisi lebih banyak variabel daripada yang dibutuhkan untuk fungsi tersebut.
let
salam = { nama, umur, ... }: "${nama} berumur ${toString umur} tahun";
orang = {
nama = "Cave";
umur = 420;
email = "[email protected]";
};
in salam orang
# menghasilkan: "Cave berumur 420 tahun"
Nix juga mendukung penggabungan seluruh atribut menjadi satu parameter menggunakan sintaks @
:
let
fungsi = { nama, umur, ... }@args: builtins.attrNames args;
orang = {
nama = "Cave";
umur = 420;
email = "[email protected]";
};
in fungsi orang
# menghasilkan: [ "email" "nama" "umur" ]
Percabangan
Nix memiliki dukungan kondisional yang sederhana. Perhatikan bahwa if
adalah sebuah ekspresi dalam Nix, yang berarti bahwa kedua cabang harus ditentukan. Contoh kita ingin membuat fungsi untuk membedakan bilangan ganjil dan genap, untuk ini kita perlu nixpkgs
lib.
{ lib, ...}:
let
ganjilgenap = x:
if lib.trivial.mod x 2 == 0 then
"genap"
else
"ganjil";
in
[(ganjilgenap 3) (ganjilgenap 4)]
Cara menjalankan nya dengan menambahkan parameter --arg
:
$ nix-instantiate --eval --strict ganjilgenap.nix --arg lib '(import <nixpkgs> {}).lib'
[ "ganjil" "genap" ]
inherit
Kata kunci inherit digunakan dalam atribut set atau membiarkan binding untuk "mewarisi" variabel dari lingkup induk.
Singkatnya, pernyataan seperti inherit nama
; diperluas menjadi nama = nama;
.
let
nama = "Malenia";
saudara = {
kembar = "Miquella";
};
orangtua = {
ibu = "Queen Marika";
};
in {
inherit nama;
inherit (saudara) kembar;
inherit (orangtua) ibu;
}
# menghasilkan: { ibu = "Queen Marika"; kembar = "Miquella"; nama = "Malenia"; }
with
Pernyataan with
"mengimpor" semua atribut dari kumpulan atribut ke dalam variabel dengan nama yang sama. Hal ini mempersingkat kode, kita mengakses variabel secara langsung tanpa atribut akses:
let attrs = { a = 15; b = 2; };
in with attrs; a + b # tanpa attrs.a + attrs.b
# menghasilkan: 17
Bersambung
Dalam post selanjutnya:
- derivations,
mkDerivation
- membangun aplikasi dengan nix (flakes)
- membangun image docker dengan nix