2

This is the code. Got the idea from this answer.

Typescript playground

I'm using the generic type parameter: T extends unknown[], since this function should work for any kind of array.

export const pickRandomItems = <T extends unknown[]> (arr: T, n: number): T => {
  const shuffled = Array.from(arr).sort(() => 0.5 - Math.random());
  return shuffled.slice(0, n);
};

But I'm getting the following error on the return statement:

enter image description here

enter image description here

It goes away if I do a type assertion. But why is that necessary? Am I doing something wrong?

enter image description here

NOTE:

The following line is evaluated as unknown[], so why is the type assertion necessary anyway?

const result = shuffled.slice(0, n);
cbdeveloper
  • 27,898
  • 37
  • 155
  • 336
  • [How do I ask a good question?](https://stackoverflow.com/help/how-to-ask): _"**DO NOT post images of code, data, error messages, etc.** - copy or type the text into the question. "_ – Andreas Nov 03 '20 at 11:47

2 Answers2

6

Change the type parameter to be the array item, not the whole array:

export const pickRandomItems = <T extends unknown> (arr: T[], n: number): T[] => {
  const shuffled = Array.from(arr).sort(() => 0.5 - Math.random());
  return shuffled.slice(0, n);
};

Playground Link

While T extends unknown[] does mean T can be any array, it could also be a subtype of Array, which means slice will not return the appropriate type (it will return Array instead of whatever T is). Generally you can't assign a concrete type to a generic type parameter, because the generic type parameter could be any sub type that is decided by the caller, while you are using a specific concrete type in your implementation.

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
3

The issue you're having is that T can be any subtype of unknown[], however your function returns an array. Thus, typescript warns you that an array cannot be cast to T, which could be some subclass of an array. Instead, you can change T to be the item, and take an iterable as an argument and return an array:

// trailing comma in generic so ts doesn't confuse with jsx
export const pickRandomItems = <T,> (arr: Iterable<T>, n: number): T[] => {
  const shuffled = Array.from(arr).sort(() => 0.5 - Math.random());
  return shuffled.slice(0, n);
};
Aplet123
  • 33,825
  • 1
  • 29
  • 55
  • Thank you for the detailed explanation. I've never used the `Iterable` interface. Do you have any documentation on it? – cbdeveloper Nov 03 '20 at 11:58
  • 1
    [Here's a more detailed page from MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterable_protocol) and [here's a more tutorial-like page from javascript.info](https://javascript.info/iterable). Basically, an iterable is anything that implements a `Symbol.iterator` function that returns an iterator, allowing `Array.from` to collect the iterator into an array. – Aplet123 Nov 03 '20 at 12:00