20

Update: These checks are meant for compile time, not at runtime. In my example, the failed cases are all caught at compile time, and I'm expecting similar behaviour for the other should-fail cases.

Suppose I'm writing a table-like class where I want all members of the class to be arrays of the same length, something like:

class MyClass {
  tableHead:  string[3]; // expect to be a 3 element array of strings
  tableCells: number[3]; // expect to be a 3 element array of numbers
}

The closest solution I've found so far is:

class MyClass {
  tableHead:  [string, string, string];
  tableCells: [number, number, number];
}

let bar = new MyClass();
bar.tableHead = ['a', 'b', 'c']; // pass
bar.tableHead = ['a', 'b'];      // fail
bar.tableHead = ['a', 'b', 1];   // fail

// BUT these also pass, which are expected to fail at compile time
bar.tableHead = ['a', 'b', 'c', 'd', 'e']; // pass
bar.push('d'); // pass
bar.push('e'); // pass

Any better ideas?

6
  • You should wrap your member data in access functions. These can then check the number of arguments and/or length of data being asked to be added to your arrays. Commented Feb 24, 2017 at 14:45
  • You mean getter and setter ? Commented Feb 24, 2017 at 14:50
  • yes. make them private and provide methods to control what is being pushed or removed from your list. Then you would have total control on it Commented Feb 24, 2017 at 15:09
  • @iberbeu if I understand correctly, this provides checking at runtime, right? what about compile time? Like the way in C/C++. Commented Feb 24, 2017 at 15:21
  • 2
    Not currently possible, but there is a proposal: github.com/Microsoft/TypeScript/issues/6229 Commented Feb 24, 2017 at 15:45

3 Answers 3

17

Update 2: From version 3.4, what the OP asked for is now fully possible with a succinct syntax (Playground link):

class MyClass {
  tableHead: readonly [string, string, string]
  tableCells: readonly [number, number, number]
}

Update 1: From version 2.7, TypeScript can now distinguish between lists of different sizes.

I don't think it's possible to type-check the length of a tuple. Here's the opinion of TypeScript's author on this subject.

I'd argue that what you're asking for is not necessary. Suppose you define this type

type StringTriplet = [string, string, string]

and define a variable of that type:

const a: StringTriplet = ['a', 'b', 'c']

You can't get more variables out of that triplet e.g.

const [one, two, three, four] = a;

will give an error whereas this doesn't as expected:

const [one, two, three] = a;

The only situation where I think the lack of ability to constrain the length becomes a problem is e.g. when you map over the triplet

const result = a.map(/* some pure function */)

and expect that result have 3 elements when in fact it can have more than 3. However, in this case, you are treating a as a collection instead of a tuple anyway so that's not a correct use case for the tuple syntax.

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

1 Comment

Good answer. I like that you included Hejlsberg's comment, helps to understand the background.
9

From Typescript: Can I define an n-length tuple type?, programmatically, with dynamic length:

type Tuple<TItem, TLength extends number> = [TItem, ...TItem[]] & { length: TLength };

type Tuple9<T> = Tuple<T, 9>;

2 Comments

what if this hardcoded 9 would need to be dynamic. In other words, given 2 tuples, i want a 2nd tuple to be as long as the first one.
from types point fo view, I think this is the most dynamic it can be. Maybe you are overcomplicating it and just need old array type: MyThing[] ?
-1

Here is a simple example of a class to control the length of its internal array. It isn't fool-proof (when getting/setting you may want to consider whether you are shallow/deep cloning etc:

https://jsfiddle.net/904d9jhc/

class ControlledArray {

  constructor(num) {
    this.a = Array(num).fill(0); // Creates new array and fills it with zeros
  }

  set(arr) {
    if (!(arr instanceof Array) || arr.length != this.a.length) {
      return false;
    }
    this.a = arr.slice();
    return true;
  }

  get() {
    return this.a.slice();
  }

}

$( document ).ready(function($) {

  var m = new ControlledArray(3);

  alert(m.set('vera')); // fail
  alert(m.set(['vera', 'chuck', 'dave'])); // pass

  alert(m.get()); // gets copy of controlled array

});

1 Comment

Thanks for the answer, but as you can see in the discussion under the question, I was intended to perform this check at compile time, not run time. I'm updating the question to avoid future confusions.

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.