
TypeScript’s quirks: How inconsistencies make the language more complex - lobster_roll
https://blog.asana.com/2020/01/typescript-quirks
======
twotwotwo
These can make sense if you think of TS as better-checked JavaScript, not as a
from-scratch design for a statically typed language. Excess property checks
catch a common JavaScript bug (typoed property names), structural static
typing matches the dynamic duck typing JavaScript authors had already been
working with (though I see how nominal would be useful), and it's awesome they
found a workable hack for discriminated unions where the code and objects look
like what you'd do in vanilla JS, but checked (and requiring the class to have
a specific structure seems fine as limitations go).

I get how folks coming from "natively" static type systems could find all this
weird, but I think by targeting "JS but with better checks" instead of a more
fundamental redesign, the TypeScript crew have managed to build something that
can get traction where other efforts have not.

~~~
kazinator
If you can pass a Cat to a Dog function, where is the better checking?

That would be one of the hello-world test cases for a type checker bolted on
to a dynamic language.

That type checker itself has _introduced_ the syntax for declaring Cat and Dog
classes; yet is neglecting to do the obvious with it.

~~~
MaulingMonkey
Note that you can only pass Cat to a Dog function if:

* All fields of Dog are also present in Cat, with compatible type signatures

* All functions of Dog are also present in Cat, with comaptible type signatures

"compatible type signatures" does seem to leave some suprising
co/contravariance holes still when using wider union types, but C#/Java arrays
also have co/contravariance holes and we don't automatically write off their
entire type systems because of it. Or, well, at least I don't ;).

As a meaningless point of ancedata: Making these types sufficiently equivalent
to fall through this type safety hole appears to be rare enough that I've
never done so by accident - allowing me to be suprised by the article's
example of passing a class Cat to a class Dog-accepting function being legal.
While typescript has never been my primary dayjob, it's been a significant
secondary part of my dayjob and hobby junk.

(EDIT: Minor clarifications)

~~~
Kinrany
Another example of a hole:
[https://www.typescriptlang.org/v2/en/play#code/MYGwhgzhAECSB...](https://www.typescriptlang.org/v2/en/play#code/MYGwhgzhAECSB2wBOBTAti+AXFToG8AoaaAS0VQ2wAoAPALmngFc0AjXASkZfdwOIkyAM2jUsATwAOKAPaja0AIQBeFdADkvDkg2cBQoVgAWSWQHcmKSwFEkZpNQAGAEny0AvmRhhobyTLy0J4ANEyyWNC+2rhOnADcgiQehEnQqFjMSPDB0ADU0ACMicmEKYTkOEjCYMAo0ADKWEgIyOiYVQZkFO00DEysOtAAPtAQzeQA5twDfEiJ5cLMiFiksjnkbVRYdIzjSFNhm5QduIxNLT3bXDyD-
EQkGVkbV6dIAHTHvTu0CWWpAHoAdA7A5GIUAEwAZm8UTGE3gkzC8AicJiSEIQOgTlaJ2wsWgABNZCgYCjIqQ0FIQN9ulUanVsRdcd9YoRgOsILIae8QLJJtQvttqBpIVCNMjrHBXvjHJx5fFoIQgA)

------
chmod775
The following is purely anecdotally, my opinions is solely based on my
personal experience with the language.

As someone who learned TypeScript just by using it and without any serious
"study", I'll have to disagree the basic premise of the article.

I already knew about almost everything the article mentioned and nothing
really seemed weird to me at the time.

The only point that I did not know about is type narrowing not working on
nested objects, because that came up exactly 0 times in about 50k lines of
TypeScript so far.

Half of the points made are actually TypeScript being _lenient_ (interfaces
allowing excess properties, classes working like interfaces). In my book this
makes the TS easier to work with, because it gets out of the way when it's not
needed.

Classes essentially working like interfaces is probably a result of TS wanting
to be backward compatible with JS, because when you enrich your existing JS
lib with type information, TS can't know about your "classes" \- they're a
runtime thing. Treating them the same as interfaces can make this much nicer.

It also allows 3rd party code to add extra properties to your new-instanciated
object and using an interface to reflect the change, but still being able to
pass it to stuff expecting the original thing. This way you can have type
safety without losing JS features.

~~~
yawaramin
> TS can't know about your "classes" \- they're a runtime thing. Treating them
> the same as interfaces can make this much nicer.

No–this is a very surprising and unsound choice and not at all what you would
expect if you previously saw that TypeScript lifts classes into types.

> This way you can have type safety without losing JS features.

IMHO, you can't get type safety out of an unsound type system. (Which
TypeScript is, by choice.) I think surprises like the above drive that point
home.

~~~
shhsshs
TypeScript’s goal isn’t to be sound. Its goal is to be a balance between type-
safety and effort. Full type safety would require a lot of runtime checks
injected into the final JavaScript, which TypeScript intentionally avoids.

JavaScript will never be fully type-safe without fundamental changes, which
will probably never happen.

~~~
yawaramin
I understand that, which is why I said 'Which TypeScript is [unsound], by
choice'.

Disagree that full type safety would require a lot of runtime checks. That's
not the experience I've had in ReasonML.

~~~
afiori
ReasonML is exactly the kind of backwards incompatible change that would be
necessary. You cannot mix reasonML and JavaScript freely together.

------
davedx
This article makes me think about the "rust compile times are terrible" blog
post from the other day.

If you spend a lot of time with a technology, day in and day out, you get
intimately familiar with all of its shortcomings, and sometimes you maybe lose
some perspective.

Having used Rust (enough to see its innovations and promise) and TypeScript
(daily), I think they are incredible languages and platforms. Sure, they have
shortcomings, but I think it's important to keep in perspective the huge
advances in the state of the art they have brought us for these trade offs.

~~~
thosakwe
Nothing wrong with highlighting areas for improvement, though.

------
stupidcar
I don’t agree with the article’s claim that discriminated unions are just a JS
compat feature. Many APIs type JSON serialised entities using a string
property and this feature makes it very easy to write interfaces and code to
work with them.

~~~
alexcole
Author here! Yeah I think you are right that discriminated unions are useful
more broadly than just legacy JS code.

That being said, I still think TypeScript's solution to handling them of using
"type guards" where the type of a variable changes in different scopes is
definitely designed to match common JS patterns at the cost of added
complexity. Most other languages I know of only have a single type for a
variable (and if you want to pattern match you must give each case a new
variable name).

~~~
kccqzy
> Most other languages I know of only have a single type for a variable

This may be pedantic, but with subtyping many OO languages can give many
different distinct types for a variable. In Java you can assign any non-
primitive typed expression to a variable of type Object. So almost any
expression in Java can be typed as Object.

What you are describing as novel is rather the phenomenon of type refinement
in a pattern match or conditional expression. When an expression undergoes
pattern matching, its type becomes increasingly refined. This is a useful
feature in intermediate-to-advanced Haskell (known as GADT) as well as
dependently typed languages.

~~~
alexcole
> In Java you can assign any non-primitive typed expression to a variable of
> type Object.

Sure, but if you do this in Java you must use different variable names for the
reference of type Animal and the reference of type Object.

I think algebraic data types and type refinement are great, but I’m not a fan
of automagically changing the types of variables in different scopes if it
can’t be applied consistently.

------
nojvek
Typescript is great but it has its evils because it’s a superset of
JavaScript. I really hope we get a modern language like c# where the types are
guaranteed compile + runtime and you can’t any-ignore problems.

The more I use typescript I realize I want a more sound and stricter language.
Like no prototype overriding at runtime.

~~~
jbob2000
Noooo! Go away with those ideas! JavaScript is great _because_ it’s not
strict. That’s the flexibility that allows for speed.

I have like 30 different APIs I implement at work, but I only pull out a few
attributes from each. I don’t want to go to the moon and back creating types
for all this crap, I’ll have more than a 100 different types to model because
of all the nested data and convoluted structures the APIs return.

I want the flexibility to incur tech debt where I want to, I don’t want the
language to get in my way. That’s what makes JavaScript great!

~~~
arusahni
In strongly typed languages, there are serialization/deserialization libraries
that don't require you to type the entire JSON object - just the fields you
care about.

~~~
sjy
I’m not a very skilled Haskell programmer, but every time I’ve tried to parse
JSON in it I’ve felt a strong urge to switch back to Python or JavaScript.
There are libraries that make it easier but I think it’s always going to be
hardly to get started with JSON APIs in a language with a powerful and strict
type system. Of course, that extra up-front effort might be worth it in the
long run due to the benefits of type safety, but as the grandparent said,
sometimes programmers need the freedom to incur technical debt.

~~~
yawaramin
Out of curiousity, what exactly was the difficulty with decoding JSON in
Haskell/

~~~
sjy
I was trying to use Aeson, which as noted in this excellent tutorial [1]
(which didn’t exist when I first tried it) is “hopelessly magical for people
who try to learn it by looking at provided examples, and existing tutorials
don’t help that much.“ The rest of the tutorial should give you some idea of
why I say that parsing JSON in Haskell is complex, at least if you’re used to
doing it in JavaScript.

[1]: [https://artyom.me/aeson](https://artyom.me/aeson)

~~~
yawaramin
I'm interested in specifically what issues you had with JSON.

------
earthly
I still don't understand what pulls developers to Typescript. The examples
given show major flaws in this (duck)type system. That it works when you don't
use the 'any' type and when you know how to avoid all the edge cases and
workarounds, I know. But when I see an example like this:

    
    
      const shasta = new Cat("Maine Coon")
      printDog(shasta)
      > Dog:Maine Coon
    
    

I see a huge red flag. Coming from C/C++ it looks like a joke. Why are all
these front-end static type fanatics not more interested in Dart, Purescript,
Elm, etc..?

~~~
potatoz2
It just means that structural types are the default and you need to opt in to
nominal types, when necessary:
[https://www.typescriptlang.org/play/?ssl=13&ssc=1&pln=13&pc=...](https://www.typescriptlang.org/play/?ssl=13&ssc=1&pln=13&pc=22#code/C4TwDgpgBAIg9gcygXigbwFBW1ARgJwggBMAuKAZ2HwEsA7BLHQgQ2LjoBsQoASUSOQCudGgEch0CiAC2uOJwwBfDBgHQAwi2Ap0TbASJlK1eoxxRW7Lj37gIw0RKmz5ilRgDGHKlE-
byLR1UNDxCEnIAIhgAZQAJSKglKBYKKCCAbi8fHX9gACZA7V08rO86X3YEcngkVFKgA) (there
are other ways as well)

In something like Java, it's the opposite: you get nominal types by default
and you need to opt in to structural types (via interfaces).

Because TS is built on a duck typed language, tons of existing libraries would
break if they had to declare explicit nominal types as arguments. Or you'd
need to add custom interfaces for each library you use. Structural typing is
what you want 99% of the time.

~~~
alexcole
I think that Flow’s approach of using nominal types for classes and structural
for everything else makes more sense. Are there really cases you expect
classes to be structural types?

~~~
jolux
Objects in OCaml are all structurally typed.

------
alkonaut
Being able to gradually introduce types into an existing code base seems like
a poor reason to use unsound or quirky features in a language. I’d then much
rather use a small and consistent language if I’m going to compile to js
anyway, even if it’s not useful as a migration path.

What seems like a good reason for accepting language design tradeoffs on the
other hand is the package ecosystem. A language that doesn’t work seamlessly
with existing _third party_ js is probably doomed. It’s not too difficult to
write a tiny language that is nice and consistent and compiles to js, but few
will use it if you need ffi to interact with js code. The impressive
engineering effort in TS is all about these tradeoffs.

This is why I’m having a hard time loving TypeScript, it’s a language for
solving a real world problem (bringing types to the js ecosystem) in a
pragmatic way, and that rubs me the wrong way. It acknowledges that js, js
developers, and js packages are not going to going away any time soon.

------
habosa
Ok since we have a lot of TS people here maybe someone can help me out.

Let's say I have a simple interface with a few fields. I want to make sure
incoming JSON conforms to that interface.

I realize at runtime the interface isn't there, but is there some tool I can
use to compile a utility to check JSON against an interface without having to
write a duplicative JSONSchema or something else?

~~~
stefan_
io-ts [1] is what I think most people use for this

1: [https://github.com/gcanti/io-ts](https://github.com/gcanti/io-ts)

~~~
shhsshs
Personally I dislike the fact that io-ts requires taking a pure functional
programming approach to this validation. For most cases I just want to throw
an error if something doesn't match up. My main use case for this is
validating that my UI and backend API have the same ideas about the shape of
the data, which will either _always_ work or _always_ fail. All errors should
be caught during development and testing, so the "either" case will never
realistically be hit in production.

Because of this I prefer runtypes [1], because it's much more simple to get my
desired behavior. My only gripe is that errors aren't all that descriptive.

I guess I could write a function to pipe/fold into the type, throwing a
descriptive error if something fails, but I like the simplicity of runtypes.

Edit: I just discovered io-ts ErrorReporter [2]. That's way better than my
solution, and I'm considering switching now!

1: [https://github.com/pelotom/runtypes](https://github.com/pelotom/runtypes)

2: [https://github.com/gcanti/io-
ts/blob/master/src/ThrowReporte...](https://github.com/gcanti/io-
ts/blob/master/src/ThrowReporter.ts)

~~~
Shacklz
We wrote a bunch of utility methods around io-ts at work with custom error
reporting/handling, otherwise it can indeed feel a bit too fp-/boilerplate-
heavy for your every day frontend development, especially in combination with
other stuff such as rxjs/redux.

But once you have those set up, it's an easy breeze, and the benefits are
enormous. Our helper-method with the hopefully self-explanatory name
"validateOrThrow<T>(t:TypeGuardForT)" is essentially called at every point
where there is some form of incoming external data, and the amount of
debugging-headaches that have simply disappeared because of this is mind-
blowing.

------
esjeon
Let me be honest, but this article is just horrible. The whole article is for
the sake of complaining (though I prefer to call this "nitpicking").

> 1\. Interfaces with excess properties

... in immediate objects, which will never be reused. This is nothing worse
than golang emitting errors on every single unused variable. There are good
reasons to behaviours like this, even though not always preferable.

> 2\. Classes (nominal typing)

It seems like the author straight rejects the idea of structural typing
itself, by calling the concept itself a "quirk". This is rather about
preference, not right-and-wrong. If this point is to be true, one should also
reject duck typing, many other popular dynamic languages, a large portion of
software industry, scientific researches, etc. Good luck with that.

> 3\. Discriminated Unions

While I do agree that it's a shortfall of TS type system, the example is
simply unrealistic. Using composite values as type discriminator is a bad
design. Plus, the compiler is not even allowing anything destructive nor bug-
prone.

~~~
the_gipsy
I don't want to go into detail, but everything the author said is true. These
are real quirks, that aren't explained correctly in the documentation.

They are all justified, and the author acknowledges this, but nevertheless
surprising.

------
drderidder
After using TypeScript for the past couple of years, I've come to see it as
simply another "embrace and extend" maneuver by Microsoft carve off a chunk of
JavaScript mindshare, preying upon the naïveté of less-experienced developers
who've fallen for the "static typing is safer" myth. TypeScript's decision to
disallow JSDoc within .ts files [1] for example doesn't contribute anything
positive to my impression of this syntactic pocket-protector. While the type
hinting it provides is useful, the tradeoffs of added complexity, tool
dependencies, and cross-compiling phase have made it more trouble than its
worth in my opinion, particularly since tooling to provide code introspection
and type hinting has already existed for a long time [2]. For new projects,
I've begun using straight JSDoc, which is working beautifully with minimal
setup [3] and provides all of the benefits I ever found useful about
TypeScript, while remaining completely unobtrusive otherwise[4].

1\.
[https://github.com/microsoft/TypeScript/issues/20774](https://github.com/microsoft/TypeScript/issues/20774)

2\. [https://ternjs.net/](https://ternjs.net/)

3\. [https://medium.com/@trukrs/type-safe-javascript-with-
jsdoc-7...](https://medium.com/@trukrs/type-safe-javascript-with-
jsdoc-7a2a63209b76)

4\. [https://fettblog.eu/typescript-jsdoc-
superpowers/](https://fettblog.eu/typescript-jsdoc-superpowers/)

------
leetrout
The first example doesn't bother me and I think it's a weak argument.
TypeScript allows literals[1] as types[0] so it is trying to use the object as
a type.

The correct use of:

    
    
      printDog({
          breed: "Airedale",
          age: 3
      })
    
    

is to explicitly cast it:

    
    
      printDog({
          breed: "Airedale",
          age: 3
      } as Dog)
    
    

[0]
[https://www.typescriptlang.org/play/index.html?ssl=1&ssc=1&p...](https://www.typescriptlang.org/play/index.html?ssl=1&ssc=1&pln=15&pc=2#code/JYOwLgpgTgZghgYwgAgGIHt3IN4ChkHIyYAUARnFAFzIBEFUtAlDQM5hSgDmA3PocXTlKNenABezNh259CRUg1FluU5O04heuAL65cAegPIAculwwAriARhg6EMgAOmsBiEwa7pjmT8CCA6s6AA2EAB0IehcJDDhgiS0gsxMuvpGyACaEKwW1rb2ji6gbpgA4pgAJrFemD7YfvKBIMFhkdGx8aRijEypOkA)

[1] [https://www.typescriptlang.org/docs/handbook/advanced-
types....](https://www.typescriptlang.org/docs/handbook/advanced-
types.html#string-literal-types)

~~~
felixfbecker
Casting can hide errors. It's better to assign it to a variable first that has
a type declaration.

    
    
        const dog: Dog = {
            breed: "Airedale",
            age: 3
        }
        printDog(dog)
    

An IIFE is safe too:

    
    
        printDog(((): Dog => ({
            breed: "Airedale",
            age: 3
        })())

~~~
jahewson
Actually this cast is safe in TypeScript, additional variables are
unnecessary.

The statement ‘const d: Dog’ and the expression ‘d as Dog’ are equivalent
w.r.t. type safety. The compiler will error in both cases the if the value is
not a Dog.

~~~
agentcooper
This is not correct. Following example is valid in strict TypeScript:

    
    
      interface Dog {
        breed: string;
      }
      const dog = {} as Dog;
      console.log(dog.breed);
    

Using `as` is very unsafe in TypeScript.

I've collected more quirks of `as` in this playground:
[https://www.typescriptlang.org/play/index.html#code/PTAEAMEs...](https://www.typescriptlang.org/play/index.html#code/PTAEAMEsGd1AzArgOwMYBdIHtmgLYCGAnqADZZYDW0o0kppRANKAEaLqgrxYBO6KAugCmjUOgAWw3sNAxQyLKADuxcUpkAHXlgAmiVLMjoAUCFAEaBUJsQzxRTbNLCAbqNCoc0dLwOYcADozMABlYVk8PiNkENAJdHRNaAAuEABzYwlEVkCvPGA8SFQdaCx4dGAAFUdhUJLITUqYaERhaGAAdgAWAA4ARgBiFrb8vGFkdABabv6AVgGAZkWABgA2YKQ0ANwYAB4qgD4AClcCUjaU0CqASiuq0ABvE1BQGQFeXDOL4QBuEwAviYTOhaqAAGI4TgAXlAACJSJB0gk4aAAD7w8a6SCIPCojFw1hYUi6OH-
EyQSbSeAEQygAAK0jKuGer3gkF4PgAcgRxlcfLxKel-
kC4gBJXBuaQkVCWWTKKS4cCWOA0+g0dBKWXoVASCy4LCsVzYRAasHSHS8JhxKCwGw6fSGGi6doc4S6NjCCQEY18YKoUiWGgAYSeL1o6CEslhrNetH6AH1LFdHgCLDRGZycNa4-GEzArvtM8yTqmbtbw69oAAmJOpUAAbQRSJRLDhynQ8DhAF106AAIK8XjEPaQyaHHNxmv5+v7MfoBvdk5NxHI9Bwtsdrvd8vA3PQRZ1q7tzuoywQqGTquHgtyaCjqEnE9dm7hgEikxAA)

------
cryptica
>> Real world JavaScript is inconsistent, messy, and complicated

JavaScript is consistent. Expressive languages such as JS are consistent in
their permissiveness. It doesn't stop developers from doing irrational things
like comparing objects with numbers just like TypeScript doesn't stop
developers from writing all other kinds of flawed logic.

TypeScript basically only protects code from typos. The worst bugs I see in
production systems are almost never caused by typos or by comparing
incompatible types; usually they're caused by issues in the flow and
manipulation of data; for example the same instance is being modified in two
different parts of the code without the other part of the code being aware of
it; or in the case of pure FP a function is using outdated instances because a
change in the underlying data source did not propagate through correctly,
etc...

TypeScript is to a software developer what a spell checker is to a book
author; it's convenient but once you run the final product past your editor-
in-chief (whose analogy in the software world are unit/integration tests), the
spell checking didn't actually add any value; at best it saved your editor-in-
chief some time. It has no bearing at all on whether or not you'll get a
Pulitzer Prize or a top ranking on Amazon.

The downside to TypeScript is compile time and this is a significant drawback
IMO because it slows down iteration time significantly. Time spent waiting for
the build to finish is time that was not used to add new tests and new
features.

If you add up all the time that all developers on a project spent on waiting
for the build (or lost their focus/train of thought as a result of waiting for
the build to complete), how many extra unit or integration tests could have
been written using that lost time? I think if you do the math, you will find
that TypeScript is a net liability to the project.

~~~
lazarljubenovic
You obviously never wrote TypeScript with strict flag turned on. It does
control flow analysis, not just typo checking.

~~~
cryptica
It does 'Control Flow Based Type Analysis' \- Meaning that it only analyzes
control flow for the sole purpose of determining type correctness which adds
almost no value to the project.

It doesn't prevent asynchronous control flow issues like for example;

\- Having two different parts of the code simultaneously (asynchronously)
mutating the same object and causing data inconsistencies.

\- Your code starts a new asynchronous job (e.g. setInterval) in the
background without killing the previous/existing job which was launched
earlier.

\- An event listener was registered for the same event multiple times (e.g.
from inside another event handler) without unregistering the previous listener
and so every event now triggers multiple updates instead of one (and CPU usage
keeps going up and you have a memory leak).

\- It doesn't tell you when you forgot to trigger a specific event

\- Or guarantee that two different parts of your code never run in parallel
(asynchronously) when mutating some state.

\- Or that you missed an edge case and forgot to update a specific instance's
property

\- Or that you were modifying the original instance of the object instead of
merely a copy/clone of it as you assumed (or the opposite was necessary and
you made the reverse assumption)

Only a human brain can prevent those issues (and the list of such difficult
issues is practically infinite; my list is a tiny sample) and those are the
real issues in software development, not typos and auto-completion.

Unless you're a web developer and your job only involves writing static
webpages, you are likely to encounter much more difficult problems in your
career besides not having automatic method name completion or typo prevention
mechanisms. These problems I described above are complex enough that they make
all the problems solved by TypeScript seem negligible. TypeScript's compile
time delay becomes a bottleneck when trying to debug and resolve real
difficult problems.

~~~
lazarljubenovic
> the list of such difficult issues is practically infinite

Precisely. No matter how good a language is, you can always say "but it
doesn't do xyz". If you want to replace the human brain by a language, then
you're looking for AI, not a language.

I understand what you mean, and I agree that these things are lacking from
TypeScript. And I would love them. TypeScript is slowly adding more and more
things, mitigating issues one by one, which were previously dissed off by
developers with "it doesn't even do xyz".

I have my job because my brain is smarter than TypeScript's compiler. And it
does help me, because I know which things I don't have to think about anymore,
based on how I write the code. So it does help me, because I think less about
the set of problems which it DOES solve.

------
austincheney
There are problems in their approach:

In the first case the behavior is quirky because they are passing an untyped
object into a function. If the object is typed the behavior is consistent.

The second example is about classes. Classes are created as easy extension
objects via inheritance and poly-instantiation. Those ideas are friendly
concepts for many developers but they increase complexity so I intentionally
avoid classes and use a custom ESLint rule to enforce such.

In the third case they can solve for complexity with a simple refactor. The
two interfaces are structurally identical so they should be a single interface
with either a union on property values (static literals) or by defining a
property again a union of other interfaces called by reference.

------
namelosw
It can only be more complex, then be simple.

If Typescript never existed, people got used to simple nominal typed languages
like Java would never notice there are much more advance type system ahead,
and what can it potentially bring us - the lambda cube, the dependent type,
the Curry-Howard correspondence.

After everyone have a much better understanding in type and the complexity in
Typescript, simpler and more powerful languages would take off.

------
evmar
Regarding #2, if the class has any private properties (even if the classes
have the same private properties) they are no longer assignable.

------
aratakareigen
How does TypeScript ensure encapsulation if it doesn't have nominal types?

Edit: The article says "magical hidden properties".

~~~
andrew_n
Programming TypeScript has a section on “Simulating Nominal Types” [1]. You
define a type that’s impossible to create naturally, and provide a function
that asserts something is of that type.

    
    
        type CompanyID = string & {readonly brand: unique symbol}
        type OrderID = string & {readonly brand: unique symbol}
        type UserID = string & {readonly brand: unique symbol}
        type ID = CompanyID | OrderID | UserID
    
        function CompanyID(id: string) {
          return id as CompanyID
        }
        ...
    

TypeScript can be very confusing sometimes, and given the learning curve I’d
caution someone trying to learn modern JS away from starting out with it. But
in even small codebases it’s an amazing improvement in safety, especially for
refactoring.

[1] [https://learning.oreilly.com/library/view/programming-
typesc...](https://learning.oreilly.com/library/view/programming-
typescript/9781492037644/ch06.html#nominal-types)

~~~
earthly
> TypeScript can be very confusing sometimes

That's not good. A type system should be crystal clear, predictable and
reliable.

> and given the learning curve

Types should not be a complex thing with a steep learning curve. If you know
Assembler and C it's pretty clear what types are about. Added complexity to
your codebase is what you should try to avoid at all times. We have a limited
capacity of things we can think of at a time, developers should therefore
focus on things that really matter. In my daily work I see highly complex
piles of spaghetti in TS that are 'type safe' and easy to refactor..

~~~
potatoz2
I don't follow how knowing C and Assembler makes types clear and I'd love it
if you could clarify.

Nominal types are really a compile time only thing: if you have two structs
(or classes) with the exact same fields, I'd expect them to be stored the same
way in memory. And as a result I'd expect any function to work on them just
fine, as long as they find semantically valid data at the right offsets.

------
iLemming
After using Clojurescript for some time, I don't even understand anymore what
kind of problems Typescript supposed to be solving. Sometimes it feels it
causes more headaches, to be honest.

------
DonHopkins
From the HN discussion about the video of "A Conversation with Language
Creators: Guido, James, Anders and Larry"

[https://news.ycombinator.com/item?id=19568378](https://news.ycombinator.com/item?id=19568378)

[https://www.youtube.com/watch?v=csL8DLXGNlU](https://www.youtube.com/watch?v=csL8DLXGNlU)

I posted these Anders Hejlsberg quotes, who co-designed TypeScript, C#,
Delphi, Turbo Pascal, etc:

[https://news.ycombinator.com/item?id=19568378](https://news.ycombinator.com/item?id=19568378)

>"My favorite is always the billion dollar mistake of having null in the
language. And since JavaScript has both null and undefined, it's the two
billion dollar mistake." -Anders Hejlsberg

>"It is by far the most problematic part of language design. And it's a single
value that -- ha ha ha ha -- that if only that wasn't there, imagine all the
problems we wouldn't have, right? If type systems were designed that way. And
some type systems are, and some type systems are getting there, but boy,
trying to retrofit that on top of a type system that has null in the first
place is quite an undertaking." -Anders Hejlsberg

>Andrew Hejlsberg:

>Maybe I'll just add, with language design, you know one of the things that's
interesting, you look at all of us old geezers sitting up here, and we're
proof positive that languages move slowly.

>A lot of people make the mistake of thinking that languages move at the same
speed as hardware or all of the other technologies that we live with.

>But languages are much more like math and much more like the human brain, and
they all have evolved slowly. And we're still programming in languages that
were invented 50 years ago. All the the principles of functional programming
were though of more than 50 years ago.

>I do think one of the things that is luckily happening is that, like as Larry
says, everyone's borrowing from everyone, languages are becoming more multi-
paradigm.

>I think it's wrong to talk about "Oh, I only like object oriented programming
languages, or I only like imperative programming, or functional programming".

>It's important to look at where is the research, and where is the new
thinking, and where are new paradigms that are interesting, and then try to
incorporate them, but do so tastefully in a sense, and work them into whatever
is there already.

>And I think we're all learning a lot from functional programming languages
these days. I certainly feel like I am. Because a lot of interesting research
has happened there. But functional programming is imperfect. And no one writes
pure functional programs. I mean, because they don't exist.

>It's all about how can you tastefully sneak in mutation in ways that you can
better reason about. As opposed to mutation and free threading for everyone.
And that's like just a recipe for disaster.

And these Larry Wall and James Gosling and Guido van Rossum quotes:

>James Gosling wants to punch the "Real Men Use VI" people. "I think IDEs make
language developers lazy." -Larry Wall

>"IDEs let me get a lot more done a lot faster. I mean I'm not -- I -- I -- I
-- I -- I'm really not into proving my manhood. I'm into getting things done."
-James Gosling

>"In the Java universe, pretty much everybody is really disciplined. It's kind
of like mountain climbing. You don't dare get sloppy with your gear when
you're mountain climbing, because it has a clear price." -James Gosling

>"I have a feature that I am sort of jealous of because it's appearing in more
and more other languages: pattern matching. And I cannot come up with the
right keyword, because all the interesting keywords are already very popular
method names for other forms of pattern matching." -Guido van Rossum

Also:

[https://news.ycombinator.com/item?id=19568860](https://news.ycombinator.com/item?id=19568860)

DonHopkins 10 months ago [-]

>Anders Hejlsberg also made the point that types are documentation.
Programming language design is user interface design because programmers are
programming language users.

>"East Coast" MacLisp tended to solve problems at a linguistic level that you
could hack with text editors like Emacs, while "West Cost" Interlisp-D tended
to solve the same problems with tooling like WYSIWYG DWIM IDEs.

>But if you start with a well designed linguistically sound language (Perl,
PHP and C++ need not apply), then your IDE doesn't need to waste so much of
its energy and complexity and coherence on papering over problems and making
up for the deficiencies of the programming language design. (Like debugging
mish-mashes of C++ templates and macros in header files!)

------
loxs
To me, TypeScript is "perfect". Unlike other strictly typed languages (looking
at you OCaml, ReasonML, Haskell), TS has never prevented me from achieving all
three of:

1) Be type safe enough to guarantee practically bug-free code (at least with
regard to types, you can always have logic bugs)

2) Actually achieve what I want to do in less than a day

3) Upgrade easily when a new version of the language/tooling is released

The only thing that's missing is indeed better nominal typing so that we can
more easily discriminate between logically separate instances of strings and
numbers. For objects it's actually good enough to qualify for "perfect".

~~~
IggleSniggle
TS has led me to a desire for more functionally pure typed code. I'm super
interested in learning OCaml and ReasonML right now so I would be interested
in understanding where you felt ReasonML falls flat vs TS.

~~~
loxs
I definitely recommend trying them, as it will be a great learning experience.
It will probably even make your TS better.

What you won't be able to do is to actually ship a product easily, as the
ecosystem is virtually non-existent. Documentation - virtually non-existent
especially for actually creating working products.

Of course, there are exceptions to this (like the Facebook Messenger being
written in ReasonML), but people tend to underestimate what kind of
engineering effort went into that. If you actually want to do stuff by
yourself, use TS.

~~~
spion
And that's the funny bit because a lot of the quirks in TS are actually bits
that are needed to integrate with other imperfect tools and libraries and
deliver a fully functional product.

------
evolveyourmind
TyspeScript is nothing more than just better type annotations for JavaScript

------
smoyer
Sounds like we're about to see a book titled "TypeScript - The Good Parts".
That was actually one of my favorite JavaScript books and led to my early
adoption of CoffeeScript (since it purposely fixed the "bad" and "ugly" parts
at the back of the book.

------
mpawelski
Well, typescript is constantly improving over the years, witch each release
you are able to represent more real-world JS patterns and type system is
getting more strict options. So this 3 points from article might be improved
in future.

About the point the article makes:

1) I remember when excess property check was introduces I was really happy!
Because I remembered bug that would be caught with it. I had some interface
with optional properties in common library and I renamed property name. Of
course when I updated this library in another project I didn't get any compile
errors because this was optional property and now I was just passing some
extra property which was ignored. Excess property check would have found it
because I was passing object literal.

Structural typing is great choice in Typescript because it's basically type-
safe duck-typing which is used everywhere in Javascript projects. It's not
uncommon to have one component that accept objects with some properties but
this object contains many extra properties because it came from different
component and these properties are used elsewhere. It's standard pattern in
Javascript world. But when your function requires some object of given shape
and you are _creating this object right now_ using literal syntax, then it's
_really_ likely that you made a mistake. Why create extra properties for _just
created_ object when you clearly see they are additional? And I read that
Typescript teams is very happy with this feature[0].

However, excess properties check is great but useful only on call side. I
cannot say that my function doesn't accept extra parameters. I would really
love to get Exact types[1]. Which would allow me to force not having extra
parameter in passed object.

2) As mentioned in article, Flow decided to have classes nominally typed. In
Typescript everything is structurally typed so it's actually more consistent
;) I really hope we'll get nominal typing and opaque types sometime in future.
I read TS team was thinking a lot how to do this. I don't remember what is the
current status of this.

3) For me it's more problematic that Typescript doesn't narrow types of an
union when you do conditional check. The reason is that in general it is
unsound (but I think it would not be if you had only union of Exact types,
that's another reason why I would love to have Exact types!).

Example in article doesn't have this problem so I guess this could have been
improved. Sound like minor issue for me but maybe author could create
suggestion on github to improve it and allow discriminator to be nested?

[0]
[https://github.com/Microsoft/TypeScript/issues/12936#issueco...](https://github.com/Microsoft/TypeScript/issues/12936#issuecomment-284590083)
[1]
[https://github.com/Microsoft/TypeScript/issues/12936](https://github.com/Microsoft/TypeScript/issues/12936)

------
kjtekn
I like how nobody has realized that typescript is classic Microsoft embrace,
extend, extinguish. Oh no, they have "turned a corner". This is a different
Microsoft! Haha.. they are exactly the same. Every single company has wanted
to try and control JavaScript as it takes over more and more of the roles
other languages used to fill.

Google failed with their new language, Microsoft is having some success.
Google claimed "javascript was too slow, so we need Dart". Hmm, turns out to
be complete bullshit and electron applications are now the standard way to
create desktop applications. Microsoft is claiming "you need static typing",
which is more bullshit.

First Microsoft embraced JavaScript, then they made TypeScript to extend it,
and now there is talk of "hey why not just use c# with web assembly etc". "why
not just make deno TypeScript only". "what if Chrome only ran TypeScript,
would it be faster?".

With vscode->typescript->github, Microsoft is managing to gain control of the
masses of mediocre developers, while the good programmers will always be
elusive. A digital divide is getting bigger.

~~~
lexicality
The only electron app I've ever used that wasn't slow/terrible is VSCode which
happens to be written in typescript by Microsoft and it still occasionally
decides to use 100% of my macbook's CPU making it entirely unusable until I
restart, so I'm not sure that's a very good argument.

~~~
ravenstine
Do you think that's actually thanks to TypeScript? From what I know,
TypeScript doesn't have an inherent performance quality that makes it better
than JavaScript.

~~~
lexicality
No, typescript has no impact on runtime performance that I know of.

