Assuming ample experience with both, how does one reach this conclusion?
I have yet to see a project of any size that needs to be worked on by multiple teams and is written in an untyped language not descend into dumpster fire.
Sure, at boundaries between teams, you need to specify the data in some way. That could be a type, but for me, often the other team is using a different language than me, so it needs to be a language agnostic type, and it can't include unsigned numbers because Java can't cope, and it can't include large integers because Javascript can't cope, etc. Protobufs are popular, json is too.
I have a lot of unpopular opinions though, and that's fine. It's just tiresome that everyone wants to come in and add types to things that don't need them. Also, I agree with dllthomas, most developers and teams are capable of creating dumpster fires in all sorts of environments, with all sorts of tooling. :)
Putting the fire out in an untyped language is a Herculean effort.
That said, a dumpster fire usually has no or little tests, so maybe we're arguing non-existent hypotheticals :|
However, a language with an insufficient type-system indeed makes things harder than they are without it. I would count all the languages you listed into this category.
As another poster mentioned, typescript is fairly expressive. There are other (production) languages too, such as Scala or maybe D. And there are lots of academic/very-niche languages.
> It's just tiresome that everyone wants to come in and add types to things that don't need them
Well, types are there, if you like them or not. There's a reason that you have e.g. typeof in javascript, gettype in PHP. The question is rather if you explicitly annotate them or not. But yeah, sometimes it's not helpful to annotate types, especially if the language is incapable of expressing the correct type anyways, which is true for most programming languages.
Disclaimer: I work at Microsoft, but not in the Developer Division
I have started programming in untyped languages, but simply can’t remember back at all, and now I can’t really imagine dealing with objects in my mental medal as not having some type.
Note: this is not a rebuttal for/against dynamic typing, I do think that types are really important at boundaries, but they may not be the silver bullet - contracts may be better at some things, for example. This may be an open question.
In Elixir, I do think about shapes more than capabilities (because Elixir is not OO), but with pattern matching, I can either specify "this must be a MyApp.Account struct" (which is just a fancy map) or I can specify "we will handle any map that has the keys X and Y, and Y must be a map itself".
I replicate this more formally when writing TypeScript, usually by building up type definitions and specifying those.
Look at Erlang. It has bigints, floats, Booleans, but-strings (sequence of bits —added because it is so common in telecom), string (not technically a primitive data type), functions, atoms, list, tuple, and map.
None of these look the same or act the same. People dream about seeing `123 == myMap`, but it simply isn’t a common thing because it doesn’t make sense.
The common rebuttal becomes: but how do I know if property X is a string or number when I’m using it?
If that’s your question, you are already messing up. What you really want to know is what X actually represents. Otherwise, you’re just shooting in the dark which is at least as dangerous as getting the type wrong and probably more so because a wrong type will become obvious quickly while mangling that number or string may not be caught until a much later time after serious damage has propagated throughout the data.
Let’s say you have something called `login`. Is it a number or string? If it’s a string, is it an ISO date, UTC date, or something else? Is it when they logged in or when their login expires? If it’s a number, it could be a Unix string. It could also be a calculated value for how long the user has been logged in and could be days, hours, minutes, seconds, milliseconds, or something less common.
How do you know which thing is correct?
In a good codebase, you read the docstring comment on the data constructor that describes what it does. If it says “milliseconds since last login” vs “token expiration using ISO datetime format” do you have any question at all about whether it’s a number or string?
If there isn’t a docstring, you’ll be digging through that code or playing around with the responses and will see the data type anyway.
The result is that you’re forced to better understand what you’re doing which isn’t a bad thing in my opinion. There may still be mistakes, but that leads to the next point.
Dynamic languages generally make it easy to dynamically check the types of incoming data and tend to be more flexible with mistyping (especially JS). I can’t count the number of major errors from common typed languages because they make introspection hard, so programmers don’t do it and crash on malformed data or when an API suddenly changes.
It’s also worth talking about null exceptions. Many dynamic languages expect type weirdness and handle it well. This usually includes null. Most statically typed code out there has LOADS of null exceptions lurking about which only trigger in obscure cases during runtime. In this regard, you could argue that the worse type issues also happen in typed languages, but are more dangerous in those languages too.
Untyped languages also tend to use safe numbers everywhere. Infinity is mostly useless, but not usually dangerous. Bigints everywhere are slightly slower in some cases, but completely eliminate overflow errors. Most typed languages use risky numeric types, so they must also force users to think about those things.
Finally, no common typed language offers good runtime introspection like smalltalk, Common Lisp, Erlang, or even JS. I feel static types are just a crutch to make up for this deficiency.
That brings us to good static languages. Typescript offers all the benefits of normal, unsound static typing combined with all the robustness of JS’s dynamic environment. The same can be said for Coalton and Common Lisp.
StandardML offers a language that feels like a dynamic language, but still offers static checks that are actually sound and completely eliminates null exceptions. Rust (an ML in spirit if not in syntax) does the same things in environments where garbage collection and other such amenities aren’t possible.
I like both kinds of languages and good examples from each word around the problems of each approach to make them (in my experience) about equal in productivity for equally skilled and experienced developers.
Some times, some type systems actually make people jump through hoops to accommodate their design and then it can actually have a negative effect. Other times, the typing helps.
It’s kind of like really good grammar and punctuation. They can make a story you write better and clearer. But they far from guarantee it. You can write a very good story with subpar grammar/punctuation. And you can write a really lame story that is grammar perfect.
One thing that I haven’t seen much in the discussion, is any discussion about Elixir’s matching abilities. Does Rust have that as well? I love what Elixir matching does for my code.
(Edited spelling)
The issue is working on projects once they've reached a certain size where you have no idea what the intent of the original author was and you maybe need to refactor, add-in major pieces, or change anything with the expectation that it continues to work.
I think it’s a question of understanding how to work and think without explicit types rather than something that makes statically typed codebases easier to maintain.
They are exceptionally easy to refactor, add-in new parts, etc.
The keyword is microservices, you need to know how to do proper microservices if you are using untyped code.
Can we say that's misuse of the tools? Sure. Is it less likely than things becoming a mess without types? Probably? Even more so as the tools improve and as the people involved know better how to use them.
It is easy to be maintainable at iteration 1 when the requirements haven’t changed 20 times yet.
Github? Dropbox? I mean they both eventually went to type hints in their respective languages or migrated to a typed language but for a long time I'm certain it wasn't.
The only thing you can't do is have both untyped and monolithic at the same time.
Dynamically typed microservices is where it is at.