New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixed params last in variable argument functions #1360

Open
JeroMiya opened this Issue Dec 3, 2014 · 12 comments

Comments

Projects
None yet
@JeroMiya

JeroMiya commented Dec 3, 2014

Extracting this suggestion from this issue:
#1336

Currently the variable arguments list supports variable arguments only as the last argument to the function:

function foo(arg1: number, ...arg2: string[]) {
}

This compiles to the following javascript:

function foo(arg1) {
    var arg2 = [];
    for (var _i = 1; _i < arguments.length; _i++) {
        arg2[_i - 1] = arguments[_i];
    }
}

However, variable argument functions are limited to appearing only as the last argument and not the first argument. I propose support be added for having a variable argument appear first, followed by one or more fixed arguments:

function subscribe(...events: string[], callback: (message: string) => void) {
}

// the following would compile
subscribe(message => alert(message)); // gets all messages
subscribe('errorMessages', message => alert(message));
subscribe(
  'errorMessages',
  'customMessageTypeFoo123', 
  (message: string) => {
     alert(message);
  });

// the following would not compile
subscribe(); // supplied parameters do not match any signature of call target
subscribe('a1'); // argument of type 'string' does not match parameter of type '(message: string) => void'
subscribe('a1', 'a2'); // argument of type 'string' does not match parameter of type '(message: string) => void'

subscribe compiles to the following JavaScript:

function subscribe() {
  var events= [];
  var callback = arguments[arguments.length - 1];
  for(var _i = 0; _i < arguments.length - 2; _i++) {
    events[_i] = arguments[_i];
  }
}

notes: it should be impossible for typescript code to call this function with zero arguments when typechecking. If JS or untyped TS code calls it without arguments, callback will be undefined. However, the same is true of fixed arguments at the beginning of the function.

edit: used a more realistic/motivating example for the fixed-last/variable-arguments-first function.

@danquirk

This comment has been minimized.

Member

danquirk commented Dec 5, 2014

I'm assuming by fixed arguments you mean required arguments since this certainly couldn't work when mixed with other rest params or optional arguments. While this proposal would work I'm not sure I see this adding enough value. Are there a lot of existing JavaScript APIs that would require this sort of signature? If not, this isn't really adding any expressivity or much convenience, just reordering some arguments in a subset of signatures that use rest params. Offhand I can't think of any other languages with varargs that support using them in any position but the last.

@RyanCavanaugh

This comment has been minimized.

Member

RyanCavanaugh commented Dec 17, 2014

A cataloging of libraries that use this pattern would be useful.

@mhesler

This comment has been minimized.

mhesler commented Apr 10, 2015

One example is String.prototype.replace. Although this occurs in the middle and not exclusively at the beginning or end.
lib.d.ts does this:

replace(searchValue: RegExp, replaceValue: (substring: string, ...args: any[]) => string): string;

should be this:

replace (searchValue: RegExp, replaceValue: (match: string, ...refs: string[], index: number, input: string) => string): string;
@pgrm

This comment has been minimized.

pgrm commented May 5, 2015

I just came across this when updating TypeDefinitions for rethinkdb. getAll (http://rethinkdb.com/api/javascript/get_all/) should look like this:

getAll(key: string, ...keys: string[]): Selection
getAll(key: string, ...keys: string[], index: {index: string}): Selection

The problem here is index in the end being optional, but that is there problem, in an interface this still should work. Another example from rethinkdb client is the map function (http://rethinkdb.com/api/javascript/map/) which doesn't have the last field as optional.

For now I'm kind of "solving" it like Action and Func are defined in C# by simply creating new functions with more parameters:

getAll(key: string, ...keys: string[]): Selection;
getAll(key: string, key2: string, index?: {index: string}): Selection;
getAll(key: string, key2: string, key3: string, index?: {index: string}): Selection;
getAll(key: string, key2: string, key3: string, key4: string, index?: {index: string}): Selection;
getAll(key: string, key2: string, key3: string, key4: string, key5: string, index?: {index: string}): Selection;
getAll(key: string, key2: string, key3: string, key4: string, key5: string, key6: string, index?: {index: string}): Selection;
getAll(key: string, key2: string, key3: string, key4: string, key5: string, key6: string, key7: string, index?: {index: string}): Selection;
getAll(key: string, key2: string, key3: string, key4: string, key5: string, key6: string, key7: string, key8: string, index?: {index: string}): Selection;
getAll(key: string, key2: string, key3: string, key4: string, key5: string, key6: string, key7: string, key8: string, key9: string, index?: {index: string}): Selection;

but this isn't just a pain to do, it's also not the representation of the underlying javascript library

@aholmes

This comment has been minimized.

aholmes commented Jun 30, 2015

As mentioned in #3686, to add to the list of libraries doing this, lodash uses this pattern in extend/assign.

I also want to call out that lodash makes the callback and thisArg arguments optional.

@Xananax

This comment has been minimized.

Xananax commented May 13, 2016

I'm getting stumped by something. Trying to create the typings for Flyd; it's a library that creates streams. It has a function combine that combines a variable amount of streams in one. It works like this:

const combinedSignal = flyd.combine(function(s1:Signal,s2:signal2,s3:signal3....,self:signal,changed:signal[]){})

I've tried the method described by @pgrm, but I'm getting Argument of type '(s: FlydStream, self: FlydStream) => void' is not assignable to parameter of type 'FlydCombinator', even if one of the overloaded calls matches this pattern exactly.

It seems the method works as long as I don't have another interface with overload that uses an overloaded interface. But the library is entirely made of curried functions calling each other, I just can't not have overloads using overloads.

I think I've managed to recreate a minimal test case here

@olee

This comment has been minimized.

olee commented Jun 5, 2016

Same here.
Tried to write type definitions for an library function which accepts a list of string arguments for filtering and as a last argument a callback. Only way to solve this right now I know is to prepare many copies of the function so it can be used in most cases.

Considering this issue was first opened in Dec 2014, I think this should definitely be added in the near future!

@DanielRosenwasser

This comment has been minimized.

Member

DanielRosenwasser commented Jul 8, 2016

It looks like Ember.computed uses it.

I wonder if allowing rest arguments in non-implementation signatures would be acceptable. That way we could potentially support this at the type level but we'd leave the runtime implementation to be decided by users.

@thorn0

This comment has been minimized.

thorn0 commented Oct 23, 2017

lodash 4 uses it for a number of functions: _.differenceBy, _.differenceWith. _.xorBy, _.xorWith, _.intersectionBy, _.intersectionWith, _.unionBy, _.unionWith, _.zipWith, _.assignInWith, _.assignWith.

@jsdevel

This comment has been minimized.

jsdevel commented Oct 12, 2018

koa-router also uses fixed params after varargs: https://www.npmjs.com/package/koa-router#named-routes

@u8sand

This comment has been minimized.

u8sand commented Dec 11, 2018

This has comes up a lot with certain style of function calling I've seen. The following is not possible as far as I'm aware, related to this issue.

type FnWithCallbackAtEnd<
  FnA extends Array<unknown>,
  FnR extends unknown,
  CbA extends Array<unknown>,
  CbR extends unknown
> = (
  ...args: FnA,
  cb: (...args: CbA) => CbR
) => FnR

google api client library uses these types of callbacks, params where the last argument is a callback.

In general the args are usually fixed, but I want to easily convert any callback function in this style into a promise. To do so, it'd be nice to maintain type safety throughout.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment