I'm exploring the Typescript type system by implementing the Fantasy Land Spec and I ran into an issue while trying to implement the spec for Semigroup.
The spec stipulates that a Semigroup
should adhere to the following type definition:
concat :: Semigroup a => a ~> a -> a
I understand this to mean that a type a
, which implements Semigroup
, should have a concat
method that takes in a parameter of type a
and returns a parameter of type a
.
The only way I could think of expressing this type definition in TypeScript is this:
interface Semigroup {
concat(other: this): this;
}
But when I try to implement this interface on a class, like this:
class Sum implements Semigroup {
constructor(readonly num: number) {}
concat(other: Sum): Sum {
return new Sum(this.num + other.num);
}
}
I get a compiler error telling me that:
Property 'concat' in type 'Sum' is not assignable to the same property in base type 'Semigroup'.
Type '(other: Sum) => Sum' is not assignable to type '(other: this) => this'.
Type 'Sum' is not assignable to type 'this'.
'Sum' is assignable to the constraint of type 'this', but 'this' could be instantiated with a different subtype of constraint 'Sum'.(2416)
Thanks to this S/O answer, I think I understand the problem.
I think the compiler is essentially telling me: your interface says that you should be taking a parameter that is of the concrete type this
(Sum
in this particular case), but a class that extends Sum
could also be passed in.
However, I don't know how to fix it. That is, I don't know how to express the type definition for Semigroup
in TypeScript. How to reference the implementing class from an interface?
Here is a link to a TS Playground.
Update
@Guerric P's answer made me think of a partial solution. Guerric's solution was to use a generic on the interface. This solution makes implementing the Semigroup
spec possible, as shown here, but the interface doesn't really enforce it.
The fantasy land further describes the spec as follows:
s.concat(b)
/**
* `b` must be a value of the same `Semigroup`
*
* If `b` is not the same semigroup, behaviour of `concat` is
* unspecified.
*
* `concat` must return a value of the same `Semigroup`.
*/
Instead of making b
a generic, I figured that we could at least restrict the type to Semigroup
. That way it enforces the constraint that b
must be of type Semigroup
as shown here:
interface Semigroup {
concat(other: Semigroup): Semigroup;
}
But it still doesn't enforce that it must be of the SAME Semigroup
. I'm still looking for a way to do that with the TypeScript type system.