type-fest icon indicating copy to clipboard operation
type-fest copied to clipboard

`NonEmptyTuple` type

Open sindresorhus opened this issue 1 year ago • 10 comments

export type NonEmpty<T> = [T, ...T[]];

Context: https://github.com/sindresorhus/type-fest/pull/287

Why add such a simple type? Mostly for readability and discoverability. Most people don't know about this trick and we can document it well with examples and stuff.

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • The funding will be given to active contributors.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

sindresorhus avatar Sep 29 '22 05:09 sindresorhus

I would find it just a bit more readable if it mentioned the word Array, like NonEmptyArray for example. Other than that, I'm all for it. Or indeed, OneOrMore or AtLeastOne like how the original PR called it. Anything that clearly implies that we're talking about a list of some kind.

Evertt avatar Sep 30 '22 05:09 Evertt

I will make the one we already have generic and public. https://github.com/sindresorhus/type-fest/blob/fedbc441a314c1f9f5f6225c993860d0886261da/source/internal.d.ts#L74

skarab42 avatar Sep 30 '22 05:09 skarab42

I agree.

Any thoughts on naming it NonEmptyTuple? It's technically a tuple, even though the term tuple/array is a bit of a conflated mess in TS.

sindresorhus avatar Sep 30 '22 05:09 sindresorhus

Isn't the difference that tuples have a fixed length and arrays have a variable length?

So a NonEmptyArray is not the same as a NonEmptyTuple. The main difference being that the NonEmptyTuple is readonly and the NonEmptyArray is not (necessarily) readonly. Right?

Evertt avatar Sep 30 '22 05:09 Evertt

Isn't the difference that tuples have a fixed length and arrays have a variable length?

I'm not sure about that, it's more to do with the positioning of the elements and their types. A tuple can have a variable length but an imposed sequence of elements.

type VariableLengtTuple1 = [string, boolean, ...number[]];

const foo:VariableLengtTuple1 = ['hello', true, 1, 2, 3]; // Pass
const bar:VariableLengtTuple1 = ['hello', 'world', 1, 2, 3]; // Fail
const baz:VariableLengtTuple1 = ['hello', true, 1, 2, 3, 'not-allowed']; // Fail

skarab42 avatar Sep 30 '22 06:09 skarab42

Any thoughts on naming it NonEmptyTuple? It's technically a tuple, even though the term tuple/array is a bit of a conflated mess in TS.

A tuple matches both tuple and array, but array does not match tuple. It's clearer for me to call it a tuple because technically it is one, but I understand that it can be confusing.

skarab42 avatar Sep 30 '22 06:09 skarab42

The main difference being that the NonEmptyTuple is readonly and the NonEmptyArray is not (necessarily) readonly. Right?

No, a readonly tuple cannot be modified in any ways, a non-readonly tuple can have its members modified as long as they are of the same type.

const writableTuple: [string, number]= ['life', 42];

writableTuple[0] = 'hello'; // Pass, it not reaonly
writableTuple[1] = 'hello'; // Fail, Type 'string' is not assignable to type 'number'.
writableTuple[2] = 'hello'; // Fail, key out of bound


const readonlyTuple: readonly [string, number] = ['life', 42];

readonlyTuple[0] = 'hello'; // Fail, Cannot assign to '0' because it is a read-only property.

skarab42 avatar Sep 30 '22 06:09 skarab42

NonEmptyTuple's type could definitely be improved to allow passing a type restriction to the tuple:

Current: export type NonEmptyTuple = readonly [unknown, ...unknown[]]

Proposed: export type NonEmptyTuple<T extends any = unknown> = readonly [T, ...T[]]

bvandercar-vt avatar May 04 '23 18:05 bvandercar-vt

Sadly these ideas won't fly for this use case:

type NonEmptyTuple<T extends any = unknown> = readonly [T, ...T[]];
const wontFly: NonEmptyTuple = [...[], ...[1]];

EDIT: At the time of writing, it will fail with:

Type 'number[]' is not assignable to type 'NonEmptyOf<number>'.
  Source provides no match for required element at position 0 in target.ts(2322)

Using "typescript": "^5.2.2"

adam-rocska avatar Sep 05 '23 14:09 adam-rocska

Potentially already exists, with the exception that Adam mentions (which I think is just a TypeScript limitation):

  • https://github.com/sindresorhus/type-fest/issues/420

fregante avatar Sep 09 '23 13:09 fregante