1

I want to write an interpreter by TypeScript. This language has 2 primitive types and I define them as follows.

type DataType = { kind: 'number' } | {kind: 'string' }

and then I would like to define another type that also include it's instance.

type _DataType<T extends 'type' | 'typedValue'> = {
    kind: 'number',
    value: T extends 'type' ? undefined : number,
} | {
    kind: 'string'
    value: T extends 'type' ? undefined : string,
}
type DataType = _DataType<'type'>;
type TypedValue = _DataType<'typedValue'>;

It works well. But when I want to define a higher order type like array which can take original DataType as parameter. I don't know how to define it. Below is what I tried:

type _HigherType<T extends 'type' | 'typedValue'> = {
    kind: 'Array',
    t: DataType,
    value: Array<?????>
} | {
    kind: 'Single',
    t: DataType,
    value: ?????
}
type HigherType = _HigherType<'type'>;
type HigherTypedValue = _HigherType<'typedValue'>;

It seems that the value's type depends on t's type, but how to express their relation?

Maybe I could just expand HigherType like below:

type _HigherType<T extends 'type' | 'typedValue'> = {
    kind: 'Array',
    t: { kind: 'number },
    value: T extends 'type' ? undefined : Array<number>
} | {
    kind: 'Array',
    t: { kind: 'string' },
    value: T extends 'type' ? undefined : Array<string>
} | {
    kind: 'single',
    t: { kind: 'number' },
    value: T extends 'type' ? undefined : number
} | {
    kind: 'single',
    t: { kind: 'string' },
    value: T extends 'type' ? undefined : string
}

But If there are m DataType variants and n HigherType(despite Array, we may have Option, Set,......), we need to write m*n variants, and most of them have repeated part. Is there a way to only define m + n variants?

user2925565
  • 1,302
  • 2
  • 11
  • 14

1 Answers1

0

If you need to express m*n in typescript system you should use distributivity.

Consider simple example:

type All = 'a' | 'b' | 'c'

type Variants<T extends string> = T extends any ? { tag: T } : any


type Result_ = Variants<All> // union of all possible states

// WIthout distribution
type Variants2<T> = { tag: T }

type Result__=Variants2<All> // no union

YOu can also find this article and this question helpfull.

Solution:


type Kinds = 'number' | 'string'
type Value = 'type' | 'typedValue'

/**
 * HashMap for type names
 */
type Dictionary = {
    number: number,
    string: string,
}

type HandleValue<V extends Value, Type> = V extends 'type' ? undefined : Type

type Values<T> = T[keyof T]

type DataType_<V extends Value> = Values<{
    [Prop in Kinds]: {
        kind: Prop,
        value: HandleValue<V, Dictionary[Prop]>
    }
}>


type DataType = DataType_<'type'>;
type TypedValue = DataType_<'typedValue'>;

type HigherTypes = 'Array' | 'single' //| 'Option' | 'Set'

/**
 * You should puth in this conditional type all allowed tpye constructors:
 * Array, Set, Options ....
 */
type MapStructure<Prop extends string, T extends DataType> =
    (Prop extends 'Array'
        ? Array<Dictionary[T['kind']]>
        : (Prop extends 'single'
            ? Dictionary[T['kind']]
            : never
        )
    )

type HigherOrderType<V extends Value, T extends DataType = DataType> =
    Values<{
        [Prop in HigherTypes]: (T extends any
            ? {
                kind: Prop,
                t: Pick<T, 'kind'>,
                value: HandleValue<V, MapStructure<Prop, T>>
            }
            : never)
    }>

type Result = HigherOrderType<'type'>

type _HigherType<T extends 'type' | 'typedValue'> = {
    kind: 'Array',
    t: { kind: 'number' },
    value: T extends 'type' ? undefined : Array<number>
} | {
    kind: 'Array',
    t: { kind: 'string' },
    value: T extends 'type' ? undefined : Array<string>
} | {
    kind: 'single',
    t: { kind: 'number' },
    value: T extends 'type' ? undefined : number
} | {
    kind: 'single',
    t: { kind: 'string' },
    value: T extends 'type' ? undefined : string
}


/**
 * Tests
 */
type HigherType = _HigherType<'type'> extends HigherOrderType<'type'> ? true : false; // true
type HigherTypedValue = _HigherType<'typedValue'> extends HigherOrderType<'typedValue'> ? true : false; // true

Playground If you want to distribute smth, just use T extends any.