I’m a little bit concerned about the direction we’re taking with respect to class and instance declarations. It is mainly about readability and intention of the code. Let me first summarise two current issues and state my concerns, after which I’ll introduce a possible way to ease the pain.
Kind annotations and class inheritance
First, since PureScript 0.14, we can add kind annotations to datatypes, newtypes and classes.
When browsing some source code, I stumbled upon this (there are more examples):
class Category :: forall k. (k -> k -> Type) -> Constraint class Semigroupoid a <= Category a where identity :: forall t. a t t
I don’t know if I’m alone in this, but reading this code from top to bottom boggles my mind. It goes like this:
- “Ah, we’re declaring a
- “Oh wait! It’s the
- “Oh sorry.
Semigroupoidis the superclass of the
I know, writing down the superclass before the new class is the way we always did it,
is the way Haskell does it for some decades, but…
- Now that we have kind annotations, they don’t align with the declaration.
- It makes me wonder what we are declaring here.
- It remembers me of reading C, where type annatations read backwards.
Instance declarations and
Second, a PR has been merged which removes of explicit instance names. The compiler can generate them. Wonderful! We’re also discussion the addition of
instance declarations, which helps avoiding problems with type variable scoping. Hurray! I guess the end result will be something like this:
instance forall a b. (Show a, Show b) => Show (Tuple a b) where ...
We’re repeating ourselves. We have to state that we’re declaring an instance for all
b, but the part after the
=>implicitly states the same thing.
Just as with class declarations, it reads backwards. We’re declaring an instance of
Tuples, and for that we need two other
Showconstraints. (But again, we did this, and Haskell did this, for decades now…)
The introduction of an explicit
forallfor instances, also raises the question: Shouldn’t we treat class declarations the same and add an explicit
forallthere too to aid type variable scoping?
class Category :: forall k. (k -> k -> Type) -> Constraint class forall a. Semigroupoid a <= Category a where ...
data declarations we do not need an explicit
forall, as the type variables are introduced together with the data declaration. When, for example, we write
data Tuple a b = ..., it is clear that we are introducing type variables
b, and we’re only allowed to use
b after the equals sign. We don’t have to repeat ourselves! Why shouldn’t we do the same thing with
So, what if we make the experience of entity declaration simpler and more uniform by starting every declaration with the name we are declaring, together with the variables that can be used in that declaration.
datadeclarations stay untouched;
classdeclarations start with the class we’re declaring together with their type variables, followed by their superclasses which can use the type variables introduced earlier
(as is custom in I think every language with some kind of oo-class or interface/protocol/trait inheritance except Haskell and Idris);
- do the same for
- drop the explicit
data Tuple a b = ... type Usually a = ... class Category :: forall k. (k -> k -> Type) -> Constraint class Category a | Semigroupoid a where ... instance Show (Tuple a b) | Show a, Show b where ...
I’m curious if other people’s minds have to do the same yoga when reading current class and instance declarations, and if above proposal would enhance DX.