Repository navigation
Feature: type alternatives (to facilitate traits/mixins/type decorators) #727
Description
Activity
A comment about the proposed syntax,
T|Eventsto me reads as "T" or "Events", meaning that it is either; using a value decorated with this type, I would want to only use the intersection of the properties of both, and not the union. I would suggest calling itT&Events.I perceive e.g.
User|Observableas meaning, "you can treat this object asUserorObservable".Interestingly, you can use the
|operator in e.g. php-doc as meaning, "this type or that type", or "this type and that type" - since there is only one possible type of static operation, which is a union, which really is neither "and" or "or", but is both (or neither) depending on the context.To give a concrete example, if I type an argument as
File|Folder, you can take that as meaningFileorFolder- when checking property access or method calls statically, you have to permitFileandFolderproperties and methods, since the argument could be either.Compared with an argument typed as
User|Observable, which you can take as meaningUserandObservable, or "can be treated as eitherUserorObservable" - there's no difference, since, likewise, when checking property access or method calls statically, you have to consider bothUserandObservableproperties and methods to be valid, since the argument could be either or both.In cases where the
|type operator is used to specify type alternatives, you will often see further type-checking and type-casting in the implementation - e.g.:if (item is File) { ... } else if (item is Folder) { ... }In other words, the difference is a conceptual (or in some languages run-time) aspect, not something you can distinguish at design-time or compile-time, and it doesn't matter whether you think of the operator as meaning "and" or "or", because, as far as static analysis is concerned, there's no difference.
My guess is they picked
|over&for the operator, because&is often taken as meaning address-of or reference-to in various other languages - it's meaning is already more overloaded than that of|.It's possible they picked it simply for aesthetics too - since it doesn't really mean "and" anymore than it means "or", they may have picked it just because it's easier on the eyes ;-)
If you wanted to avoid ambiguity by overloading, you could also choose a literal keyword operator, e.g.
union(User, Observable)though it might start to look rather strange in complex type expressions such asList<union(User, Observable)>which might look more natural as simplyList<User|Observable>.Come to think of it, it's probably premature to debate the syntax until we hear from the TypeScript team whether this is a feature they're even willing to consider.
Appears to be similar to #14 but different from #186 which proposes sum types.
I would prefer something simple and declarative, which does not leave a run-time footprint, to ensure semantic compatibility with JavaScript, in and out - especially in... there are tons of JS libraries already which decorate variables or prototypes with new members...
- addedSuggestionAn idea for TypeScriptAn idea for TypeScriptNeeds ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.This issue needs a plan that clarifies the finer details of how it could be implemented.
on Nov 26, 2014 Hitting this issue while using React mixins. I was wondering if Multiple Inheritance would work, which is how C++ does mixins. A sum type could be expressed as an anonymous type extending other types:
function addEvents<T>(object: T): extends T,Events { // decorate the object: object['on'] = function() { // ... } return <extends T,Events>object; } var user = addEvents(new User());
Unlike C++, order would matter here, with later entries overriding slots of previous entries.
Joe Wood (@joewood), they don't need to be
extends, onlyimplements.function addEvents<T>(o: T): implements T, Events { o["on"] = function(){} return <implements T, Events>o; }If an interface could be extend a type parameter, this would provide an alternative solution, more verbose, but also possible to be more type-safe:
class C { c(){}} interface HasEvents<T> extends T { on(name, fn: (o: T, n:number);} function addEvents<T>(object: T) { object["on"]=function(){}; return <HasEvents<T>>object; }OK this is not like #14 or #805, it's kind of the opposite. Here we need to say, in a generic way, "if you give me any object I will give you back the same object but now supporting a new interface", i.e. of both types so it is an intersection type not a union type.
If interfaces could extend a type parameter this would solve the issue and enable expressing the traits pattern properly, as above.
In the a real-world example we would want something like this (standard
Emitterpattern):interface Emitter{ on(event, fn); once(event, fn); off(event, fn); emit(event); } interface EmitterTrait<T> extends T, Emitter{} function MakeEmitter<T> : EmitterTrait<T>(t: T){ // add the functions to the object require('emitter-library').mixin(t); return <EmitterTrait<T>>t; }Presumably it would also allow writing a type-safe .d.ts which did the same.
Rasmus Schultz (@mindplay-dk) , I think this is resolved by Intersection types #3622 committed to master 21 days ago.
benliddicott is correct. closing..
- addedFixedA PR has been merged for this issueA PR has been merged for this issueand removedNeeds ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.This issue needs a plan that clarifies the finer details of how it could be implemented.
on Jul 16, 2015 - locked and limited conversation to collaborators
on Jun 18, 2018
The type system does a great job at mirroring most type patterns in JS, all except mixins/traits, anything that decorates types - which unfortunately is very common in JS.
Without it the type system remains incomplete - that is, we're still one step short of being able to describe JavaScript mixin type patterns.
Duplication and interfaces are not the answer, because this does not reflect what's actually happening in JavaScript - e.g. not duplication, but type decoration of sorts. Not sure if I'm using the correct terminology here.
This small library is an example of a real-world decorator - duplicating all of it's method declarations in every single model type in a view-model hierarchy would be extremely impractical.
The following is a simplified example:
The drawback here is you have to choose which is more important - that
Useris still aUserafter being decorated byaddEvents(), or that it is now alsoEvents. There is no direct way to document this type pattern - your only option currently is to document the shape of the resulting type fully, duplicating all the member declarations contributed by the decorator.I propose syntax along the lines of the following, to support type alternatives:
The return type of the call is now
User|Eventswhich is a distinct composite type composed members ofUserandEvents.The resulting composite type extends
Userand implementsEvents, and should pass type-checks for both.This should facilitate type-safe traits/mixins and probably other arbitrary type-patterns possible in JS.
Static type-hints with composite types are implemented in this way in e.g. PhpStorm using php-doc annotations, and it works well.
I think this may be a simpler and more flexible way to support mixins/traits, without explicitly adding support for those patterns specifically.
Thoughts?