question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

TypeScript supports generic type/interface/class declarations. These features serve as an analog of “type constructors” in other type systems: they allow us to declare a type that is parameterized over some number of type arguments.

TypeScript also supports generic functions. These serve as limited form of parametric polymorphism: they allow us to declare types whose inhabitants are parametrized over some number of type arguments. Unlike in certain other type systems however, these types can only be function types.

So for example while we can declare:

type Id = <T>(x : T) => T

const test : Id = x => x

we cannot declare (for whatever reason):

type TwoIds = <T> { id1: (x: T) => T, id2: (x: T) => T }

const test : TwoIds = { id1: x => x, id2: x => x }

One result of this limitation is that the type constructor and polymorphism features interact poorly in TypeScript. The problem applies even to function types: if we abstract a sophisticated function type into a type constructor, we can no longer universally quantify away its type parameters to get a generic function type. This is illustrated below:

// This works
type Id = <T>(x : T) => T

// This should also work
type IdT<T> = (x: T) => T
type Id = <T> IdT<T> // but is impossible to express

Another problem is that it is often useful to quantify over types other than bare functions, and TypeScript prohibits this. As an example, this prevents us from modeling polymorphic lenses:

type Lens<T, U> = {
    get(obj: T): U
    set(val: U, obj: T): T
}

// no way to express the following type signature
const firstItemInArrayLens: <A> Lens<A[], A> = {
    get: arr => arr[0],
    set: (val, arr) => [val, ...arr.slice(1)]
}

firstItemInArrayLens.get([10])      // Should be number
firstItemInArrayLens.get(["Hello"]) // Should be string

In this case, a workaround is to break down the type into functions and move all the polymorphic quantification there, since functions are the only values that are allowed to be polymorphic in TypeScript:

type ArrayIndexLens = {
    get<A>(obj: A[]): A
    set<A>(val: A, obj: A[]): A[]
}

const firstItemInArrayLens: ArrayIndexLens = { ... }

By contrast, in Haskell you’d simply declare an inhabitant of a polymorphic type: firstItemInArrayLens :: forall a. Lens [a] a, similarly to the pseudocode declaration const firstItemInArrayLens: <A> Lens<A[], A>:

{-# LANGUAGE ExplicitForAll #-}

data Lens s a = Lens { get :: s -> a, set :: a -> s -> s }

firstItemInArrayLens :: forall a. Lens [a] a
firstItemInArrayLens = Lens { get = _, set = _ }

In some sense TypeScript has even less of a problem doing this than Haskell, because Haskell has concerns like runtime specialization; it must turn every polymorphic expression into an actual function which receives type arguments.

TypeScript just needs to worry about assignability; at runtime everything is duck typed and “polymorphic” anyway. A more polymorphic term (or a term for which a sufficiently polymorphic type can be inferred) can be assigned to a reference of a less polymorphic type.

Today, a value of type <A> (x: A) => A is assignable where a (x: number) => number is expected, and in turn the expression x => x is assignable where a <A> (x: A) => A is expected. Why not generalize this so an <A> Foo<A> is assignable wherever a Foo<string> is expected, and it is possible to write: const anythingFooer: <A> Foo<A> = { /* implement Foo polymorphically */ }?

Issue Analytics

  • State:open
  • Created 6 years ago
  • Reactions:319
  • Comments:50 (15 by maintainers)

github_iconTop GitHub Comments

18reactions
OliverJAshcommented, Aug 19, 2019

Here’s a potential use case: generic React components:

import * as React from 'react';

type Props<T> = { t: T };

// Ideally we could somehow re-use the `React.FC` function type:
// declare const MyComponent: <T> FC<Props<T>>;

// But we can't, so we have to write out the whole of the `FC` type, inline in the function definition:
const MyComponent = <T>(props: Props<T>): ReactElement<any> | null =>
  <div>Hello, World!</div>

Another use case: given this generic React HOC type,

import { ComponentType } from 'react';

type HOC<P, P2> = (
    ComposedComponent: ComponentType<P>,
) => ComponentType<P2>;

… when we use it to define our HOC, we want the resulting HOC type to also be generic:

// Ideally we could somehow re-use the `HOC` function type:
// declare const myHoc: <P> HOC<P>

// But we can't, so we have to write out the whole of the `HOC` type, inline in the function definition:
declare const myHoc: <P>(ComposedComponent: ComponentType<P>) => ComponentType<P>;

(Originally filed in https://github.com/microsoft/TypeScript/issues/32973.)

14reactions
DylanRJohnstoncommented, Dec 4, 2019

👍 this issue, it comes up whenever you have objects that contain related polymorphic functions. The lack of this feature makes lenses super awkward to work with in Typescript which is a shame as they make working with immutable data so much easier. You can’t write polymorphic lenses that can be re-used, you always have a specify concrete types. The lack of partial application of generic parameters also means you end up with an awkward calling syntax.

import { Lens } from 'monocle-ts'

interface Foo {
   bar: number
}

// type :: Lens<Foo, number>
const bar_ = Lens.fromProp<Foo>()('bar')

When in reality the proper type of the Lens is Lens<A extends { bar: unknown }, A['bar']> because it should be able to work with any type containing a bar

Read more comments on GitHub >

github_iconTop Results From Across the Web

Generic values and characters with special meanings - IBM
Wherever a parameter can have a generic value, it is entered ending with an asterisk (*), for example ABC*. A generic value means...
Read more >
Generic Value Variables
The generic value variable is a type of variable with a wide range that can store any kind of data, including text, numbers,...
Read more >
Generic Types - Learning the Java Language
A generic type is a generic class or interface that is parameterized over types. ... @param <T> the type of the value being...
Read more >
Generic Value Products (GVP) | Brands - Sally Beauty
Shop for Generic Value Products (GVP) products at Sally Beauty. We offer salon professional beauty supplies and products for all your beauty needs....
Read more >
Generics in Java - GeeksforGeeks
Types of Java Generics. Generic Method: Generic Java method takes a parameter and returns some value after performing a task. It is exactly...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found