Hacker News new | ask | show | jobs
by Scooty 2548 days ago
My biggest problem with this claim is typescript doesn't include any support for runtime type checking.

My experience with TS has been great until I need to deal with data that comes from an external system (API, client, database). Then I either have to cast the type (which can lead to bugs where compile and run time types don't match and IMO isn't an option server-side) or I have to effectively duplicate my types by writing type guard functions.

3 comments

Speaking of TS, here is a good argument for static typing:

>Change a front-end React prop, then follow static type errors through the statically typed API, into the backend, all the way down into the database where they force a change in the Postgres schema. The system won't even compile unless React can talk all the way down to Postgres.

https://twitter.com/garybernhardt/status/1140695491685933060

You’ve just described hell.
Okay, let me be more specific. I've built complex React apps full-stack down to Postgres and maintained them over ever-evolving/increasing product requirements. To schedule. With a tiny team, part-time dedicated. And little maintenance/defects/outages in between feature pushes.

The success here lied in using various tools, applied pragmatically at each step according to the given context. Probably the most important tool is defensive coding and building up a system from decoupled, almost visibly-correct components. Then some nice technical tools thrown in: dynamic development with hot loading, some modest storybooking of components, a repl, good backend libraries, conscious and measured unit testing, strong build/deploy/release tools.

The ability to turn on a dime and incrementally refactor things was paramount to the success and efficiency of this development.

Then someone says we need to use strong static typing. All the way. No judicious application of this tool, we'll use it everywhere. We'll make everything cohere to the type system. This is no pragmatic choice. This is religion. You know its religion because its an ideology that pervades the whole system. There is no judicious application. The extreme tax of contending with types as your system evolves is high. I've only ever seen strong typing enthusiasts deny this truth. Will one enthusiast be honest here? You can literally stand over a strong typing enthusiasts shoulder as they spend an hour running all over their system adjusting their types for one minor change in business requirements and they'll still say, "No, no, types don't cost anything..."

You sound smart, but I don't want to have to be that smart when I'm refactoring code. I want the compiler to do as much thinking for me as possible. I want to be able to change it in one place and have type errors to remind me of any other places in the codebase I forgot to change.

Static analysis doesn't solve all your problems, but it solves enough of them to be a very useful technique.

Static typing cannot ensure about their code what the coder can and should ensure about their code.

And often -- very often -- especially in strongly typed languages -- the static checker will reject code that is otherwise valid. First point.

Second point: Statically typed PLs (especially strongly typed ones) enforce the verifications across the code, with no (at least non-ridiculous) way for the coder to be judicious about things.

Third point: statically typed languages have weaker runtime features/abilities: polymorphism, homoiconicity, true REPL (ie true read->eval), etc.

All of these points add up to MORE cognitive complexity for the coder who is trying to develop non-trivial business applications -- not less cognitive complexity.

It's not being smart. It's learning. Once you learn algebra, say, many classes of problems without algebra would be too much cognitive complexity.

Type systems are focused on one thing: type coherence. But the real world demands runtime dynamics are what the customer is paying for; being as close to that need as possible IS lower cognitive impedance.

"enforce the verifications across the code" is the point.

I've worked with a ton of Python code that looks like this:

    def foo(bar):
        # do stuff
        return baz(bar)
What does `foo()` return? I have to go read the body (or docstring if I'm lucky) of `baz()` to know that. And `baz()` might be another level of indirection to `quux()`. If I change what `baz()` returns, now I need to grep my codebase for all call sites of `baz()` and verify that the new return type is acceptable at each of them, and make changes if not. This is super time-consuming and error-prone, especially when a compiler (especially with an IDE refactoring tool) could do take care of it for me in seconds. It's easier if I have a good test suite, but that means I'm just implementing static type checking with runtime tests, which is more code that I have to maintain.
Yeah, it's much better when you have to change something, and you miss an important step in the guts of the code somewhere, and the compiler DOESN'T warn you, and you continue merrily on your way, unaware that six months from now when that code path executes in the wild, it'll take your company offline for six hours while someone (probably you) sweats their box off trying to figure out what the hell just happened.
This scares the hell out of me when I hear people say stuff like this, because if you think a type system is going to save you from your outages then that means you're not aware of a whole other more comprehensive, efficient and dynamic set of programming idioms and tools that will actually save you.

Here's how you know what I'm saying is true. Because you can go across the landscape of systems in the real world and see both crappy systems falling over and rock-solid systems humming along happily -- and the differentiation between these two categories is NOT a type system. It's one thing: it's who wrote it and what was their level of experience and what was their value system.

It's not going to automatically save me any more than a motorbike helmet will save me if I crash my bike. That's not going to stop me from wearing one, though.

I actually agree with your second paragraph, and I like to think the systems I build fall in the latter category, but I'm still not too proud to take whatever help the compiler can give me.

You would stop wearing a bike helmet if:

   - It made you top heavy and more likely to crash
   - It reduced your speed in half
The tacit implication (or elephant in the room) when someone argues for types and the presumed safety they bring is the severe cost they impose. Now they save some costs too, for sure. But the balance that I see is far, far in the direction that they save much less than they cost. (When applied universally as they almost always are when applied.)
Check out https://github.com/woutervh-/TypeScript-is. It works as a compile-time transformer which emits the type guard code for a given type and narrows down the type for you. You just call `is<T>` (returns a bool and narrows if true) or `assertType<T>` (narrows or throws an error). We've used it in production with great success.
It would be really nice to be able to coerce/validate incoming data (JSON I assume) at runtime against an interface definition. However, I feel like something like this goes against most of TypeScript's other stuff that aims for low cost abstractions on top of JS.

That said, even if I have to write type guards at the edges (which I'd do in JS anyway, just because you aren't using types doesn't mean you don't need to enforce schemas), I think being able to rely on compiler typechecking inside the codebase boundary is really nice for making module interactions more predictable.