ts-union icon indicating copy to clipboard operation
ts-union copied to clipboard

How do I create a generic union of all variants?

Open Kinrany opened this issue 5 years ago • 16 comments

  • I'm submitting a ... [ ] bug report [ ] feature request [ ] question about the decisions made in the repository [x] question about how to use this project

  • Summary

How do I create an equivalent of this Rust type, including an interface for all of it's members?

enum LoadingEnum<T> {
  Loading,
  Loaded(T)
}
const LoadingEnum = Union(T => ({
  Loading: of(null),
  Loaded: of(T)
}));
type LoadingEnum<T> = ???

Kinrany avatar Jun 20 '19 18:06 Kinrany

Hey, thanks for the question!

does this snippet work?

import { Union, of, GenericValType } from 'ts-union'

const LoadingEnum = Union(T => ({
  Loading: of(null),
  Loaded: of(T)
}));

type LoadingEnum<T> = GenericValType<T, typeof LoadingEnum.T>;

twop avatar Jun 20 '19 19:06 twop

It does, thanks!

Kinrany avatar Jun 20 '19 21:06 Kinrany

Next question: how do I get the type of a payload of a variant of a generic union?

// `Cow` is `{cow: 'cow'}`
type Cow = ???<LoadingEnum<{cow: 'cow'}>, 'Loaded'>;

Kinrany avatar Jun 20 '19 21:06 Kinrany

Maybe im missing something but why not:

type Cow = {cow: 'cow'};

basically if you have a snippet like:

const Maybe = Union( a=> ({
 Just: of(a),
 Nothing: of(null)
}))

type Maybe<T> = GenericValType<T, typeof Maybe.T>

const printIfExists = <T>(maybe: Maybe<T> ) => {...}

You essentially want to extract T type from it. Is there a situation when you don't know T?

If so could you give a little bit more context around your question?

twop avatar Jun 20 '19 21:06 twop

Yeah, I don't know T. More accurately, it's defined in another module, and I don't want to depend on it. I was trying to use the new enum type to rewrite an equivalent of MaybeLoaded['Loaded'].

// maybe.ts
export const Maybe<T> = ...
export type Maybe<T> = ...

// thing.ts
export type Thing = ...

// maybeThing.ts
import {Maybe} from './maybe';
import {Thing} from './thing';
export type MaybeThing = Maybe<Thing>;

// doStuff.ts
import {MaybeThing} from './maybeThing';
export function doStuff(maybeThings: MaybeThing[]) {
  const things: PayloadOf<MaybeThing, 'Just'>[] = /* filter actual things */
}

It does look like a weird problem to have, but unfortunately it's not something I can easily change.

Kinrany avatar Jun 20 '19 22:06 Kinrany

Ok. In that particular case you want to extract back the type of the generic union given the existing one. Let me try to brainstorm:

type ExtractThing<T> = T extends Maybe<infer Thing>? Thing: never;

type Payload = ExtractThing<MaybeThing> ;

I think it will work

twop avatar Jun 20 '19 22:06 twop

Oh, right. This should work, thanks again :D

Kinrany avatar Jun 20 '19 22:06 Kinrany

Wait, what if it's not a generic type, or a generic type that uses the type argument in a different way?

Kinrany avatar Jun 21 '19 12:06 Kinrany

Could you give me examples? :)

twop avatar Jun 21 '19 22:06 twop

const MaybePair = Union(T => ({
  None: of(null),
  Some: of<typeof T, typeof T>()
})
type MaybePair<T> = ...

type MaybePairOfThings = MaybePair<Thing>;

type PairOfThings = /* an equivalent of MaybePairOfThings['Some'] */

Kinrany avatar Jun 22 '19 10:06 Kinrany

That is currently not supported :( It is definitely doable but would require a lot of typescript magic.

twop avatar Jun 22 '19 23:06 twop

Oh well.

A tangentially related idea: what if there was a Variant<TTag, TValue> type, and the Union function merely returned a dictionary of functions that returned Variants? Like a structural analogue to the nominal sum types in other languages.

It'd be possible to solve the problem above with just T extends Variant<'Some', infer TValue> ? TValue : never.

Kinrany avatar Jun 24 '19 21:06 Kinrany

Full disclosure: I've already tried to implement this, but types are hard. Now I'm hoping that someone will solve it before I have to try again out of frustration :P

Kinrany avatar Jun 24 '19 21:06 Kinrany

If you have a sketch of this that is backward compatible with the current api im interested in exploring that

twop avatar Jun 25 '19 05:06 twop

Also curios do you have an actual usecase for having a generic pair?

twop avatar Jun 25 '19 05:06 twop

Also curios do you have an actual usecase for having a generic pair?

I'm not entirely sure it's a good case, but I was trying to reimplement a function withLoading that converts an arbitrary React-like render function (args: T) => Node into an equivalent of (args: {loading: undefined} | {loaded: T}) => Node.

Kinrany avatar Jun 25 '19 17:06 Kinrany