I’d argue that it makes the language smaller because it separates compiler features from language features.
- Deriving is a compiler features to stamp out code for you. It doesn’t affect language semantics since you could just write all those instances by hand.
- Newtype is a compiler feature that affects code generation. It doesn’t affect language semantics since the same code will continue to terminate or not terminate.
- Roles are a compiler feature that affect resolution of a compiler solved typeclass. It doesn’t affect language semantics because zero-cost coercions are not necessary for program soundness.
I think it’s desirable to keep language semantics small (including syntax), but I think there will always be a desire to introduce compiler features that make development faster and easier. Syntactic changes incur a heavy burden because they necessarily break any syntactic tooling, even for small contextual keywords. I think it’s desirable, especially if users want a 1.0 specification at some point, to have some way to introduce and iterate compiler features while keeping the language stable. AFAIK no one on the core team wants to introduce pragmas for any features. I’m not saying annotations are necessarily the solution, but I think they have the potential to fill that need.
I agree that adding more syntax for each change is not ideal, but it’s not clear to me that adding a language of annotations is a better solution, because it relies on the compiler understanding every possible annotation, so it is the opposite of extensible. Having a unified syntax suggests that these things are somehow the same as each other, but they are not. You could build it in such a way that the language of annotations is open, with a core set of magic annotations, but that actually seems worse to me than what we have now, since at least something which invokes magic in the compiler is distinguished by the “derive” keyword (I don’t count auto-solved instances as the same sort of magic as e.g. derived generic or newtype instances).
I think a lot of this is up to taste.
because it relies on the compiler understanding every possible annotation, so it is the opposite of extensible
I don’t see how it follows that this is the “opposite” of anything. Nothing has to be anything. It doesn’t have to be extensible, but we do try to encourage users to try implementing things as external tooling, and I can see how extensible annotations might help with that.
Having a unified syntax suggests that these things are somehow the same as each other, but they are not.
They are related by being compiler directives. What do you see as it suggesting? The nice thing about using the type language is that they have lexical rules (you need to import them from somewhere) and need to kind check. Maybe different declarations types require different kinds (DeclarationAnnotation
vs InstanceAnnotation
). If they are extensible, then you can trace it to a support library for some tool.
but that actually seems worse to me than what we have now, since at least something which invokes magic in the compiler is distinguished by the “derive” keyword
I think compiler magic is sufficiently made clear by virtue of being in Prim
.