1

Sorry for the vague title, but I don't know how to express this problem concisely.

Basically, I have the following code (with unimportant bits removed):

function identity<T>(value: T): T {
    return value;
}

function extent<T, K>(
    values: T[],
    map: (value: T) => K = identity // the '= identity' part gives an error
): [K, K] | undefined {
    let min;
    let max;

    // ... stuff ...

    return min === undefined || max === undefined ? undefined : [map(min), map(max)];
}

Where the default = identity value results in a compiler error.

I can remove the default value for the map parameter from the extent's signature and always provide one myself:

extent([5, 1, 3, 4, 8], x => x)

Which will work just fine, but I'd rather not supply the second parameter, unless I'm actually mapping the values from one type to another.

How do I type the indentity function so that it's accepted as the default value for extent's map parameter? I'm stuck on TypeScript 3.6 for the time being, if that matteres.

0

3 Answers 3

1

Probably the best way to deal with this that maintains type-safety to the outside world is via overloads:

function extent<T>(values: T[]): [T, T] | undefined
function extent<T, K>(values: T[], map: (value: T) => K): [K, K] | undefined
function extent<T, K>(
  values: T[],
  map?: (value: T) => K 
): [K, K] | [T, T] | undefined {
  const actualMap = map ?? identity;
  let min;
  let max;

  // ... stuff ...

  return min === undefined || 
         max === undefined ? 
             undefined : 
             [actualMap(min), actualMap(max)];
}
Sign up to request clarification or add additional context in comments.

3 Comments

Awesome! That’s the no compromise solution I’ve been looking for! Thanks 😊
Actually, it still fails if I declare the type of min/max: let min: T; let max: T; I mean, in my code I don't have to declare it because it is inferred by the compiler from the code section I omitted (marked as "... stuff ..."), but it fails nontheless. So the return type of extent should be not [K, K] | [T, T] | undefined but [T | K, T | K] | undefined, but this solution is still valid 👍🏻
@Vitaly I had the same identity function with the same signature in some of my own projects, and it took a short while to get this clean (or at least, clean for any consumers!). Glad to help.
0

It's because of type error.

In map parameter you declare that this function will return the type of K but in the identity function you expect that to return the type of T so you can't assign it as a default value.

You could fix it like this: map: (value: K) => K = identity

2 Comments

That's not going to be much use if the mapping is to something other than K.
Yeah, I just quickly answer in the author's case.
0

There is a conflict with T => K type of map and K => K type in identity function. Making identity less strict on types can help:

function identity<T>(value: T): any {
    return value;
}

or even

const identity = (x: any) => x;

1 Comment

Yes, but then the compiler won’t be able to properly infer the return type of the extent function. It would be either [unknown, unknown] or [any, any].

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.