I don't know that many languages but, having been writing lots of typescript in the last 3 years there are so many things I love about it.
It infers types. If I do
const data = [
{ name: 'bob', age: 35, state: 'CA' },
{ name: 'jill', age: 37, state: 'MA' },
{ name: 'sam', age: 23, state: 'NY' },
];
Typescript knows data is an array of { name: string, age: number, state: string }. I don't have to tell it.Further, if I use any field, example
const avg = data.reduce((acc, { age }) => acc + age, 0) / data.length;
It knows that `age` is a number. If I go change data and add an age that is not a number it will complain immediately. I didn't have to first define a type for data, it inferred it in a helpful way.Further, if I add `as const` at the end of data, then it will know 'state' can only be one of `CA`, `MA`, `NY` and complain if I try to check it against any other value. Maybe in this case 'state' was a bad choice of example but there are plenty of cases where this has been super useful both for type safety and for code completion.
There's insane levels of depth you can build with this.
Another simple example
const kColors = {
red: '#FF0000',
green: '#00FF00',
blue: '#0000FF',
} as const;
function keysOf<T extends string>(obj: { [k in T]?: unknown }): readonly T[] {
return Object.keys(obj) as unknown[] as T[];
}
type Color = keyof typeof kColors;
const kAllColors = keysOf(kColors);
Above, Color is effectively an enum of only 'red', 'green', 'blue'. I can use it in any function and it will complain if I don't pass something provably 'red', 'green', or 'blue'. kAllColors is something I can iterate over all colors. And I can safely index `kColors` only by a ColorIn most other languages I've used I'd have to first declare a separate enum for the type, then associate each of the "keys" with a value. Then separately make an array of enum values by hand for iteration, easy to get out of sync with the enum declaration.
Inferred types are really great, but I feel they do not scale. Inside a method/function? Generally fine, but the signature, including the return type, had better be explicit.
Also, I think there was some performance issue with too much inference? Could be wrong, could also be fixed.
Even though that works reasonably well, you may still want to explicitly define your types/interfaces. In this example, if I do need to hardcode the data instead of fetching it somewhere, I would define the interface with "state" being a union of values, because there is nothing that stops you from using `state: 'ABC'` which would be hard to discover.
Lots of languages can infer types. And your last example with the colors is just a dictionary.
What you are describing is structural types. It is indeed a mystery that these are so under used, especially as they are a cornerstone of type theory. Structural types are so useful that they creep into most languages in some way. Even in Java, the Kingdom of the Nouns, where the rulers refused to merge a pair class, functions essentially take tuple arguments and these tuples don't have to be named and defined. You can't return a tuple though, so there is an unfortunate asymmetry. In Haskell and OCaml, we like to describe functions in a structural way, so com.google.android.Predicate would be just "a -> Bool". You wouldn't have to convert your com.google.guava.Predicate. But even these languages lack structural records and variants and suffer for it.