gadt が実際には adt のように動作する場合があるため、型コンストラクターには別の名前を付ける必要があります。
タイプを次のように定義したいとします。
type 'canonical boolean_expression =
| Bool : bool -> canonical boolean_expression (* here I assume that you wanted to have that case too *)
| Var : int -> not_canonical boolean_expression
| Not : 'canonical boolean_expression -> 'canonical boolean_expression
| BinOpC : canonical boolean_expression * binary_operator * canonical boolean_expression -> canonical boolean_expression
| BinOpNC : _ boolean_expression * binary_operator * _ boolean_expression -> not_canonical boolean_expression
;;
少し変更を加えたタイプを考えてみましょう。
type 'canonical boolean_expression =
| Bool : bool -> canonical boolean_expression
| Var : int -> not_canonical boolean_expression
| Not : 'canonical boolean_expression -> 'canonical boolean_expression
| BinOpC : canonical boolean_expression * binary_operator * canonical boolean_expression -> canonical boolean_expression
| BinOpNC : 'a boolean_expression * binary_operator * 'a boolean_expression -> 'a boolean_expression
;;
canonical boolean_expression
これで、最後の 2 つのコンストラクターのいずれかを使用するバイナリ操作になる可能性があります。明らかに、結果の値の型を任意に選択することで得られる自由には、その結果があります。「型ケース」が重複するガジェットを作成できます。その場合、これらの値のいずれかを取る関数は、両方のケースをテストする必要があります。したがって、コンストラクターの名前を同一にすることはできません。なぜなら、型だけではどの値を扱っているかを知るのに十分ではない可能性があるからです。
たとえば、以下の関数:
let rec compute = function
| Bool b -> b
| BinOpC (a,And,b) -> compute a && compute b
| BinOpC (a,Or,b) -> compute a || compute b
;;
あなたの定義を考えると、問題なく正規表現を型チェックして処理する必要があります。BinOpNC
私の変更により、コンパイラーは当然のことながら、式も含まれている可能性があると文句を言いcanonical
ます。
これは、gadt のこの側面を公開するのはばかげた例のように思えます (そして実際にそうです)。なぜなら、ブール式の変更された定義は (意味的に言えば) 明らかに正しくありませんが、コンパイラは型の実用的な意味を気にしません。
本質的に、gadt は依然として adt です。適切なアクション コースを選択するためにコンストラクターに頼らなければならない状況が依然として存在する可能性があるからです。