Description
Hi,
I'm creating this issue to suggest a feature which is kind of related with type reflection. I don't know if it is possible to implement or if it is something that aligns with the TypeScript project goals but I though that it would be worth to share it...
The problem
I'm trying to think about ways to improve the support for immutable.js or similar libraries.
The current .d.ts
file uses a generics and two type arguments (key and value) to declare aMap
:
import Immutable = require('immutable');
var map1: Immutable.Map<string, number>;
var map = {a: 1, b: 2, c: 3};
map1 = Immutable.Map(map );
var map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50
This is a problem because in some cases not all the values will be of the same type:
var map = {a: 1, b: "test", c: 3};
However, this is not a major problem because we can use union types:
var map1: Immutable.Map<string, (string|number)>;
map1 = Immutable.Map(map);
The second problem is more serious:
map2.get('d'); // No error! We need something!
The compiler is not able to validate that we have used the correct string literal.
The not so good solution
After trying a few things I decided I decided that it would be much better to do something like the following:
interface ImmutableMap<TMap, TPropertiesOfMap> {
set<TProp>(prop: TPropertiesOfMap, val: TProp): ImmutableMap<TMap, TPropertiesOfMap>;
get<TProp>(prop: TPropertiesOfMap): TProp;
toJS(): TMap;
}
interface Immutable {
Map<TMap, TPropertiesOfMap>(map: TMap): ImmutableMap<TMap, TPropertiesOfMap>;
}
declare var Immutable: Immutable;
interface MyMap {
a: number;
b: string;
c: number;
}
type PropertiesOfMyMap = "a" | "b" | "c";
var map = { a: 1, b: "something", c: 3 };
var map1 = Immutable.Map<MyMap, PropertiesOfMyMap>(map);
var map2 = map1.set<number>("a", 50);
let num1 = map1.get<number>("a"); // 1
let num2 = map2.get<number>("a"); // 50
let string1 = map2.get<string>("b"); // "something"
let num3 = map2.get<number>("d"); // Error (as expected)
let js = map2.toJS();
js.a; // 50
js.b; // "something"
js.d; // Error (as expected)
The main problem I see with this is having to manually create the following type:
type PropertiesOfMyMap = "a" | "b" | "c";
I believe that this is a potential failure point. A place in which we may type something incorrectly or forget one of the properties.
The suggested solution
I was wondering if it would be possible to implement a design time only type operator. I have called this operator propertiesOf
:
interface MyMap {
a: 1;
b: 2;
c: 3;
}
type PropertiesOfMyMap = propertiesOf MyMap; // "a" | "b" | "c"
If this operator was applicable to generic types we could re-implement the immutable example as follows:
interface ImmutableMap<TMap> {
set<TProp>(prop: propertiesOf TMap, val: TProp): ImmutableMap<TMap>;
get<TProp>(prop: propertiesOf TMap): TProp;
toJS(): TMap;
}
interface Immutable {
Map<TMap>(map: TMap): ImmutableMap<TMap>;
}
declare var Immutable: Immutable;
interface MyMap {
a: number;
b: string;
c: number;
}
var map = { a: 1, b: "something", c: 3 };
var map1 = Immutable.Map<MyMap>(map);
var map2 = map1.set<number>("a", 50);
let num1 = map1.get<number>("a"); // 1
let num2 = map2.get<number>("a"); // 50
let string1 = map2.get<string>("b"); // "something"
let num3 = map2.get<number>("d"); // Should throw an Error
let js = map2.toJS();
js.a; // 50
js.b; // "something"
js.d; // Error (as expected)
I'm not sure but this operator could maybe also prevent index property access errors?
var map = { a: 1, b: 2, c: 3 };
map["a"]; // 1
map["d"]; // Not Error! Could propertiesOf help here?
Thanks in advance for taking the time to review this 👍