Skip to content

Type aliases requiring type parameters #1616

Closed

Description

Sometimes we need to capture a union of types that may be a mixture of primitives, functions, objects, etc. but we want to leave it parameterised (just as we already can with interfaces).

Example

A simple non-recursive example:

type Source<T> = T | (() => T);

Here, a source can either be a plain value or a nullary function that obtains a value.

We can provide uniform access to such sources:

function unwrap<T>(p: Source<T>) {
    return (typeof p === "function") ? p() : p;
}

And then we can specify model interfaces where we we leave open the nature of the source but we tie down the value types:

interface Person {
    name: Source<string>;
    age: Source<number>;
}

e.g. name is a constant, but age depends on when you ask:

var p: Person = {
    name: "John Lennon",
    age: () => ageFromDOB(1940, 10, 9),
}

But we can treat them identically in consuming code:

var n = unwrap(p.name), a = unwrap(p.age);

NB. The above is already possible with union types alone, but the interface Person has to repeat the pattern:

interface Person {
    name: string | (() => string);
    age: number | (() => number);
}

Not so bad for a simple example, but the pattern for a value source might evolve to get more complex and then you have a lot of fiddly updating to do because you "Did Repeat Yourself".

Recursion

The more flexible recursive version:

type Source<T> = T | (() => Source<T>);

A source can either be a plain value or a nullary function that obtains a source (which may be a plain value terminating recursion, or a nullary function that... and so on).

We can again provide uniform access to such sources, either with runtime recursion (risky until tail-call optimisation is widespread):

function unwrap<T>(p: Source<T>) {
    return (typeof p === "function") ? unwrap(p()) : p;
}

Or with a loop:

function unwrap<T>(p: Source<T>) {
    for (;;) {
        if (typeof p !== "function") {
            return p;
        }
        p = p();
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Labels

CommittedThe team has roadmapped this issueSuggestionAn idea for TypeScript

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions