Regoのルール記述の注意点

# はじめに

Regoは一般的な手続き型プログラミング言語と言語仕様が大きく異なります。
こみいったルールを書いた際いくつか文法でハマったところがあった[^1]ので、注意点を備忘録としてまとめました。

# `A != B` と `not A == B` は意味が異なる

~~「`not` とかいう演算子使いどころなくね？」とか舐めてましたすみません~~

`A != B`は「`A != B`となるA, Bが存在する」ことを表します。一方、`not A == B` は「`A == B` となるA, Bが存在しない」ことを表します。違いが表れるのは `A` や `B` が**もともと存在しなかった場合**です。

```json:input
{
    "values": [
    	{"a": 1, "b": 1},
    	{"a": 1, "b": 2},
   		{"a": 1}
    ]
}
```

```rego
not_equals1[n] {
	obj := input.values[n]
	obj.a != obj.b
}

not_equals2[n] {
	obj := input.values[n]
	not obj.a == obj.b
}
```

```json:output
{
    "not_equals1": [
        1
    ],
    "not_equals2": [
        1,
        2
    ]
}
```

`input.values[2]` はそもそも `b` を持っていないので、 `not obj.a == obj.b` の場合にしか該当しません。
 
# 参照した要素が存在しない場合ルールに該当しなくなる

[conftest](https://www.conftest.dev/)でエラーメッセージを表示しようとしたときにハマりました。見たい要素だけ切り取ってエラーメッセージに出そうとすると、**その要素を持っていない場合は無視されてしまいます**。

例えば、先ほどの値の比較の例を考えてみます。

```json:input
{
    "values": [
    	{"a": 1, "b": 1},
    	{"a": 1, "b": 2},
   		{"a": 1}
    ]
}
```

```rego
not_equals2[msg] {
	obj := input.values[n]
	not obj.a == obj.b
    msg := sprintf("values[%d]: a must equal to b: a: %d, b: %d", [n, obj.a, obj.b])
}
```

```json:output
{
    "not_equals2": [
        "values[1]: a must equal to b: a: 1, b: 2"
    ]
}
```

`input.values[2]` の場合が消えてしまいました。これは、`sprintf` 内で `obj.b` を参照したために、このフィールドが存在しない `values[2]` の場合がルールに該当しなくなってしまったためです。

`sprintf`を使う場合は、**可能な限り大きな範囲のプロパティのみ参照しましょう**。

```rego
not_equals2[msg] {
	obj := input.values[n]
	not obj.a == obj.b
    msg := sprintf("values[%d]: a must equal to b: obj: %v", [n, obj])
}
```

```json
{
    "not_equals2": [
        "values[1]: a must equal to b: obj: {\"a\": 1, \"b\": 2}",
        "values[2]: a must equal to b: obj: {\"a\": 1}"
    ]
}
```

また、この仕組みを逆手にとって

```
obj.a
```

のような一見意味の無さそうな式文を書くことで `a` が存在する場合のみを絞り込むことも可能です。


# 変数は特定の値を代入しているわけではない

一般的なプログラミング言語のように、「変数nに何か特定の整数値を代入して次の行に移る」という読み方をすると面食らいます。
Regoには行の評価に順序は無く、変数は条件を満たす値の集合を表します。
そのため、「変数nは以下を満たす整数の集合である」のように捉えたほうが分かりやすいと思います。

アルゴリズムを考えず、定義だけ宣言すればよいので慣れると便利です。

```rego
# 整数nが以下を満たすとき、nは合成数であるという
composite_numbers[n] {
    # ある整数n, 整数n > m > 1が存在し
	# NOTE: 無限ループにならないように、範囲をinput.numbersに制限（本質ではない）
	n := input.numbers[i]
	m := input.numbers[j]
	n > m
	m > 1
    # mはnの約数である
	n % m == 0
}

# 整数nが以下を満たすとき、nは素数であるという
prime_numbers[n] {
	n := input.numbers[i]
	n != 1
	not composite_numbers[n]
}
```

```json:input
{
    "numbers": [
        1,
        2,
        ...,
        100
    ]
}
```

```json:output
{
    "prime_numbers": [
        2,
        3,
        5,
        7,
        11,
        13,
        17,
        19,
        23,
        29,
        31,
        37,
        41,
        43,
        47,
        53,
        59,
        61,
        67,
        71,
        73,
        79,
        83,
        89,
        97
    ]
}
```

# ルールは分割して書ける

ルールを複数書けば、OR条件としてマージされた結果が得られます。

```rego
# グロタンディーク素数
prime_numbers[57]
```

```json:output
{
    "prime_numbers": [
        2,
        3,
        ...,
        53,
        57,
        59,
        ...,
        97
    ]
}
```

# 再帰が書けない

そろそろ宣言的プログラミングの感覚がつかめたところで、「よし、Prologみたいに書けばいいんだな」と再帰を使うと撃沈します。

```rego
fact[0] := 0

fact[n] := v if {
	n := input.values[_]
	v := n * fact[n-1]
}
```

```json:input
{
    "values": [1,2,3,4,5]
}
```

```
1 error occurred: policy.rego:25: rego_recursion_error: rule data.play.fact[__local0__] is recursive: data.play.fact[__local0__] -> data.play.fact[__local0__]
```

チューリング完全性は意図的にふさがれていました[^turing]。

# おわりに

以上、Regoの個人的注意点の紹介でした。RegoはOPA以外でもいくつかのツールで使用可能なので、言語仕様を把握して使いこなせるようになりたいです。

[^1]: ~~「この記事の話全部、文法（syntax）ではなく意味論（semantics）の話では？」とお思いの方、おっしゃる通りです。分かりやすさ重視で文法と書きました,,,~~
[^turing]: 永遠に評価が終わらないリスクを回避するためだと思うのでやむなし