Structural typing is really necessary in Typescript just because Javascript objects don't generally carry a class around (at least not if it's defined with the {} literal syntax). I find though, that now that I've used structural typing nominative typing is a lot more restrictive and I generally prefer the structural approach. It's especially nice with JSON objects coming from remote APIs.
It is a tradeoff though, there is one thing I tend to like better about nominative typing that isn't quite as easy with structural typing, and that's reflection. Sure, you can query about keys/fields in the instance itself, but you can't match it back to say a type annotation on a class very easily. Type guards (for things like overloads) can also be a bit weird to write, for instance you can't really do an "instanceof" because that information is lost at run time, so instead you write these functions that can return "T is Cat", and that function will check for something like if the instance has a "meow" function attached. Once you get used to it it's basically fine, but it can be a bit cumbersome sometimes.
A lot of people don’t seem to be aware that there are a bunch of excellent runtime validation libraries that one can use to encode runtime validation checks which produce the typescript types for you (so you rarely have to write ”T is Cat” yourself where it is easy to introduce a false type) but instead can write (or even generate) trustworthy decoders that give you the types. There’s too many of these very-similar libraries to list but some of the more popular ones include io-ts, zod, purify-ts,and suretype.
And while there’s no instanceof that works for non-class structured data like this, (and it wouldn’t be particularly necessary in TS only codebases), even if you were in a mixed repo where there’s a bunch of untyped JS, you could fairly easily create a wrapper with the same behavior of instanceof even without resorting to classes by using symbols. So long as the data is nonmutable, you can effectively get the equivalent of an O(1) instanceof check in JS code, but with a bit more ceremony.
I didn't get into this in the blog post, but we use io-ts at Pilot, and we generate most of the codecs based on the type annotations on the Python side. It works reasonably well, although I think the io-ts syntax confuses people who are new to TypeScript.
> Type guards (for things like overloads) can also be a bit weird to write, for instance you can't really do an "instanceof" because that information is lost at run time
Isn’t the normal way to do this in TS to use discriminated unions, where you just store the type right there on the object as a string (perhaps in a key called “tag” or “type”)? I love discriminated unions, and needing to explicitly carry around that discriminator is a bit awkward coming from, say, OCaml, but it’s still my default way of modeling most things in TS.
When used as a vehicle to increase productivity TypeScript type files begin to resemble XSD files (XML Schema definitions) if you have ever used those. This tends to become more true the larger a project becomes.
So TypeScript is to JavaScript as XSD is to XML.
The analogy isn't perfect, but it's close. The analogy doesn't seem to work for DOM composition at all.
I mentioned TypeScript used for productivity. What that means is not reducing code authorship time, but instead reducing time everywhere else such as maintenance and refactoring time. The common use cases are restricting primitives to unions of accept values where appropriate, defining functions to use required typed arguments and typed output, and defining all objects against respective static types. You want your editor to yell at you as you are making code changes when things don't fit as you intended opposed to finding these problems at execution time.
I recently wrote an XSD file for a fairly complex XML document format that is mostly hand-authored. It's amazing, and I agree with the analogy. VS Code even picked up on it and started to suggest valid values and highlight errors. XML seemed like the wild west before, like JS, but XSD tamed it for me.
When you're working on something where requirements are changing and function signatures are changing (I'm looking at you, every frontend codebase ever written), having your editor yell at you about types until you've fixed everything really cuts down on bugs.
I've been writing Python for the last few months. This week I've been hacking on some JS scripts. I was missing the type hints I'd get in Python via vscode. Until it occurred to me that vscode might support jsdoc out of the box. And lo and behold it does. I was pleasantly surprised.
Sometimes hints and intellisense are 90% of what you need from a type system.
VSCode's support of TypeScript is a boon even if you're writing vanilla JS, because it will pick up types defined by your libraries in use and now you can very quickly/easily get to the interface information without having to dig into source.
TS in VS code is such an elegant experience. I wanted my terminal vim to work (my setup for the past five years) but the productivity gains were too much to ignore.
agree.. you don't even need "strict: true" to get 90% of the value of Typescript + probably 50% more dev time back not fighting types, but I get downvoted whenever I suggest this. I mean blame the TS team for including it if you think it's such a bad feature.
I don't totally disagree, but I've found that you lose whatever dev time you might have thought you gained where bugs crop up and the type system isn't helping you fix them. I've ended up in a situation where the codebase was typed and the test cases weren't, and the test cases had plenty of issues that were ridiculously hard to debug because we let the tests remain untyped.
I’m new to TS and have done python forever. Mypy and python type signatures are catching up, but it’s nowhere as good as typescript yet (in my limited honeymoon experience).
Structural typing is enforced at compile time, duck typing is "enforced" at runtime (by going up in a burst of smoke if you provide something that doesn't quack like a duck).
Duck typing can still be a compile-time thing… C++ templates are “duck typed” in that the template can’t specify the bounds for the passed-in type, but if you pass the wrong type, it still breaks at compile time instead of runtime. (It’s just that the error messages are way more impenetrable than they otherwise could be.)
I’d argue that either “runtime is compile time in the case of C++ templates” and/or “C++ templates are structurally typed”, especially now that concepts are a thing.
ETA: and/or that talking about C++ templates as "typed" one way or another is problematic, given that they're a part of the type system.
These are non=competing qualities. In duck typing there is no actual code interpretation, as in run time evaluation. Instead instances are compared against a type definition looking for conformance and presumed good so long as the compiler does not see a misalignment to the type definition. This is generally good enough when both the type definitions and the code instances are static qualities in the code, but exceptions can occur if there are dynamic mutations the compiler does not recognize such as extending extending a base type with a dynamic quality.
Structural typing uses structures to define relationships and those relationships become points of evaluation to determine subsets. Relational evaluation is most useful in tree models such as DOM (HTML, XML), file system, certain database models like DB2, some machine learning models. The useful qualities of relationship evaluation are that you can evaluate correctness via a path, as in navigation from one node to another, and determine if a code instance is properly composed.
There is some overlap between those two type definitions in TypeScript in that an object interface can be defined from various smaller interfaces. This is mostly done for code reuse, but benefits composition.
You might be interested in the PEP introducing structural subtyping to Python: https://www.python.org/dev/peps/pep-0544/ - there, they use "structural subtyping" and "static duck typing" as synonymous.
I'd say structural typing is static typing (you declare the shape you want upfront), whereas duck typing is dynamic typing (it works as long as it fits, as soon as it doesn't it crashes).
It is a tradeoff though, there is one thing I tend to like better about nominative typing that isn't quite as easy with structural typing, and that's reflection. Sure, you can query about keys/fields in the instance itself, but you can't match it back to say a type annotation on a class very easily. Type guards (for things like overloads) can also be a bit weird to write, for instance you can't really do an "instanceof" because that information is lost at run time, so instead you write these functions that can return "T is Cat", and that function will check for something like if the instance has a "meow" function attached. Once you get used to it it's basically fine, but it can be a bit cumbersome sometimes.