Decodal/doc/manual/souce/language/operators.md
2026-06-16 01:27:54 +09:00

3.2 KiB

合成演算子

この章では、&// の意味を定義する。

&: 制約合成

& は値・制約・構造を合成する演算子である。

A & B

基本規則:

  • 両方が制約なら、両方を満たす制約になる。
  • 制約と具体値なら、具体値が制約を満たす必要がある。
  • 両方が同じ具体値なら、その値になる。
  • 両方が異なる具体値なら conflict になる。
  • 両方が object なら、フィールドごとに合成する。
  • 同じフィールドが両方にある場合、そのフィールド値を & で合成する。
  • 矛盾が発生した場合はエラーになる。

例:

Port = Int & >= 1 & <= 65535;
NarrowedPort = Port & > 443;

MyConfig = {
    port = NarrowedPort default 8080;
};

Config = MyConfig & {
    port = 8000;
};

Config.port は概念的には以下になる。

NarrowedPort & 8000

8000NarrowedPort を満たすため成功する。

一方、以下は失敗する。

BadConfig = MyConfig & {
    port = 80;
};

80> 443 を満たさないためである。

//: patch 合成

// は右辺優先の構造的 patch 演算子である。 & が制約を保った合成であるのに対し、// は設定やスキーマを上書き・変更するために使う。

A // B

基本規則:

  • 両方が object なら、フィールドごとに再帰的に patch する。
  • 同じフィールドが object/object なら、さらに再帰的に patch する。
  • 同じフィールドが object/object 以外なら、右辺で置き換える。
  • 左辺にしかないフィールドは保持する。
  • 右辺にしかないフィールドは追加する。
  • 配列はデフォルトでは右辺で置き換える。
  • 関数値はデフォルトでは右辺で置き換える。

つまり // は shallow merge ではなく deep patch とする。

Base = {
    feature_hoge = {
        enable = Bool default true;
        fuga = Int default 10;
    };
};

Patched = Base // {
    feature_hoge = {
        enable = false;
    };
};

Patched は以下に相当する。

{
    feature_hoge = {
        enable = false;
        fuga = Int default 10;
    };
}

ドットパスを使うと以下のようにも書ける。

Patched = Base // {
    feature_hoge.enable = false;
};

object 全体の置換

// が deep patch である場合、object 全体を置き換えたいときの escape hatch が必要になる。

候補として、replace(...) を組み込み関数として提供する。

Replaced = Base // {
    feature_hoge = replace({
        enable = false;
    });
};

この場合、feature_hoge は再帰 patch されず、右辺の object に丸ごと置き換わる。

&// の使い分け

& は制約を満たす具体化に使う。

ValidConfig = MyConfig & {
    port = 8000;
};

// は既存構造の上書きや変形に使う。

ModifiedSchema = MyConfig // {
    port = Int default 9000;
};

// は右辺優先の patch であり、左辺の制約を常に保持するとは限らない。 制約を保持したい場合は & を使う。