I absolutely love Node for the ease of writing something quick and dirty. No dependency injection, no coding standards, nothing but a tool to quickly churn through a ton of data or to perform a single task really well. I also think there are some frameworks (using Typescript, like NestJS) that do JavaScript apps really, really well. I will still, never, ever, ever voluntarily write any kind of “real” application in a language that is not type safe again. The benefits just aren’t worth the perceived time savings...
But there’s some truly awful stuff in the ecosystem; in the underlying language and the platform’s DNA; in the compromises TS (rightly) makes to be a productive real world tool; in the commonly used tooling; and just peppered throughout everything you can expect to encounter in common third party libraries.
Overcoming all that awfulness requires a lot of additional effort, is inherently limited, and isn’t common in the community (although that too is improving as TS becomes more popular, and as safer patterns become more idiomatic).
I think if I were building a new greenfield project with my choice of platform today, it would be a difficult choice whether to take all that I’ve learned in Node/TS and accept those trade-offs, or to invest in learning another platform.
What other platform do you have in mind?
But I think off the top of my head, languages that I’d look at as first contenders include F#, C#, Kotlin, Swift. I’m sure there are other good choices, for the kind of space I tend to work, that are equally productive and have enough of a community for me to be comfortable adopting them, but I would need to spend some more time researching options to really say with confidence what else I would consider.
All tied in with one singular company each that calls all the shots - For good and for bad. You kinda get all their other stuff showed down your throat along with the languages and things can get bloated fast. But at least things are streamlined. Nodejs had an uprising with io.js in 2014-15 and is now governed by a foundation. I like that a lot. I sleep soundly knowing that no company can pull the rug from under me on a whim.
for (var i = 1; i != Math.pow(2, 16); i <<= 1)
Replacing Math.pow(2, 16) with 1 << 16 effected a 30× speedup--and this was in SpiderMonkey, which tends to be a little less finicky about optimizing mixed types than V8.(It's been a while since I coded PHP, but my recollection is that PHP tries to pull a strings-are-integer tricks a few times).
Yeah, the implicit casting in PHP is bonkers, especially when it comes to comparisons. The comparison table looks like the scribblings of a madman: https://www.php.net/manual/en/types.comparisons.php
(though granted, Javascript acts very similarly.)
That said, after ten years of use, I still see something like:
function_returning_false_or_int() > -1
And sometimes have the wrong intuition about what it does.Now, nobody should write that shit in the first place, but turns out it’s equivalent to ‘!== false’.
‘(int)false’ is zero, but ‘false > -1’... is not true.
Then I tell them about strings and ints. You can represent 66 both with strings and ints, but one is usable for computation where the other one is simply usable to write Alice in Wonderland with it, or simpler forms of text. Sometimes I get the question then: why can't a string both be computational and as a means of character display? And then I list the upsides and downsides of such a system, just like the upsides and downsides of mixing brick and wood to build the outer wall of a house.
I'm curious if people can think of other analogies that they use for teaching.
Personally, I avoid analogies because they usually mean I don’t really know how to teach the topic, and I’m “hand-waving” on the fly. I either find a way to build on the student’s existing knowledge or I “park” the topic for discussion when the student has enough knowledge to give a correct answer.
Run what you have so far. Print the last value you computed. Crash. Verify the data has the "shape" you expect it to. Write a bit more code. Repeat.
On the other hand, Python’s repl was nice for a small handful of tasks (although I mostly used it for figuring out what the actual type of some variable was, which is obviously a non-problem in the statically typed world).
We get editor time feedback of mistakes with optional type hints + light inferance via clj-kondo and a data modelling system that we can export out as database schema, JSON schema etc
And dynamic enough constraint system to express something like all human names must be "Tony" only on Tuesdays
The same constraint that can be shared server side and client side without writing it twice without learning more syntax
Additionally check what the expected inputs for a function are by checking the function specs I think guardrails pro will make this more ergonomic when released
And finally ask the constraint system to generate valid examples of the constraints great for mocking data
I don't miss type systems but I also understand if you're not using solutions here then you're in trouble
Clojure is a good example here, it actually can be used with a very powerful static type checker core.typed, yet its users chose not too for reasons that say in JavaScript maybe a different choice would have been made. The REPL for example catches lots of type errors as you code. Other languages don't have such a coding workflow, so a static type checker feels really great in that it too will catch type errors as you code, etc.
None of that is to say OCaml is "superior" to Clojure in some way. I disagree with a lot of the ways that the OCaml type system has evolved and I wouldn't be surprised to see people who have moved to Clojure from OCaml. However, having programmed professionally in Clojure (although it's been quite a few years so I'm not familiar with the latest advancements in e.g. spec) I still think a Clojure-like language could benefit from a static type system.
I don't think it'll work for Clojure itself because of a variety of patterns and choices in the standard library (which is in part why I think core.typed died, we also found core.typed painful to use in some of our experiments at my old job both in how it interacted with Clojure and the tooling around it). And philosophically Rich Hickey would probably kill Clojure before he ever considered designing Clojure around a static type system. However a programming language based off the same data driven ideas could maybe do it.
While a REPL and hot code reloading are absolutely huge productivity boosts, they are more or less orthogonal to the benefits provided by a good static type system (see e.g. hot code reloading with Elm or Purescript which comes quite close).
The thing that static type systems provide over tests and runtime contracts is the ability to constrain users of the API of a library. We use regression tests to make sure regressions in code we write doesn't happen again. Likewise types are effectively regression tests at the API level to make sure certain regressions in code that calls our code doesn't happen again. That is an extremely powerful capability that I consistently miss in dynamically typed languages.
That's what I find interesting about it. Not all language benefit from a type system in the same ways, some, like Clojure, actually get crippled. Now, it can mean that you need to find the right kind of type checker that provides the correct ergonomics for Clojure and maybe that would work. But it's still quite interesting.
For example, Erlang has a bit of a similar thing, Dyalizer made specific choices to work within Erlang's design. Had it not done so, it probably wouldn't have found adoption. Same with TypeScript.
So what's interesting here is that you have an apples to apples comparison where a language is found to be better without the constraints of static type checking.
When you look at other statically typed languages, they're oftened designed around the static type checker. That's the main focus, and the language itself revolves around that. So obviously in such a language, the type checker would be a necessity, as it's the main draw. So it's interesting to look at Clojure for a counter example.
That said, JavaScript you could argue is also an apples to apples comparison, and people opted for types. It'll be interesting to see also where Ruby and Python go, now that they have type checking features as well.
You can go crazy and spec everything, in fact that can help, but:
- In practise, nobody does it
- Specs come with no guarantees. They could even be wrong.
- The official implementation stubbornly insists on not checking return types, so half of your annotation may just be glorified documentation (although you can use third party libs like orchestra)
Just imagine: Add a new required field to a spec, and get a convenient list of source code locations that you need to review. That's the promise of a statically checked system. It's not a silver bullet, but not having this leads to what I like calling "refactor anxiety" (i.e.: did I handle all cases?)
I still love clojure no matter what. I think in practise you can express so much, so elegantly, and with far less code, that your project size is always sorta manageable.
Ultimately I think people just weren't given any credit for making good, reusable types, because then the next dev who submits a better feature faster using your work gets a raise, but you look like a kook ranting about best practices who doesn't do anything "business".
Bad type systems need you to use the escape hatches frequently - you can't write much C without using casts.
I haven't yet used a language with no need at all for an escape hatch - but some languages need them far less often than others.
Java's type system is better than it was. The main places it still has weaknesses are around exceptions (you will need to wrap checked exceptions in runtime exceptions in places you'd have preferred to specify an exception type via generics, eg) and the occasional cast after an instance type check.
You can phrase good development practices in business terms: what are the risks to the business due to sloppy code? Are they greater than the risk of being slow to market?
Sloppy code has accumulating costs. A good type system can help greatly with refactoring to address those costs, but it cannot help with the attitude that those costs aren't real and don't need to be paid.
Help a lot to start with something SO wrong :)
My first dynamic language was python, and the quality of the docs and overall apis of major libraries soft the landing.
But later I note that was parts of MY code that become a mess. The problem of delay of good design? is that good design GETS delayed.
And then fix that later is a problem. Major libraries and core APIs have the time frame and focus to polish them, but the rest of the code most do?, not much, so it stay in the awkward phase of "later will be refactored, maybe. Perhaps..."
Then later I move to F# and rust and can't delay bad design for long.
Is a chore to slow down at the start of the coding phase but later the speed up is huge: i don't need to fix my past mistakes by the truckloads...
What it doesn't support is any sort of readability as a code base scales. It's often hard to know how the function you're calling will behave because the param can tweak some flag and totally change the behavior.
Using PhpStorm, I've worked with 100 kloc PHP codebases where almost every type was inspectable. And the language support and tooling are only getting better all the time.
1: https://stitcher.io/blog/new-in-php-8
2: https://www.php.net/manual/en/migration71.new-features.php
3: https://stitcher.io/blog/typed-properties-in-php-74
4: https://phpstan.org/blog/generics-in-php-using-phpdocs
5: https://blog.jetbrains.com/phpstorm/2020/07/phpstan-and-psal...
At the moment, you have to put a $ in front of most variables, and no adornment for when you want to call something as a function. Reminds me of Common Lisp with its two name spaces for functions and other variables.
A few decades later, I really value them. My code now lives for decades, not for weeks. Lines are measured in the hundreds of thousands. Other people work with me. Types eliminate a set of errors. They're worth the effort.
I think time is where the tradeoff lies. How long are you writing for? If your code is only for this week, types maybe are a net loss. If you're writing for a month, maybe it's a toss-up. But if you're writing for a year, types are a net win.
I still hate typing in Java / C#. I feel intellectually insulted from time to time. There are too many times I can naturally express something in natural languages or just JavaScript, but cannot easily express those in Java or C#.
TypeScript is great, it lets me express many things I want, but it's more or less bloating and inconsistent. I always have to worry if the signatures are hard to read for the team members.
Interestingly, TypeScript reveals a lot of use cases to the mass - there's more to type systems. It's not possible to write a typesafe 'printf' in most of the statically typed languages. Maybe a full-sized dependent typed language like Idris will come in sight in the following decades.
Ruby flexibility is amazing. I love the language and syntax frameworks built around it. Amazing.
Except for investigating and refactoring. Everything in Java, even in worse written systems, was fairly easy to understand and find using intellij and friends. Meanwhile in ruby if someone gets messy it might be impossible to figure that code out.
I like the concept of typescript best. Types when you want them. No types when you just prototyping. It was actually my favorite aspect of Adobe flex when I had a short stint coding in it 12 years ago.
The great thing of Java was garbage collection. No more malloc/free hell and programs still worked.
The great thing about Perl and the other scripting languages was no type declaration (plus garbage collection.) And programs still worked.
After 30 years I kind of agree with the author of the OP: the time lost annotating programs with types of not offset by the benefits. In almost all cases type discovery is something the computer should do, not me. Same as for memory management: automatic, not manual.
I loved them because, well the things you mentioned.
But I hated them because in C++ it was required to open a separate header file to declare them.
I started off in PHP. It was wild. Anything could be anything. Refactoring was a nightmare. Our codebase was littered with mystery variables like $hold, $hold1, and $holda, which were re-used all over the place. (Granted, this two other problems entirely orthogonal to types.)
Then I got a job at a Java place. My god, it was beautiful. I could suddenly know what arguments functions were expecting, even if I hadn't seen them before. I could be instantly aware if I was trying to use the wrong variable somewhere, or pass in invalid data.
It was as if someone lifted me out of a dank cellar where I had previously subsisted on whatever mushrooms the rats didn't deign to eat, into a brightly lit dining room full of steak and pastries.