#How to define type for function with collapsing optional parameters

29 messages · Page 1 of 1 (latest)

halcyon field
#
function foo(one: number, two?: string = undefined, three?: number = undefined) {}

foo(123, 123)
//       ^^^
// Argument of type 'number' is not assignable to parameter of type 'string'.ts(2345)

I was hoping to figure out how to make this call work such that if you omit any of the trailing optional parameters it'll work properly based on the type and number of parameters.

I'm building a typings for an existing API that was written to accept this type and i'm not sure how to do it. Thanks!

stiff mesa
#

Are you writing .d.ts for an existing JS library? Or are you writing your own wrapper code?

silent hollow
#

I guess you have to define different overloads

halcyon field
#

I'm writing a .d.ts for an existing library. Techincally a lua one. I'm building out types for hammerspoon in ts-to-lua

#

Is there a way to do the overloads without having to do every possible permutation?

I was thinking there's probably something that could be done with variadic types, but i couldn't figure it out

silent hollow
#

Do you have only three parameters of different types, or there is more?

halcyon field
#

hs.hotkey.bind(mods, key, [message,] pressedfn, releasedfn, repeatfn)

4 optional trailing params

or this one hs.alert.show(str, [style], [screen], [seconds])

stiff mesa
#

Use a function overload yeah.

declare function foo(one: number): void
declare function foo(one: number, two: string): void
declare function foo(one: number, three: number): void
declare function foo(one: number, two: string, three: number): void
halcyon field
#

seems like overloads are the only way. I'm relatively new here. is the etiquette to resolve it now that i've gotten help or leave it open in case others have some ideas?

stiff mesa
#

If there were 3 optional parameters, what are the rules of which calls are allowed?

#

You can leave it up for as long as you want if you don't find existing answers satisfactory.

stiff mesa
silent hollow
#

Yeah, I don't see any other way except providing extensive overloads

stiff mesa
#

Yeah agree, an option object would be a better API design

#

But OP doesn't really get a say in the API design.

silent hollow
#

Yep, that's the bindings. Well, anyways, function overloading is also the only way to solve it in any other statically typed and compiled language, so it's okay to define many overloads in this case

stiff mesa
#

Not necessary, depends on the constraint.

silent hollow
#

Ah, yep, there are some languages with named arguments, so it could be solved naturally with only a single overload

stiff mesa
#

TS can do it, TS type system is very powerful.

silent hollow
# stiff mesa TS can do it, TS type system is very powerful.

Well, as the D language fan, sometimes the TS type system makes me smile though.

For instance, here is how I would put the variable arguments size constraint to only even numbers:

import std;

void foo(Args...)(Args args) if (!(Args.length & 1)) {
    writeln(args);
}

void main() {
    foo("one", 1, "two", 2);
}

The same task in TypeScript, I guess, is much more tricky

slow obsidian
#

if you don't like the overload syntax you could do this:

declare function foo(...args:
  | [one: number]
  | [one: number, two: string]
  | [one: number, three: number]
  | [one: number, two: string, three: number]
): void

(assuming the overload signatures all actually do return the same type)

#

and you could generate that union with a generic type as long as you can specify which parameters are allowed together. but honestly if there are only a few cases like this i'd just write out the signatures

silent hollow
last portalBOT
#
Burrito#6903

Preview:```ts
type Args<T extends unknown[]> = T extends [
infer H,
...infer T
]
? [...([H] | []), ...Args<T>]
: []

declare function foo(
...args: Args<[string, boolean, number]>
): void

foo()
foo("hello")
foo("hello", true)
foo("hello", true, 5)
foo("hello", 5)
foo(true)
...```

stiff mesa
# last portal

This one works with the constraint that:

  • All optional parameters are of different types (otherwise there's no way to tell if a string argument is for one or two)
  • All optional parameters must be in order, so if you have string, boolean your string parameter must be passed before boolean, or not passed at all.
#

It generates all 8 permutations.

slow obsidian
#

too bad we can't manipulate tuple element labels programmatically. then it'd be perfect

stiff mesa
#

Yeah losing the labels really sucks for the end user, making it kind of impractical.