Scenario: new ind
syntax
As far as I know, in Coq, you cannot create new inductive types by only using expressions. You must use commands--namely, the Inductive
command.
Suppose we want to turn Coq into a language with only expressions, and no commands. This requires us to remove the Inductive
command (as well as all the other commands, of course). In its place, we introduce an ind
expression. The syntax might look like
ind <name> <params> : <arity> with
| case1 <args> : <name> <indices>
| case2 <args> : <name> <indices>
| <...>
end
We also introduce syntax for accessing constructors: <ind_exp>::<constr_name>
.
Example
To make things clear, here's a more concrete example. Suppose we have the existing Coq code:
Inductive nat : Set := | zero | succ (pred : nat).
Definition one := succ zero.
Definition ident := fun (n : nat) => n.
If we use the new syntax, the code for the expressions one
and ident
respectively become:
one
:(ind nat' : Set with | zero : nat' | succ (pred : nat') : nat' end)::succ (ind nat' : Set with | zero : nat' | succ (pred : nat') : nat' end)::zero
ident
:fun( n : (ind nat' : Set with | zero : nat' | succ (pred : nat') : nat' end) ) => n
Question
Does this new "inline inductive definition expression" syntax introduce any logical inconsistencies?
Assume that the same restrictions that apply to the old Inductive
syntax (e.g., universe consistency, strict positivity) also apply to the new ind
syntax.
EDIT: Background info (for context).
This section might be TMI, as some parts are only distantly related to the above question. You can skip it if you want. But if you're curious, read on.
I'm designing a programming language (as a personal project) where everything is an expression. Going the "everything is an expression" route was a somewhat arbitrary design choice--mostly a matter of personal preference.
The language will be a very simple "kernel" language. The main goal is maximizing the ease of compiler implementation. This language will serve as a target language for higher level languages I write in the future. Since code in this language will be computer generated, the language does not need to be "human friendly".
Regarding Andrej Bauer's answer:
How are inductive types compared for equality? With named inductive types we just compare the names.
Two inductive types are equal iff they are identical after normalization. Internally, the language uses De Bruijn indices, and constructors are represented with numbers instead of names. Thus, names are irrelevant.
This has the drawback of making ind bool with true | false end
equal to ind pole with north | south end
. To fix this, ind
expressions have a "discriminator string". Even after names are converted to numbers, the discriminator strings are left intact. This lets us distinguish between ind bool "bool" with true | false end
and ind pole "pole" with north | south end
.
So the true ind
syntax is actually
ind <name> "<discrim_str>" <params> : <arity> with
| case1 <args> : <name> <indices>
| case2 <args> : <name> <indices>
| <...>
end
However, since this detail isn't relevant to the question, I elided it in the original post, to keep things simple.
Do you expect the user to always write 〈huge-expression〉:: cow?
Yes. But since the code is computer-generated, this is not a problem.
Should we worry about inefficiency, because types might become very large? At the very least we need to make sure there is lots of common subexpression sharing, and we must avoid checking the same equalities over and over.
You bring up a great point! The compiler makes heavy use of caching (or I should say, it will, once I'm done building it). Most of the time, it just compares hashes instead of comparing the entire (massive) expression. It uses shared references to avoid exponential memory consumption.
By the way, inductive type definitions are used not only for definitions of (inductive) recursive types, but also for simple sums ... If we take these away, we should consider expression-style sum types.
As far as I can tell, ind
expressions already cover this case. If we translate your OCaml code to our new hypothetical language, we obtain:
(* sum *)
fun (A B : Set) => ind sum with
| inl (a : A) : sum
| inr (b : B): sum
end
(* tree *)
fun (A : Set) => ind tree with
| empty : tree
| inl (a : A) (left right : tree) : tree
end