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.

Suggestion: Allow local types to be declared in interfaces

See original GitHub issue

I have something like this:

export interface Reducer<State, Types extends Action<string, any>> {

    add<TypeName extends string, Payload>(action: {
        type: TypeName,
        reduce: (state: State, action: Payload) => State
    }): Reducer<State, Types | Action<TypeName, Payload>>;

    readonly cursorType: Cursor<State, Types>;
}

The details aren’t that important except to illustrate that a Reducer is immutable, and has an add method that returns another Reducer, but see that the return type has something extra “unioned” into it. By repeated chained calls to add I can build up a big nasty old type that would be ugly to have fully declare by hand. Fortunately type inference takes care of building the type for me, which is great.

Then elsewhere in my code I want to be able to declare something called a “cursor”, which needs to have a type that corresponds to the reducer’s type. The cursor could be a field in a class so I need to be able to refer to the type so I can declare such a field.

So I want to provide a simple way to declare a const of the type “correct kind of cursor for a given reducer”, leveraging the work that the TS compiler already did for me with its type inference.

My slightly hacky approach, as shown above, is to declare a readonly field cursorType. The value of this is at runtime is junk and should not be used! So I need a “here be dragons” comment on it. Its only purpose is to be prefixed with typeof, e.g.:

const R = getReducerSomehow();

class Test {
    constructor(public readonly myCursor: typeof R.cursorType) { }
}

To fill in the cursorType field of a Reducer I have to do this filth:

newReducer.cursorType = {} as Cursor<State, Types>;

So cursorType really should never be used as a value. It doesn’t even need to exist as a value. It will cause a runtime error if anyone tries to used it as a cursor. Ugh. But how else can I make this elaborately computed type available conveniently?

I’m wondering if TS could allow:

export interface Reducer<State, Types extends Action<string, any>> {

    add<TypeName extends string, Payload>(action: {
        type: TypeName,
        reduce: (state: State, action: Payload) => State
    }): Reducer<State, Types | Action<TypeName, Payload>>;

    // not currently possible:
    type CursorType = Cursor<State, Types>;
}

i.e. a type alias can be added to an interface. So now my implementation of Reducer no longer has to do anything. No nasty dummy runtime variable hack required.

And my usage example becomes:

const R = getReducerSomehow();

class Test {
    constructor(public readonly myCursor: R.CursorType) { }
}

That is, CursorType is a type that can be referred to as if it was a member of an instance. Similar I guess to:

namespace N {
    export type S = string;
}

const s: N.S = "hi";

In which N is an object at runtime and yet can also be used to find the type S.

Issue Analytics

  • State:open
  • Created 7 years ago
  • Reactions:11
  • Comments:12 (3 by maintainers)

github_iconTop GitHub Comments

3reactions
dead-claudiacommented, Feb 14, 2017

Edit: Fixed an inconsistency.

Just thought of a few libraries that could really use them: React and friends. It’d be much easier to type, say, “components that resolve to a particular element”, using a local generic within a generic while still remaining humanly readable. Something like this:

// The outer type carries most of the necessary validation info, and
// could be aliased in React.DOM.
interface Resolution<Name extends string, Element, Attrs> {
    interface DirectChild {
        type: Name;
        props: Attributes;
        // etc.
    }

    // The inner type is much easier to type.
    interface Component<Props, State> {
        state: State;
        props: Props;
        render(props: Props, state: State): ReactComponent<this> | DirectChild;
        // etc.
    }
}

The declaration files would grow a bit, but the user wouldn’t see most of the verification boilerplate.

2reactions
dead-claudiacommented, Feb 14, 2017

May I note that allowing inner types to be both overridden and abstract solve the higher kinded type issue entirely? It does not create Turing-completeness, though, and makes it roughly on par with OCaml’s module types and inner types.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Why you cannot declare member interfaces in a local class?
You can define an interface either at top level or in another class or interface. You cannot declare an interface inside a block....
Read more >
Handbook - Interfaces - TypeScript
To describe a function type with an interface, we give the interface a call signature. This is like a function declaration with only...
Read more >
Using Interfaces and Inner Classes Together
Java 1.1 has a new feature called inner classes. An inner class is defined within another class or within a method of some...
Read more >
Java Generics Example Tutorial - Generic Method, Class ...
If you have any suggestions for improvements, please let us know by ... A generic type is a class or interface that is...
Read more >
Google TypeScript Style Guide
When introducing an interface for a class, give it a name that expresses why the ... For example, do not declare types in...
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