How to create a map with immutable arrays as values?

1 week ago 16
ARTICLE AD BOX

I have arrays that I want to give access through a function. But I don't want this arrays to be mutables. For this, Object.freeze does the job.

Moreover, I want to map each array with a key. Map does the job.

The Map should be inaccessible and it would be inefficient to instantiate it at each function's call, so I hide the Map in closure (to have the same behavior as static in C/C++ and many other languages), and I use the same mechanism for arrays for the same reason, see the code above after cat. Thanks to this, outside the function, no key of the Map can be added or removed.

From TypeScript point of view, the keys of the Map are mutable (and it is true inside the function exposed to the user and the function used to have static variables). However, with Object.freeze, it sees that the arrays are not mutable and that it is exactly what I would like. However, I don't manage to do correct typing. I tried readonly, but without success. And ReadonlyMap is purely declarative, it has no effect at runtime, and it does not made a good typing too in my case. So what is the correct type for what I want?

const getArray = (function () { // static variables const array1 = Object.freeze([1]); const array2 = Object.freeze([2]); const myMap = new Map<number, Array<number>>([ [1, array1], [2, array2], ]); // function that can use static variables return function (aKey: number): Array<number> { const result = myMap.get(aKey); if (!result) { throw "No key " + aKey.toString(); } return result; } })();

Produces:

a.ts:5:50 - error TS2769: No overload matches this call. Overload 1 of 4, '(iterable?: Iterable<readonly [number, number[]]>): Map<number, number[]>', gave the following error. Argument of type '(number | readonly number[])[][]' is not assignable to parameter of type 'Iterable<readonly [number, number[]]>'. The types returned by '[Symbol.iterator]().next(...)' are incompatible between these types. Type 'IteratorResult<(number | readonly number[])[], any>' is not assignable to type 'IteratorResult<readonly [number, number[]], any>'. Type 'IteratorYieldResult<(number | readonly number[])[]>' is not assignable to type 'IteratorResult<readonly [number, number[]], any>'. Type 'IteratorYieldResult<(number | readonly number[])[]>' is not assignable to type 'IteratorYieldResult<readonly [number, number[]]>'. Type '(number | readonly number[])[]' is not assignable to type 'readonly [number, number[]]'. Target requires 2 element(s) but source may have fewer. 5 const myMap = new Map<number, Array<number>>([ ~ 6 [1, array1], ~~~~~~~~~~~~~ 7 [2, array2], ~~~~~~~~~~~~~ 8 ]); ~~~~~ Found 1 error in a.ts:5

Playground Link

const getArray = (function () { // static variables const array1 = Object.freeze([1]); const array2 = Object.freeze([2]); const myMap = new Map<number, readonly Array<number>>([ [1, array1], [2, array2], ]); // function that can use static variables return function (aKey: number): readonly Array<number> { const result = myMap.get(aKey); if (!result) { throw "No key " + aKey.toString(); } return result; } })();

Produces:

a.ts:5:35 - error TS1354: 'readonly' type modifier is only permitted on array and tuple literal types. 5 const myMap = new Map<number, readonly Array<number>>([ ~~~~~~~~ a.ts:5:59 - error TS2769: No overload matches this call. Overload 1 of 4, '(iterable?: Iterable<readonly [number, number[]]>): Map<number, number[]>', gave the following error. Argument of type '(number | readonly number[])[][]' is not assignable to parameter of type 'Iterable<readonly [number, number[]]>'. The types returned by '[Symbol.iterator]().next(...)' are incompatible between these types. Type 'IteratorResult<(number | readonly number[])[], any>' is not assignable to type 'IteratorResult<readonly [number, number[]], any>'. Type 'IteratorYieldResult<(number | readonly number[])[]>' is not assignable to type 'IteratorResult<readonly [number, number[]], any>'. Type 'IteratorYieldResult<(number | readonly number[])[]>' is not assignable to type 'IteratorYieldResult<readonly [number, number[]]>'. Type '(number | readonly number[])[]' is not assignable to type 'readonly [number, number[]]'. Target requires 2 element(s) but source may have fewer. 5 const myMap = new Map<number, readonly Array<number>>([ ~ 6 [1, array1], ~~~~~~~~~~~~~ 7 [2, array2], ~~~~~~~~~~~~~ 8 ]); ~~~~~ a.ts:10:36 - error TS1354: 'readonly' type modifier is only permitted on array and tuple literal types. 10 return function(aKey: number): readonly Array<number> { ~~~~~~~~ Found 3 errors in the same file, starting at: a.ts:5

Playground Link

const getArray = (function () { // static variables const array1 = Object.freeze([1]); const array2 = Object.freeze([2]); const myMap: ReadonlyMap<number, number[]> = new Map<number, number[]>( [[1, array1], [2, array2]]); // function that can use static variables return function (aKey: number): Array<number> { const result = myMap.get(aKey); if (!result) { throw "No key " + aKey.toString(); } return result; } })();

Produces:

a.ts:6:2 - error TS2769: No overload matches this call. Overload 1 of 4, '(iterable?: Iterable<readonly [number, number[]]>): Map<number, number[]>', gave the following error. Argument of type '(number | readonly number[])[][]' is not assignable to parameter of type 'Iterable<readonly [number, number[]]>'. The types returned by '[Symbol.iterator]().next(...)' are incompatible between these types. Type 'IteratorResult<(number | readonly number[])[], any>' is not assignable to type 'IteratorResult<readonly [number, number[]], any>'. Type 'IteratorYieldResult<(number | readonly number[])[]>' is not assignable to type 'IteratorResult<readonly [number, number[]], any>'. Type 'IteratorYieldResult<(number | readonly number[])[]>' is not assignable to type 'IteratorYieldResult<readonly [number, number[]]>'. Type '(number | readonly number[])[]' is not assignable to type 'readonly [number, number[]]'. Target requires 2 element(s) but source may have fewer. 6 [[1, array1], [2, array2]]); ~~~~~~~~~~~~~~~~~~~~~~~~~~ Found 1 error in a.ts:6

Playground Link

In my example, I use number as key. A simple array with +1 on key would do the job instead of Map. But in my real case, I don't use number as the key, so see number only as a simple key's type for having a simple example.

Read Entire Article