Repository navigation
ADTs with inheritance #20144
Description
Activity
aluanhaddad commented
on Nov 20, 2017 ContributorMore actionsimplementsis an expression of intent and provides tooling assistance when implementing a class, but it does not affect type checking.This is because, unlike Scala which is nominally typed with the exception of the experimental
scala.language.reflectiveCalls, TypeScript is structurally typed.As you have noted, there is a static error at the call site meaning that type safety is uncompromised.
Is there a plan to allow discriminated union type matching feature with interfaces?
See the approach outlined in #9163, the pull request introducing the feature.
Is there a plan to allow type aliases to extend interfaces?
This can already be done
interface Tagged<K extends string> { kind: K; } interface Drawable { draw(): void; } type TaggedDrawableWithId<K extends string> = Tagged<K> & Drawable & { id: number };
And interfaces can also extends type aliases
interface Pentagon extends TaggedDrawableWithId<'pentagon'> { points: [number, number, number, number, number]; }
Note: questions like these are better asked on Stack Overflow or Gitter.
Aluan Haddad (@aluanhaddad) Thanks for the explanation. I'm a bit confused though:
-
Is there a way to get my
Shapehierarchy working with intersection types example you gave in the end so that the compiler complains at definition site (at theTriangledefinition) when there is nodraw()method implemented? -
Since
Drawablehas adrawmethod, TypeScript should be able to structurally enforce typing, in a sense to require all the unions of a type implementDrawable, as in Scala, or am I missing something here?
-
aluanhaddad commented
on Nov 20, 2017 ContributorMore actionsIf you want to remove the function so it doesn't exist at runtime, you might alternately write
type T = A | B; interface I { draw(): void; } type __test_T_is_I<U extends I = T> = {}; class A { draw() {} } class B {}
Or actually, you could inline the constraint like
type Shape<S extends I = A | B> = S; interface I { draw(): void; }
but whichever variant you go with, I think Andy (Andrewkraft) (@Andy-MS)'s suggestion is the right approach.
Reacted by Ali AfroozehAndy (Andrewkraft) (@Andy-MS) Very nice solution, thanks!
I think it is also possible to implement it into the language, something like this (with the
<operator indicating upper bound for a type alias).type T < I = A | B; interface I { draw(): void; }
Is it something the TypeScript team consider adding to the language?
I do not think these two concepts fit together really..
can you elaborate on the scenario here? and why structural checking is not sufficient at use sites?
- addedNeeds More InfoThe issue still hasn't been fully clarifiedThe issue still hasn't been fully clarified
on Nov 20, 2017 Mohamed Hegazy (@mhegazy) You're probably right about the topics not being a fit, but let me explain with another example.
Consider I want to have an Expression hierarchy:
Expression = BinaryExpression | UnaryExpression | Literal class BinaryExpression { constructor(public left: Expression, public right: Expression) {} } class UnaryExpression { constructor(public expr: Expression) {} } class Literal { constructor(public val: number) {} }
Now consider that I also want my Expression hierarchy also have a
childrenproperty and I don't want to implement it as a separate function, e.g., no function of the typeExpression => Iterable<Expression>.I defined the following interface that defines the
children()property:interface AST { children(): Iterable<AST>; }My question is that is there a way to restrict the
Expressionunion type to also extendAST? So that, the compiler complains thatBinaryExpression,UnaryExpression,Literalshould have thechildrenproperty?In other words, is there a way to enforce a that all members of a union type have a specific property? In Scala, using case classes I can have such a property and was wondering if it is possible to also get it in TypeScript.
I understand, but why is not use site sufficient in this case. i.e. passing
Expressionto something that expectsASTwould flag if not all constituents have a methodchildrenon them.A call site error is not a real problem, and the error message is also very clear indicating which type does not have
childrenon it.There are two things, which I would say would be nice, if I could enforce a super type on the union type:
-
I don't need to really expose the AST type, as
childrenis already part of the expression hierarchy. The client code does not even need to know that there is an AST interface. -
It helps with discovery of methods. Let's say, in the Expression example, I add another concrete type to the union (say TernaryExpression) and I forget to implement
childrenthere. Ifchildrenis not used elsewhere in the code base, I will not get an error, but at the same time,childrendisappears from the list of properties. It is a bit problematic when first writing the code, when I need some IDE completion support to see what methods are available.
-
typescript-bot commented
on Dec 5, 2017 ContributorMore actionsAutomatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.
- locked and limited conversation to collaborators
on Jun 14, 2018
I'd like to define an ADT and also enforce that all concrete types implement another interface (I know that is not how ADTs are supposed to be used, but since TypeScript is a mixed paradigm language, I'd like to be able to do it the way I'm used to it in Scala). This is an attempt to achieve it:
The problem is that I cannot enforce that all concrete classes implement
Drawablewhen defining them:and I only will get an error when actually call
draw()on aShapeinstance.In Scala, for example, I can enforce that all concrete case classes to implement the
Drawableinterface:Is there a way to achieve this in TypeScript?
Is there a plan to allow discriminated union type matching feature with interfaces?
Is there a plan to allow type aliases to extend interfaces?