5

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);
1
  • How do I ask a good question?: "DO NOT post images of code, data, error messages, etc. - copy or type the text into the question. " Commented Nov 3, 2020 at 11:47

2 Answers 2

9

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.

Sign up to request clarification or add additional context in comments.

Comments

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);
};

2 Comments

Thank you for the detailed explanation. I've never used the Iterable interface. Do you have any documentation on it?
Here's a more detailed page from MDN and here's a more tutorial-like page from javascript.info. 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.

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.