What & Why
Setup
Type Annotations
Type AnnotationsPrimitive TypesUnknown TypeOptional TypesUnion TypesFunction TypesObject TypesClass TypesArray TypesArray ConstructorGetting element from ArraySubtypingTuple TypesType AliasesGeneric TypesMagic TypesModules
Type System
ConfigurationLibraries
For Potential Contributors
Index

Array Types

Edit

Array is another fundamental JavaScript data type which represents a heterogeneous collection (collection which can contain more than one data-type inside). Hegel provides syntax for annotating array types and restricts collection element types.

Playground
const numbers: Array<number> = [1, 2, 3];

If you are familiar with TypeScript or Flow.js, you may know about array types, but both Flow.js and TypeScript provide two methods of array definition: via Array-constructor and via adding [] (square brackets) at the end of the type name:

Playground
const oneWay: Array<number> = [1, 2, 3];
const anotherOne: number[] = [1, 2, 3];

Hegel provides only the first variant without additional syntax with square brackets.

Array Constructor

There are two ways to define an array in JavaScript:

Playground
const arrayLiteral = [];
const arrayConstructorInvocationWithLength = new Array(0);

Hegel treats these differently. When you create an array via the Array() constructor - items of the array will be "undefined" and will have "undefined" type in addition to the annotated one.

Playground
const arrayLiteral: Array<number> = [];
arrayLiteral[0] = 4; // 👌!
// Error: Type "undefined" is incompatible with type "number"
arrayLiteral[1] = undefined;
const arrayInstance = new Array<number>(4);
arrayInstance[0] = 4; // 👌!
arrayInstance[1] = undefined; // 👌!

The reason for this is the behavior of the Array constructor. Even if you set the length of your array it will be filled with undefined, so Hegel can't give any guarantees that all your elements will have the annotated type.

TypeScript and Flow.js do not handle this condition, so it's easy to get runtime TypeErrors with these analyzers TypeScript Example, Flow.js Example:

Playground
const arrayInstance = new Array<number>(4);
let intSequence = "";
for (const num of arrayInstance) {
// TypeError: Cannot read property 'toFixed' of undefined
intSequence += num.toFixed(0);
}

Getting element from Array

Another interesting Hegel "feature" is type inference for elements which retrieved from an array by index syntax.

Playground
const numbers: Array<number> = [];
// Type of firstElement is "number | undefined"
const firstElement = numbers[0];

If you are familiar with TypeScript or Flow.js, these "analyzers" will infer firstElement as number which can create a TypeError at runtime. TypeScript Example, Flow.js example.

Playground
const numbers: Array<number> = [];
// TypeError: Cannot read property 'toFixed' of undefined
const firstElement = numbers[0].toFixed();

Let's go back to Hegel. This behavior also means that even if you try to access an element in a for loop you still get "number | undefined" element type. You must specify that your value is not undefined using type refinement.

Playground
const numbers: Array<number> = [1, 2, 3];
for (let i = 0; i < numbers.length; i++) {
const num = numbers[i]; // still "number | undefined"
if (typeof num === "number") {
// ...
}
}

The reason for this decision is the mutable nature of the "length" property of Array, so I can write the following code, which will break my program without this property behavior. TypeScript example, Flow.js Example

Playground
const numbers: Array<number> = [1, 2, 3];
let intSequence = "";
numbers.length = 5;
for (let i = 0; i < numbers.length; i++) {
// TypeError: Cannot read property 'toFixed' of undefined
intSequence += numbers[i].toFixed();
}

Why not make length 'readonly' and add refinement for the length array property?". The answer is a popular method of cleaning an array. In many JavaScript programs you may see the next line:

Playground
someArray.length = 0;

This is the most popular way to quickly clean an array.

Nowadays this is not the best practice for iterating over an array. You can use forEach (or other array methods like map, filter and etc.) as a safe variant of iterating over the array.

Playground
const numbers: Array<number> = [1, 2, 3];
// num type is "number"
numbers.forEach((num) => {});

Subtyping

Another safe part of Hegel Arrays is their invariant nature. You can't assign one array to another if they contain different element types:

Playground
const numbers: Array<number> = [1, 2, 3];
// Error: Type "Array<number>" is incompatible with type "Array<number | string>"
const numbersOrStrings: Array<number | string> = numbers;

The reason for this decision is the reference nature of JavaScript arrays. If Hegel allows this assignment you will be able mutate the source array via another and get unpredictable errors.

Playground
const numbers: Array<number> = [1, 2, 3];
const numbersOrStrings: Array<number | string> = numbers;
numbersOrStrings.push("some string");
// TypeError: num.toFixed is not a function
const fixedNumbers = numbers.map((num) => num.toFixed(0));

If you are familiar with TypeScript, you may know about this problem. TypeScript Example