129

Closed

Support for Generics in TypeScript

description

I know you guys have this on your backlog already, but I just wanted to create an actual issue here on CodePlex for the community to track its progress. If there's already a mechanism for tracking progress on that, please let me know.
Closed Jun 17, 2013 at 3:41 PM by LukeH
This is available in TypeScript 0.9.

comments

serjic_shkredov wrote Oct 8, 2012 at 8:26 PM

It would also be great if you publish draft of specification section on generics.

dflor003 wrote Nov 5, 2012 at 5:13 PM

Any update on when we can expect generics in TypeScript? This is one of the main things keeping my team from moving to TypeScript.

seanhess wrote Nov 12, 2012 at 3:28 PM

Excited for this! Love where you are headed. Throwing in some keywords so I would have found this issue: "Type Variables"

jviereck wrote Dec 26, 2012 at 8:18 PM

I've started working on something like "generics":
function mapFunc(elm: _A[], foo: _A): _A {
  return elm[0];
}

// -------------------------------------------------
// Tests!

// Basic test
var a:number[] = [1,2];
var b:number[] = null;
var c:number = null;
This example is working for me. Is this in the scope of "Generics" (sorry my weak background in type-theory)?

jviereck wrote Dec 26, 2012 at 8:20 PM

Sorry, missed the important part
function mapFunc(elm: _A[], foo: _A): _A {
return elm[0];
}

// Basic test
var a:number[] = [1,2];
var c:number = null;

c = mapFunc(a, 1);

dflor003 wrote Dec 27, 2012 at 1:50 PM

Yeah, that's the idea. Although ideally, the syntax for generics should match that of generics in other languages like Java and C#. The example you posted would look somewhat like this if written using that flavor of generics:

function mapFunc<TSomeType>(elm: TSomeType[], foo: TSomeType): TSomeType {
return elm[0];
}

// Basic test
var a:number[] = [1,2];
var c:Number = null;

c = mapFunc<Number>(a, 1);

Also, not an absolute requirement, but in C# the compiler infers generic type parameters when it has enough information on how your method is being used. Given the level of type inference that TypeScript is using, it doesn't seem that far fetched that TypeScript generics would do the same type inference.

jviereck wrote Dec 28, 2012 at 8:34 AM

Update: I've created a PR here: http://typescript.codeplex.com/SourceControl/network/forks/jviereck/TypeScript/contribution/3856
function mapFunc<TSomeType>(elm: TSomeType[], foo: TSomeType): TSomeType {
From this example it looks to me that "_A" === "TSomeType". How should the generic type be named for a second type "_B" like in a map function:
function map(arr: _A[], fn: (entry: _A, arr?: _A[], scope?: any) => _B, scope?: any): _B[] {
?
Also, not an absolute requirement, but in C# the compiler infers generic type parameters when it has enough information on how your method is being used. Given the level of type inference that TypeScript is using, it doesn't seem that far fetched that TypeScript generics would do the same type inference.
The submitted PR infers the types when the function is called. In the example of mapFunc it knows that the function has a return type of number if the function call pattern is of the type `mapFunc(number[], number).

dflor003 wrote Jan 7, 2013 at 3:57 PM

With C#/Java-style generics, the type parameters do not have to have any specific name or naming convention. Basically, the part in between the angle brackets declares one or more type parameters and then they are used inside of the function. Here's an example with multiple type parameters with arbitrary names.

function doSomethingGeneric<MyType1, AnotherType>(arrayToSearch: MyType1[], key: string) : AnotherType
{
 for(var i = 0; i < arrayToSearch.length; i++)
 {
      var element: MyType1 = arrayToSearch[i];
      var value: AnotherType = element[key];
      if (value)
           return value;
 }
}

Notice here that regardless of the names of the type parameters, after declaring them in between the angle brackets, they can now be used to specify the types within the method and signature. Another thing to consider is generic type constraints. These are restrictions on the types that can be passed as part of the method signature such as requiring that a type parameter inherit from a certain class or interface. I would imagine since the TypeScript team already mentioned that TypeScript's implementation of generics would be through type erasure, they would probably take Java's generic type constraint model as well. Also, keep in mind that you can make generics for both functions and classes (which in JavaScript also happen to be functions).

There's more details on C# generics here: http://en.wikipedia.org/wiki/Generic_programming#Genericity_in_.NET
And Java Generics via type erasure here: http://en.wikipedia.org/wiki/Generics_in_Java

jonturner wrote Feb 27, 2013 at 3:59 PM

We hope to publish a draft of the spec for broader user comment soon. Our current thoughts on generics follow a couple principles:
  • We want the types to be erasable, just like the rest of TypeScript's type system
  • We want to be able to describe generic function signatures, interfaces, and classes with the same fidelity that non-generic types allow now
An example:
interface Array<T> {
  map<U>(callbackfn: (value: T, index: number, array: T[]) => U): U[];
}
The type parameters can be added to type definitions, like interfaces, as well as signatures.

Additionally, we're strongly considering allowing "constraints" on types, so that you can describe the basic shape of the type parameters a generic type requires. Changing the above example a bit:
interface Message {
  target:any;
  source:any;
  data:any;
}

interface MessageArray<T extend Message> {
  // T now has Message structure, without extends it's assumed to be {}
}
Now T will have be know to have at least the members listed in the Message array when it's used inside of the MessageArray interface.

We're also looking into type parameter inference, so that for many cases you won't have to explicitly give the type arguments to generic functions.

Wolverine_ wrote Mar 1, 2013 at 6:02 PM

@jonturner: Yes, please PLEASE add those two features. Type inference on generics is super important, and the fact that you can't specify real generic type constraints C# is my #1 biggest complaint with C# generics.

dflor003 wrote Mar 1, 2013 at 6:45 PM

@Wolverine_: C# DOES allow you to specify generic type constraints. http://msdn.microsoft.com/en-us/library/bb384067.aspx

clausreinke wrote Mar 1, 2013 at 9:47 PM

@jonturner: having browsed the updated TS specs in the develop-0.9.x branch's doc folder for Generics, I've got two notes/questions:
  1. Why the limitation to linear type parameter lists (3.4.1)? Unification of repeated type parameters is a powerful standard tool in type systems. Why not for TS?
  2. "G equivalent to G<any,any>" (example in 3.5.2.1). This is not very helpful but it is going to be a major obstacle to introducing type constructor parameters later (TS 0.9.x already has type constructors and type parameters, so type constructor parameters are a natural next step, eg., to abstract over different types of container). I suggest not to introduce this forward-incompatible abbreviation.

jonturner wrote Mar 12, 2013 at 9:49 PM

@clausreinke
  1. I'm not clear on your point. Are you saying that it's powerful tool to say this: GenericType<A, A, A>? I'm not familiar with this usage pattern, generally I'm used to each type parameter being listed only once.
  2. Can you give an example of a "type constructor parameter"?

clausreinke wrote Mar 13, 2013 at 3:23 PM

@jonturner:
I'm not clear on your point. Are you saying that it's powerful tool to say this: GenericType<A, A, A>? I'm not familiar with this usage pattern, generally I'm used to each type parameter being listed only once.
Assuming that repeated formal parameters (interface Gen<A,A> ...) imply type unification, instantiating such a generic interface with two types from two different sources (Gen<X,Y>) asserts that those actual type parameters (X and Y) are equal (unifiable, really).

This pattern of lifting unification of formal and actual parameters to unification of two actual parameters goes back all the way to Prolog but has been adopted in advanced type systems. How useful the pattern is depends on what other features are available in the type system. It is common, for instance, in Haskell, where the type equality assertion can even be extended to various type equality predicates.
Can you give an example of a "type constructor parameter"?
TS already has Array<T>, where Array is a type-level constructor. Applying a (first-order) type-level constructor to a type gives a type, eg Array<String>. With ES6, there will come things like Set<T> or Map<IndexT,ValueT>. If we want to declare a type that makes sense for several different type-level constructors, then we'll need to abstract over the concrete constructors. So we need to go from first-order type constructors to higher-order type constructors (type-level constructors that take type-level constructors as parameters). Just as JS has higher-order functions at the value level. Something like
interface Select<Constructor,Index,Element> {
  select: Constructor<Index,Element> => Index => Element
}

var IntArraySelect : Select<Array,Int> = {  select : ( arr, ind ) => arr[ ind ] };
Here Constructor is a type constructor parameter, and Select is a (higher-order) type constructor.

The canonical example for type constructor parameters in generic type systems is map, which can be provided on all container types:
A system of constructor classes: overloading and implicit 
higher-order polymorphism
Mark P. Jones, 
In FPCA '93: Conference on Functional Programming Languages 
and Computer Architecture, Copenhagen, Denmark,  June 1993.
http://web.cecs.pdx.edu/~mpj/pubs/fpca93.html
This text uses Haskell, but you can translate the ideas using a simple dictionary, roughly like
EcmaScript/TypeScript     |  Haskell
--------------------------------------------
object/class             |  value/type
interface                |  type class
interface implementation |  type class instance
generic                  |  polymorphic

jonturner wrote Mar 20, 2013 at 4:27 PM

@clausreinke

Thanks for the in-depth reply. There's definitely some good food for thought here.

I agree that unification has some definite advantages, and duplicating the type variables allows you to drive to a known single type. However, in TypeScript we actually don't enforce strict type equality anywhere (to my knowledge), and instead use assignment compatibility rules which allow for additional polymorphic and situational flexibility. For example, under substitution:
interface Gen<A> { 
  myfunc(x:A, y:A): bool;
}

var myvar : Gen<{x: number}>;
// substitution of Gen<{x: number}> specializes to:
interface Gen<{x: number}> { 
  myfunc(fst: {x: number}, snd: {x: number}): bool;
}

// now assignment compatibility kicks in and the following is valid:
myvar.myfunc({x: 3}, {x: 4, y: 5});
Knowing the rules that kick in after substitution, we can express the equivalent using the constraint typing that has been proposed:
interface Gen<A, B extends A> { ... }
While this isn't strict type equality checking, it gives you the ability to take multiple types to a known structure, which is in practice what TypeScript supports checking.

To your point about higher-order type construction, I agree that as generic programming becomes more sophisticated there will be a temptation to enable describing higher-order, more general abstractions.

The general philosophy in TypeScript is to resist this in favor of supporting:
  • Easy to read types and easy to understand type errors
  • Easy to tool and automate common patterns
  • Expressive types for typing existing JavaScript
As types become more sophisticated, they may favor the last bullet but possibly at the expense of the first two. In designing TypeScript, we try to balance all three.

LukeH wrote Mar 25, 2013 at 11:58 PM

Just posted a blog on the 0.9 release, including some details of what we are thinking for Generics in TypeScript. Greater detail is also now in the draft specs in the 'develop' branch under the 'doc' folder.

vamp wrote Apr 1, 2013 at 2:57 PM

What about type auto-resolving?, like:
T map<T>(T value){
    return Value;
}

map(new Date()).get| // getDate, getDay, getFullYear, ...

DRubino wrote May 7, 2013 at 3:03 AM

Please support generics with constraints. That would be incredibly valuable to the community.

LukeH wrote May 7, 2013 at 4:10 PM

@vamp - Yes, this example, rewritten to TypeScript syntax, works as you expect in the current develop branch of 0.9.0.
function map<T>(value: T) {
    return value;
}
map(new Date()).get| // getDate, getDay, getFullYear, ...
@DRubino - TypeScript 0.9 does include support for constraints on generic parameters. For example:
function cloneElement<T extends HTMLElement>(element: T): T {
    return <T>element.cloneNode();
}