Remix.run Logo
socalgal2 5 hours ago

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 Color

In 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.

grumpyprole 5 hours ago | parent | next [-]

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.

fuzzy2 5 hours ago | parent | prev | next [-]

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.

g947o 3 hours ago | parent | next [-]

This. As soon as you need to use the type in another function (e.g. function parameter), you'll discover that it's better to just write it out.

epolanski 3 hours ago | parent | prev [-]

They do scale, but explicit types have two bonuses in my eyes:

1. can be read without a compiler, useful when reading PRs

2. They make the compiler work less, it's easier to check than infer

g947o 3 hours ago | parent | prev | next [-]

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.

sfn42 5 hours ago | parent | prev [-]

Lots of languages can infer types. And your last example with the colors is just a dictionary.

grumpyprole 5 hours ago | parent | next [-]

Most languages have poor support for structural types though. If you try and join two records together (like a SQL join), what will your favourite language infer then?

sfn42 4 hours ago | parent [-]

C# has anonymous types which is pretty much the same thing. Though I prefer to declare actual types for most usecases, I'll only use anonymous types for intermediate results and such.

grumpyprole 4 hours ago | parent [-]

I certainly don't mean to knock nominal types. But I think structural types are more fundamental. A language would only need a single "newtype" or "nominal" keyword to create nominal types from structural types.

lock1 an hour ago | parent [-]

Why structural is more fundamental?

C#'s anonymous type shares some flexibility of structural type system even though it still a nominal type.

  > A language would only need a single "newtype" or "nominal" keyword to create nominal types from structural types.
I think you also can add `structural` keyword & apply structural type system in generally nominal type system as well if we're talking about adding feature.
sheept 5 hours ago | parent | prev | next [-]

dictionaries generally aren't guaranteed to contain an entry for every possible value of the key type. while you could implement the colors example with a dictionary, ideally you'd want the type system to assure that given a Color, there will be a string associated with it

vips7L 5 hours ago | parent [-]

Sounds like enums with extra steps.

socalgal2 an hour ago | parent | next [-]

it’s Enuma associated with data without having to repeat yourself

If you have to define the Enums in one place and then repeat them all in another just to associate data with each one you’ve failed

epolanski 3 hours ago | parent | prev [-]

Enums aren't type safe in typescript

fuzzy2 5 hours ago | parent | prev [-]

It's not a dictionary (type-wise). "as const" is the magic ingredient.