Chapter 02

Nix言語:純粋関数型DSLの深淵

JSONの表現力、Lispの柔軟性、そしてHaskellの厳密さを備えた、
構成管理のためのチューリング完全なドメイン固有言語。

1. 純粋関数型であることの意味:参照透過性と数学的証明

Nix言語は、単なる設定ファイル形式(JSONやYAML)ではありません。その実体は、副作用を徹底的に排除した 純粋関数型言語 です。PythonやBashのような命令型言語に慣れたユーザーにとって、Nixの「すべてが式(Expression)であり、値である」という性質は、大きなパラダイムシフトを要求します。

Nixにおける「純粋」とは、関数の出力がその入力のみに依存し、実行時の時刻やネットワークの状態、ファイルシステムの不確定な要素に左右されないことを指します。この性質を 参照透過性 (Referential Transparency) と呼び、プログラム内の任意の式をその評価結果で置き換えても、全体の意味が変わらないことを保証します。

Church-Rosser 性と並列評価

Nixが純粋であることの最大の恩恵は、計算の「合流性(Confluence)」にあります。式の評価順序が結果に影響を与えないため、Nixは大規模なパッケージグラフを安全に並列評価(Memoizationを伴う)することができます。これは、数万のパッケージを抱える Nixpkgs において、パフォーマンスを維持するための論理的基盤です。

Nixには「変数への再代入」が存在しません。一度定義された名前と値の組み合わせは、そのスコープ内で不変です。この制約があるからこそ、NixOSは「1ヶ月前にビルドした環境」と「今日ビルドした環境」が、数学的に同一であることを保証できるのです。

2. Attribute Sets:名前空間の管理と継承の幾何学

Nixにおいて、あらゆるデータ構造の基本となるのが Attribute Set (attrset) です。JSONのオブジェクトや他連想配列に相当しますが、Nixではこれを極めて高度に利用します。

Basic Attrset and Inherit
let
  version = "2.40.1";
in {
  # 現在のスコープから名前を継承
  inherit version; 
  
  # 特定のセットから抽出して継承
  inherit (pkgs) git vim;

  # ドット表記によるネスト定義
  services.nginx.enable = true;
}

NixOSの configuration.nix 自体も、一つの巨大な Attribute Set を引数に取り、システムの状態を記述した別の Attribute Set を返す関数に過ぎません。この構造により、数千の設定項目を整然とネストさせ、名前の衝突を避けながら管理することが可能になります。

Recursive Attribute Sets (rec) の内部挙動

通常、attrset内では自分自身の他のキーを参照することはできませんが、rec キーワードを用いることで自己参照が可能になります。しかし、rec 内の参照は評価時に固定されるため、後のChapter 11で学ぶ「オーバーライド」の際に柔軟性を欠くことがあり、上級者はあえて let ... in を好む傾向にあります。

3. 高階関数とカリー化:ラムダ計算の応用

Nixの関数はすべて 第一級オブジェクト です。そして、数学的な厳密さを維持するため、Nixの関数は常に「ただ一つの引数」しか受け取りません。複数の引数を取るように見える関数は、実際には「引数を取り、別の関数を返す関数」の連鎖として表現されます。これが カリー化 (Currying) です。

f: (x, y) \rightarrow z \quad \Rightarrow \quad f: x \rightarrow (y \rightarrow z)
Partial Application in Nix
let
  add = a: b: a + b;     # カリー化された関数
  addFive = add 5;       # 部分適用(a=5を固定した新しい関数が作られる)
in
  addFive 10             # 結果は 15

このカリー化により、Nixpkgsのライブラリは驚異的な再利用性を実現しています。共通の設定を部分適用した「ベースとなるパッケージ定義」を、各環境ごとにカスタマイズして展開する手法は、Nix開発における標準的なパターンです。

4. 遅延評価とサンク:計算の「経済性」と不動点

Nixの性能と柔軟性を支える最大の特徴が 非正格 (Non-strict) 、すなわち 遅延評価 (Lazy Evaluation) です。Nixは「値が必要になるその瞬間」まで、計算を一切行いません。

Nixpkgsには現在、8万を超えるパッケージが定義されています。もしNixが先行評価を採用していたなら、システムを評価するたびに数GBのメモリを消費し、全パッケージ定義を計算しなければなりません。しかし実際には、あなたが environment.systemPackages に記述したわずかな定義のみが評価されます。

Thunk(サンク)と不動点演算子

未評価の式はメモリ内で Thunk として保持されます。遅延評価の真価は、再帰的なデータ構造の定義にあります。NixOSのモジュールシステムにおいて、各モジュールが互いの設定を参照し合えるのは、lib.fix (不動点演算子)による遅延解決のおかげです。

この性質により、存在しないパスを参照していても、そのパスが結果として使われない限りエラーにはなりません。この「計算の経済性」が、巨大な Nixpkgs リポジトリを単一のデータソースとして扱うことを可能にしています。

5. プリミティブと拡張:builtins vs nixpkgs.lib

Nix言語は意図的にシンプルに保たれています。言語の核となる機能は、C++で実装された builtins (文字列操作、ハッシュ計算、I/O等)として提供されます。

Builtins Example
# JSONのパース
builtins.fromJSON ''{"version": "1.0"}''

# 文字列の置換
builtins.replaceStrings ["foo"] ["bar"] "foobar"

対して、読者がNixOSの設定を書く際に出会う lib.mkIflib.mkMerge は、Nix言語自身で書かれた標準ライブラリ nixpkgs.lib の成果です。この「小さなコアの上に、柔軟なライブラリを重ねる」構造が、OSの設定という極めて複雑なドメインを、エレガントに記述することを可能にしています。