What's more, for a programmer who doesn't get the value of types, the major downsides are already apparent, at least at a basic level. Doesn't this make my code more verbose? Doesn't this get super confusing sometimes?
It's an article design to help certain programmers learn a particular thing, not an article meant to satisfy more experienced programmers' desire to see all sides of an argument acknowledged.
How could the downsides possibly be apparent if the upsides are so mysterious they need an article to spell them out?
> It's an article design to help certain programmers learn a particular thing
Have they actually learned that particular thing if they don't know the tradeoffs they're making? I would argue they haven't. You need to know what you're getting and what you're giving up before you can decide whether something is worth using at all. There are too many articles hyping the upsides of technology X, but nobody asking what the downsides are.
In any case, though, wearing a seat belt or not is a choice one can make independently of all other factors. The cars come with seat belts, it's the same car either way. You click or not, nothing else changes about the car.
With types, however, that's not how it works. The tradeoff is... you may have to change your entire programming language, change your IDE, change your frameworks, rewrite existing code, etc. It's not analogous to seat belts at all. Do programmers want the compiler to catch mistakes? Of course they do, in an ideal world. Why wouldn't they? But there are a lot of tradeoffs here that don't exist in the case of seat belts.
Like seat belts, you can choose to wear or not wear a helmet independent of all other factors. The motorcycle or bicycle is exactly the same regardless of whether you're wearing a helmet. Also, everyone understands the benefits of a helmet. The benefits don't make everyone wear a helmet, but everyone is clear about why there are helmets.
How is this any different from the selt belt case? "(1) believe they'll beat the statistics, and thus no statistical argument will convince them or (2) value their "freedom" a lot more than they value their own lives"
Would you really expect an article "The helmet is a biker's best friend" to convince them?
Everyone knows that a helmet helps prevent head injuries. That doesn't need to be explained. Whether the tradeoff of messing up your hair or whatever is worth it is up to the individual to decide, but there's nothing complex about the decision that needs an academic discussion.
Besides, they aren't going print this article out and take it to a cave in the mountains to learn about type systems for a year. They're going to read other stuff along the way.
The trade-off is obvious: you gain confidence about your program but you need to Learn More Stuff. Nobody's talking about the downsides of type systems except to the extent that they're worth talking about: see the comments here every time someone compares the type systems of Python and Rust.
This is why engineering/software articles in general (this one included) needs to bring up tradeoffs more often. No, "learning more stuff" is not a downside or a tradeoff, it's just a fact of learning anything.
That you introduce more coupling is a tradeoff. That the program (sometimes) gets harder to change is a tradeoff. That is becomes easier to write large, messy programs because programmers feel more safe in the future to refactor, is a tradeoff. Trying to fix each one of those tradeoffs also come with their own tradeoffs, and so on.
These are "apparent" for me, when talking about languages using static types vs dynamic languages, but it is not apparent for everyone. So when bringing up these "obvious" upsides, also bring up the "obvious" downsides, as it seems quite a lot of people don't see it as "obvious" as we do.
That’s not inherent in static types.
You don't introduce more coupling, you document the coupling that already exists. If your program is hard to change with types, it would be hard to change without types - but easier to change incorrectly.
> That is becomes easier to write large, messy programs because programmers feel more safe in the future to refactor, is a tradeoff
Sure, I guess this is true in principle - but you could say the same about IDEs, or version control, or grep. The effect size is small.
This is true at the code level. But at the system-design level, this documentation is the extra coupling.
I feel like it's important to understand this. I agree with the original commenter; in engineering, nothing is truly free. In many cases, this extra coupling helps keep a system strong and stable, like extra nails holding planks of wood together. In other cases, you may find that part of a system's spec actually missed the mark and now needs to be ripped up and redone. That extra coupling might now work against you!
Again, that doesn't mean that it wasn't worth having it. It is just important to understand tradeoffs in engineering.
No, the coupling (i.e. assumptions about what type this thing can be) is implicitly there in a typeless language. If you pass in the wrong thing it'll blow up. But it'll happen in production.
The coupling is always there, it's just a matter whether you make it explicit (this allowing errors to be caught early) or you pretend it's not there and let things crash in production.
You still do need to know that stuff if you have no types in your language, but the compiler won't help you.
If I update a type, the compiler will tell me every single location where I need to make a corresponding code change. For grug: change type give red squiggle, make change code good
Also, grug makes a good point about the temptations of generics, but I think they’re exaggerating the impact to the speed of development.
I forgot about this, thanks for the morning laugh. No such thing as a free lunch.
I’m sure one could argue that member names should obviate the need for type annotations.
There’s also the distinct possibility that my preceding ~15 years of statically-typed software development have affected how I think about software development in some way. (wink)
But I am finding type annotations internet useful while working on a huge application that is about 2 years into adding a gradual typing system, enough so that I usually take time whenever I enter a new code area to add annotations to everything, just to understand what’s going on.
My perception is that I invest time to build understanding of the types, and then document what I’ve learned in the form of these type annotations so that future maintainers then gain a quicker understanding without having to do the initial research.
At least my non-statistically-significantly-sized team agrees.
At a syntax level perhaps, but not necessarily at a semantic level. This won't apply to everyone, but I've noticed that the more types are relied on, the less my co-workers really understand the code. They're relying on the compiler so much they don't slow down and think through the changes they're making. In one extreme case I saw a guess-change-compile workflow that relied entirely on the compiler doing the work for them.
There are only tradeoffs when we are at the frontier of what's possible with a set of technologies, and so must trade off on something in order to move along that frontier[1]. Many languages aren't operating at that frontier, and adding static typing is free (in the marginal case, ignoring the substantial effort to implement the type system). If you start a greenfield Python project, and you start typing right away and incorporate MyPy into your CI and IDE - it's as close to free as you can get, and the benefit is substantial.
[1] Eg, like in this diagram, http://image1.slideserve.com/2488675/production-possibilitie... - we only need to trade off on guns & butter if we're along the frontier (the blue line), if we find ourselves somewhere in the middle we can just make more stuff until we reach the frontier.
A way this often gets worked around is code generation. Anywhere you have or reach for codegen in a static language, you probably could have used a plain old function in a dynamic language.
I think this is a dangerous position to take to extremes/as an axiom.
Not talking about type systems at all here. The assumption that, given two tools/techniques for accomplishing the same goal, there are always equivalent tradeoffs simply isn't true.
Some tools are better than others.
That statement usually provokes misinterpretation. It should not be taken to mean:
- That some tools are always better than others, in every context. There are situations in 2022 where COBOL is the best choice for new code, and other situations where rewrite-it-in-Rust is the best choice. Problems occur when "tradeoffs of tool A (even if we don't know what they are yet) make it equivalent to tool B" is a core tenet of decision making.
- That some tools always have been and/or always will be better than others. Context, expectations, tool capabilities, and available programmer talent pools all change massively over time.
- That one tool is better than all the others. Plenty of times there are multiple ways to deliver optimal-given-constraints outcomes, and it comes down to a matter of taste or "just pick something, anything, and let us get to work".
Chasing hype and cargo culting leads to poor outcomes; "we should build our two-core app on Kubernetes/write our 2TPS app in Rust" are often justified with "because it's the future" or "because the cool kids are doing it". That's a major bummer.
But the opposite extreme is just as bad: assuming that all choices are fundamentally a wash because "to get something, you have to give up something else" is just as methodologically irresponsible as following the hype cycle. Programming isn't alchemy. This kind of bad decisionmaking can lead to dependence on obsolete (unsupported/insecure) tools, difficulty hiring, and, at worst, a culture of "don't talk about Python to me; if you can't freehand it in C you just need to get gud" gatekeeping cruelty.
Everything has tradeoffs. That doesn't mean they're equivalent.
The issue is that people from one side will always downplay the tradeoffs (or pretend they don't exist, like the current author). This exactly how hype and cargo culting happens.
At this point in the trajectory of software engineering, it's fair to assume that most of the low-hanging fruits have been picked, and solutions that are unequivocally better would have to bring something fundamentally new to the table (which types are not at all). Most solutions will be picking a particular point on a trade-off isocurve.
Apart from that, it's always fair to ask someone who's strongly proposing something what the downsides are.
Yeah they’re definitely programmers, but anyone who’s had to maintain and deploy what a data scientist came up with in a notebook will question whether they should really be using types.
As for types, I’m not saying they’re flawless. I’m pointing out that in the transition from assembly to high level languages there were flaws and criticisms that came out. But looking back fifty years, were these flaws and criticisms genuine tradeoffs that kept assembly as a reasonable option for most developers? No, they were not. Now we look back on programmers who insisted on writing assembly as oddities, as niche figures. I cannot say if this will be the case for types, but I won’t rule it out.
We had conversion functions between them, and type inference when you performed certain operations.
The result was a disaster. Not an enormous disaster, but enough of a problem to rip the entire thing out and replace it with plain double-prevision floating points, and sensible variable names, everywhere.
Why was this? It was a combination of there being nothing sensible to infer when you, say, multiply an angle and a distance (which happens when you're doing algebra), and everything in the whole codebase needing to be aware of these types.
The downside of all these brilliant ideas is dependency. If you define an "EmailAddress" type, as the article suggests, you've got to write the code for it somewhere. Now all your projects are dependent on this library, with all the pain and anguish that versioning and linking/including/whatever the library brings.
Before, you depended on nothing but the String type, which is very likely built into your language. When all your code needed to do was pull that email address out of some persistent store (say), and send to some other piece of code, your dependency list was just the Persistence library. But with your fancy EmailAddress type, your dependencies are now much worse.
Keep things as simple as they can be. An EmailAddress type is not useful.
wtf? metres and centimetres are not different types! they are just different ways of writing same type: Length. radians and degrees are just ways of writing a dimensionless Angle quantity. you made the absolutely elementary mistake of conflating a physical quantity with the unit used to measure it, of course it was a disaster.
>nothing sensible to infer when you, say, multiply an angle and a distance
angles are dimensionless so they should just be a distinct type of float. there is literally no problem here.
typedef float cm;
typedef float meter:
cm a = 1;
meter b = 1;
if (a == b) {
// launch rockets
}
I’ve done something similar (not including rockets, don’t worry!) in Swift with its typealias feature. Thankfully there is a way to actually force compiler errors in such situations with something like https://github.com/pointfreeco/swift-taggedBut I'm not sure that's much of a tradeoff. If I don't know what type this thing is, how do I know what operations I can safely do on it? How do I know that I can make it do what I'm trying to do? Or will it blow up at runtime when I do that?
I consider "I coded it, it's done, but it might blow up at runtime" to be highly unprofessional. "We covered that with unit tests" is theoretically OK, if you've got 100% test coverage. But you don't, and you never will.
Having to say everywhere what the type is gets tedious. Autocomplete (and "auto", for those languages that have it) help a bit here, but only a bit.
https://hirrolot.github.io/posts/why-static-languages-suffer...
The key thing the author identifies is the two languages problem. Static types are a second program about the program, and that's great when the second program is simple declarations that keep you from passing one struct when another is expected.
But a flexible language needs more than that, and generics end up either Turing Complete or bad in some other way.
I'll be mulling this one over in years to come.
Usually moving from primitives into complex types does not account for serialization and deserialization between db and the client. This can be very annoying to work with in something like C#.
Usually it ends up resulting in alot more types and a lot more mapping between types.
However this has its own benefits, but is very boilerplate-y and is sluggish to work with when your domain changes.
Luckily, for C#, https://github.com/SteveDunn/Vogen now exists thanks to source code generators which soothes some of the issues.