Suppose I have an inductive type of arithmetical expressions exp
Inductive exp : Type :=
| num : nat -> exp
| plus : exp -> exp -> exp
| minus : exp -> exp -> exp
| mult : exp -> exp -> exp
| div : exp -> exp -> exp.
and I want to define a function expsum
that returns the sum of all numbers that occur in an exp
. The obvious implementation is
Fixpoint expsum (e : exp) : nat :=
match e with
| num n => n
| plus e1 e2 => expsum e1 + expsum e2
| minus e1 e2 => expsum e1 + expsum e2
| mult e1 e2 => expsum e1 + expsum e2
| div e1 e2 => expsum e1 + expsum e2
end.
But for constructors plus
, minus
, mult
, and div
, expsum
does exactly the same thing after pattern matching. I'd like to simplify it into something like
Fixpoint expsum (e : exp) : nat :=
match e with
| num n => n
| [ plus e1 e2 | minus e1 e2 | mult e1 e2 | div e1 e2 ]
=> expsum e1 + expsum e2
end.
so that a single clause takes care of multiple constructors. I think I've seen this done in other functional languages. Is this possible in Coq?
This can't be done in the term language. Coq's language is very powerful in itself, thanks to its dependent types, but it isn't its own metalanguage; there's no way to write a Coq term that manipulates Coq constructors as such (only as terms, and that's not good enough to build a pattern matching).
There could be a way to do this in the vernacular (the toplevel language in which you write definitions of terms, proofs in the tactic language, etc.), but I don't think there is. If it existed anywhere, I'd expect it to be in
Program
. But applying the same pattern to constructors that happen to have the same type is a rather specialized need.It can be done with the proof language. Proofs in Coq are just terms; tactics that help with repetitive proofs can help in the same way with repetitive terms.
This could be generalized to variable arity by using the
match goal
tactical construct to analyze the arguments of each constructor and building the result term accordingly.While this works, it's tricky. Tactics are geared towards writing proofs, where the computational content is irrelevant. When you use them to write terms whose actual definition matters (as opposed to just the type), you need to be very careful to ensure that you're defining the term that you expected, rather than some other term that happens to have the same type. As you've probably been thinking for a few minutes now, that code doesn't win readability awards.
I don't recommend this method in general, because it's error-prone. But it can be useful when you have many similar types and functions and the types change during development. You end up with rather unreadable tacticals, but once you've debugged them, they can work even when you tweak the expression types.