DanielXMoore / Civet

A TypeScript superset that favors more types and less typing
https://civet.dev
MIT License
1.55k stars 33 forks source link

tuple typing QoL feature? #849

Open 747 opened 11 months ago

747 commented 11 months ago

As far as I have searched, there seems no other way to declare tuple in TS beside:

let tup: [number, number, number, number, number, number, number, number, number, number, number, number];

I wonder if it is beneficial to let us write like:

tup: [number * 12] .= ...

or go further by

tup2: [number * 4, string * 2] .= ...
bbrk24 commented 11 months ago

In newer versions of TS you can do this:

let x = [1, 2, 3, 4, 5] satisfies [number, ...number[]];

Just writing this off the top of my head so I may have made a mistake, but if I did that right it should automatically infer a tuple type based on the length of the array literal.

747 commented 11 months ago

Wow, quite formidable but it works as type definition too.

let tup: [number, ...number[]] & {length: 12} = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];

Thank you for letting me know.

edemaine commented 11 months ago

I still like the original idea, as a clearer way to write these types. Though I don't know how common big tuples are; do you have examples? Lately I name each item in the tuple and * wouldn't let you do so.

747 commented 11 months ago

Though I don't know how common big tuples are;

Yes, that's the point. I have only needed once so far, and have no idea if it is any common pattern.

do you have examples?

What I can easily think of is:

enum St { White, Black, None }
type GoBoard = [
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
[St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St, St],
]

would benefit a lot from

type GoBoard = [[St * 19] * 19]

But arguably you only use this type of definition once or so in your program.

bbrk24 commented 11 months ago

Here's the one use case where I had a long-ish tuple: https://github.com/bbrk24/spe-lib/blob/e1453e1f3595ca00394cb098bee90a26546e0358/src/PhonemeStringMatcher.ts#L246-L248

    static subscripts = (
        ['₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉']
    ) satisfies [string, ...string[]];
edemaine commented 11 months ago

Some related effort: https://millsp.github.io/ts-toolbelt/modules/list_repeat.html We've wanted to provide more convenient syntax for ts-toolbelt-style features, and this seems like a good first.

Note that Python would look more like [number] * 12 instead of [number * 12]. But I think Python's style of multiplication would be harder to implement at compile time; we'd need to do something more like List/Repeat except when the tuple is right there next to the *. Also note that [number] * 12 isn't as useful unless we had a concatenation operator like [number] * 12 + [string] * 12, paralleling List/Concat. But we should consider now whether we want * to only work within tuples ([number * 12]), as originally proposed, or to operate on tuple types ([number] * 12). One advantage of the latter would be the ability to write [number, string] * 12 (meaning [number, string, number string, ...]).

747 commented 11 months ago

If it isn't too much for parser, restricting [] to only arrays and tuples might be good, so that

bbrk24 commented 2 months ago

I managed to get this working for the first case of [number * 12], but not for the alternating case of [(number, string) * 12]:

// Adapted from https://stackoverflow.com/a/50641073/6253337
UnionToIntersection<U> ::= if (if U < any then (k: U) =>) < ((k: infer I) =>) then I 
NoUnion<Key> ::=
    if [Key] < [boolean] then boolean
    else if [Key] < [UnionToIntersection<Key>] then Key

SpecificNumber<N < number> ::= unless number < N then NoUnion<N>

Tuple<T, Length < number> ::= T[] & length: SpecificNumber<Length>

This even allows functions to be generic over tuple length:

function zip<T1, T2, Length < number>(
    arr1: Tuple<T1, Length>,
    arr2: Tuple<T2, Length>
): Tuple<[T1, T2], Length> {
    arr1.map((el, i) => [el, arr2[i]]) as Tuple<[T1, T2], Length>
}

Then, zip [1, 2, 3] as tuple, ["a", "b", "c"] as tuple compiles (Length = 3), but zip [1, 2, 3, 4] as tuple, ["a", "b", "c"] as tuple gives a type error at compilation time, pointing at the first tuple.