flow icon indicating copy to clipboard operation
flow copied to clipboard

Rest inside tuples

Open jamiebuilds opened this issue 7 years ago • 8 comments

type restStartTuple = [...number, string];

(["2"]: restStartTuple);
([1, "2"]: restStartTuple);
([1, 2, "3"]: restStartTuple);

([1, 2, 3]: restStartTuple); // FlowError
([1, "2", 3]: restStartTuple); // FlowError

type restMiddleTuple = [string, ...number, string];

(["1", "2"]: restMiddleTuple);
(["1", 2, "3"]: restMiddleTuple);
(["1", 2, 3, "4"]: restMiddleTuple);

(["1"]: restMiddleTuple); // FlowError
(["1", 2]: restMiddleTuple); // FlowError
([1, 2, 3]: restMiddleTuple); // FlowError
(["1", "2", "3"]: restMiddleTuple); // FlowError

The primary use case is to support rest arguments in functions in other positions (i.e. func(...a, b))

This syntax would be most natural to people:

function restStart(...a: Array<number>, b: string) {...}
function restMiddle(a: string, ...b: Array<number>, c: string) {...}

However, since JavaScript does not support that kind of syntax we need a workaround. I would suggest this:

function restStart(...args: [...Array<number>, string]) {...}
function restStart(...args: [string, ...Array<number>, string]) {...}

So really there's two feature requests here: Support for rest inside tuples and for them to work with function arguments.

The current work around is to create libdefs with a bunch of possible combinations like here: https://github.com/flowtype/flow-typed/pull/260

jamiebuilds avatar Sep 12 '16 11:09 jamiebuilds

But how can this type be used?

For example, we have var x of type [...number, string]. What's the type of x[1], x[2], x[3]? It's number | string, at best. So for reading it is essentially the same as Array<number|string>.

vkurchatkin avatar Sep 12 '16 12:09 vkurchatkin

Yeah that seems expected. Correct types enter, unknown types leave. If you were writing a function that accepted a variable number of args in a starting or middle position you'd have to check them anyways.

function method(...args: [...number, string]) {
  for (let arg of arg) {
    if (typeof arg === 'number') {...} else {...}
  }
}


method(1, 2, 3, "hi");

jamiebuilds avatar Sep 12 '16 12:09 jamiebuilds

What about restEndTuple?

For example:

type restEndTuple = [void, ...string];

In my case this is useful because I'm representing a heap using an array and while every element has a certain type, heap[0] is undefined.

migueloller avatar Dec 15 '16 05:12 migueloller

+1

For me the primary need is to be able to define rest arguments as tuples. Rest inside tuples seems a lot more complex of a feature to add and also less used (i.e. less overall value). But we desparately need to be able to do the former, eg:

type Action<P> = {
    type: string,
    payload?: P,
    error?: bool,
    meta?: any,
  }

type ActionCreator<T, P> = (...args: T) => Action<P> // this won't typecheck since args must be any or Array<something>

type Payload = { index: number, foo: string }

type Args = [number, string]

// the following line uses a tuple type that will NOT correctly be spread into ...args
const performAction: ActionCreator<Args , Payload> = createAction(
  'PERFORM_ACTION', 
   (index: number, foo: string): Payload => ({ index, foo })
)

I mean how are you supposed to have an abstract type declaration for complex libraries that do things like return functions (e.g. redux-actions, react-redux, etc) if you can't create the library definition abstract enough to allow you, in client code, to specify a tuple of argument types which will be spread. If you just did the following, typechecking won't flow through:

type ActionCreator<T, P> = (...args: any) => Action<P>

and of course the following won't work because you will rarely ever have all your arguments be the same type:

type ActionCreator<T, P> = (...args: Array<T>) => Action<P>

faceyspacey avatar Jan 22 '17 10:01 faceyspacey

I'd like to be able to type s-expression grammars encoded as tuples, where some expressions are variable arity, e.g.:

type Expression =
    | number
    | ['+', ...Expression]
    | ['*', ...Expression]
    | ['-', Expression, Expression]
    | ['/', Expression, Expression];

function evaluate(e: Expression) {
    if (typeof e === 'number') {
        return e;
    }

    const [op, ...args] = e;
    switch (op) {
    case '+': return args.map(evaluate).reduce((a, b) => a + b, 0);
    case '*': return args.map(evaluate).reduce((a, b) => a * b, 1);
    case '-': return evaluate(args[0]) - evaluate(args[1]);
    case '/': return evaluate(args[0]) / evaluate(args[1]);
    }
}

jfirebaugh avatar Aug 02 '17 20:08 jfirebaugh

@jfirebaugh if rest is the last bit, then it should work. Probably not that hard to implement, even. The problem is that disjoint union refinement doesn't work with tuples at the moment.

vkurchatkin avatar Aug 02 '17 20:08 vkurchatkin

I would like to be able to do something like:

type AtLeastTwo<T> = [T, T, ...T];

This is for modelling an AST for a math expression parser that I'm building. It treats addition and multiplication as n-ary operations, but addition and multiplication need at least two operands to make sense.

kevinbarabash avatar Aug 03 '19 16:08 kevinbarabash

One case here:

type $AddEntryFn =
    & (<T1>([], T1) => [T1])
    & (<T1, T2>([T1], T2) => [T1, T2])
    & (<T1, T2, T3>([T1, T2], T3) => [T1, T2, T3])
    & (<T1, T2, T3, T4>([T1, T2, T3], T4) => [T1, T2, T3, T4])
    & (<T1, T2, T3, T4, T5>([T1, T2, T3, T4], T5) => [T1, T2, T3, T4, T5])
    & (<T1, T2, T3, T4, T5, T6>([T1, T2, T3, T4, T5], T6) => [T1, T2, T3, T4, T5, T6])
    & (<T1, T2, T3, T4, T5, T6, T7>([T1, T2, T3, T4, T5, T6], T7) => [T1, T2, T3, T4, T5, T6, T7])
    // and again, and again, ....
;

replaced by : type $AddEntryFn = (TuppleTyped,Added) => [...TuppleTyped, Added]; leading to many fun :

type $AddEntry<B, N> = $Call<$AddEntryFn, B, N>;

export type SelectBuilder<S, O> = {|
    innerJoin<E: Entity>(
        alias: string,
        source: Source<E>,
        on: (sources: $AddEntry<S, E>) => SqlExpression<boolean>,
    ): SelectBuilder<$AddEntry<S, E>, O>,

    select<E: Entity>((sources: S) => E): SelectBuilder<S, E>,
|}

RomLAURENT avatar Nov 11 '21 10:11 RomLAURENT