At least to me, the big advantage of static typing is not that it (allegedly) reduces bugs, but that it aids my understanding and helps in navigating the program. It's a tool for thinking and communicating.
At least to me, the big advantage of static typing is not that it (allegedly) reduces bugs, but that it aids my understanding and helps in navigating the program. It's a tool for thinking and communicating.
My working day jobs have been mostly C++, and these days C#. Periodically, I will temporarily inherit some of my younger colleagues' projects, if they move on to greener pastures in different companies, with the charter of "can you do something about the long-running issues this software has been having?" My go-to solution is to go through their typescript and add return types to their functions, and replace their anys with interfaces. After having done that, I fix the bugs that revealed, and then I'm usually done. Recently when I did that, I came across a central class/data structure, which turned out to exist in no less than 5 slightly different variants. i.e. different parts of their code adhered to 5 different assumptions about what fields would exist and be populated (but all expressed on the blank canvas of 'any').
The problem is, other people with just as many credentials as you have the opposite experience. From an outsider's perspective, two people with equal authority say opposite things, what can they possibly do except an independent study?
Also, note that there's a reason anecdotal evidence is not always reliable. E.g. the famous story about fighter pilots and the "regression to the mean" hypothesis.
In this scenario, I honestly don't think it matters whose objectively right. Software is not a clean, normalized and organized set of use cases after all, maybe static typing works for person X and doesn't for person Y because of their background, or preferences, or codebase requirements, and so on.
Maybe one day we can conclusively prove that on aggregate static-typing/{insertThingHere} is overall less buggy, but even if we did, it'll still change depending on circumstances.
On the other hand, though: have you worked with large, thoroughly tested projects in a dynamic language? Personally, I find that good tests catch 99% of the bugs that static types do, plus quite a lot of other bugs as well. Arguably, you ought to write tests anyway to find those other bugs. Since they also find your typos etc, you get to enjoy the ergonomics boost of dynamic typing almost for free.
That's my (also anecdotal) argument for doubting static types.
Removing traffic lights and stop signs actually reduces accidents because drivers are more careful when driving through intersections which reduces speeds and drivers become more alert.
Developers will adapt to their toolset. If you have a statically typed language, you trust it will deal with type related issues and you become more lax with testing things related to types. When you develop in non-typed languages like Ruby, you tend to write more tests and not trust your compiler (because you don't have one). This is why you will find most Ruby developers are really good at writing tests and embracing TDD.
A type system can keep you from having to write those tests.
Because with a proper static lang (hint: not Java, not C#), nil doesn't exist? Right.
The claim is true: a type system _can_ prevent null-related issues and eliminate the need to account for them in tests. That's not the same as saying every type system does.
Static typing gives you assurances and tools with which to test your assumptions in the code, for those times when reading the whole stack is cumbersome, and you need to defend against less careful developers. It also transfers a bit of knowledge between developers in a trivial way that would otherwise be a pain to communicate.
In my experience similar arguments hold for software developers. Especially caring can be a big factor; i.e. the "move fast, break things" mentality.
I've been back and forth between typed and untyped languages (somewhere in the range of haskell and tcl) and personally prefer less typing when hacking things together and more typing for high quality software. I'm currently working an infra job where we use both ansible and terraform. They're not direct competitors, but I tend to prefer terraform over ansible when possible, as terraform gives me more "static" guarantees, which translates to more confidence when we apply our code.
Where this falls apart, the more verbose writing style hasn't been proven to convey more information or in a better way. That's an assumption still tossed around.
And typed or untyped, you’re only ever reasoning about the types in the context you’re working in, not the entire program.
A Python function can be called from within or from outside of your codebase with different callsites passing different types.
This can make it much harder to actually reason about the code, while making it seem easier to reason about. Most people would agree w/ your reasoning on a short piece of logic, which then at runtime spectacularly fails because the inputs don't adhere to the types you expected. In a statically typed language you would not even have gotten it to compile and while it might not feel like a bug is being prevented and actually feel tedious, every time your IDE (or compiler) tells you that the type on something is wrong, you've prevented a potential bug.
Let's say we compare Javascript and Typescript (as they're so close but one has static typing.
const myFunc = (param) => {
doSomethingWith(param?.property);
}
Easy, right? Well, does param actually have `property`? No idea. What type is `property`? Does the function `doSomethingWith` take that kind of input? No idea. Now I have to check that function, which might be coming from I don't know where, I might not even have an IDE that can reliably determine where `doSomethingWith` is coming from exactly. Even if I can navigate there now I have to check that piece of code and any other code it calls with `property`. Maybe `property` itself is an object and `doSomethingWith` assumes it has yet another property. This can easily go quite deep and I will not be able to easily reason about this at all. You can't tell me that someone can have all possible runtime combinations of this in his head for any reasonably sized program.Now let's take something that is almost equal but slightly longer to read and write, same thing in Typescript. I've had to define the types of these things somewhere once. Big deal.
const myFunc = (param: SomeType) => {
doSomethingWith(param.property);
}
Notice how this is really not much of a difference. Just a type declaration and it gives me a lot of safety. Let's assume SomeType defined `property` as non-null, so no `?` needed, I know my inputs have already been checked. `doSomethingWith` also defines its parameter type correctly and we know what `property` is or isn't. No need to know anything from the top of my head or spend time digging through code myself. The compiler knows that I am passing the correct type of object along and I won't get a runtime error (well, OK, it's Typescript, so let's also assume I'm not in a mixed TS/JS code base where I might very easily get `any` kind of object.Now syntax will be a little bit different, but I would argue the exact same thing in say Java or Kotlin is equivalently short and readable (yes even in Java!) while benefiting from even more type safety:
public myFunc(SomeType param) {
doSomethingWith(param.getProperty());
}
Didn't really hurt much, did it?But these are super simple example. It get can arbitrarily complex.
But Java, Kotlin, and Typescript types are very weak sauce. When types can do more, we can do more with them.
I associate verbosity with object-oriented programming, whether statically typed or not.
Here's something I can express with static typing that I can't express with dynamic typing: "this function returns a function which returns an integer for every input". There's no test you could write to verify this property. So I'm inclined to say that static typing is more expressive, since it gives me a way to express and verify properties like this.
Without compile-time types, you are not equipped to express serious compile-time work.
And yet, per TFA, it’s not; at a minimum it’s clearly not “self evident”.
Why do we developers value our personal experience above studies, while dunking on average citizens for doing the same?
Guess we’re just as human as the rest of humanity; subject to the same urge to trust our own beliefs over contrary evidence.
Cold shower attempted, but the plumbing was busted?
Such studies invariably wholly miss the point: when you have a language with powerful type support, error checking is the least valuable work you get out of them. Types do serious heavy lifting expressing semantics.
And at the end of the day you have to contend with being in a work environment where politics and personalities rule, not science (or engineering).
That said I do wish more devs would take an interest in the available quality literature. Unfortunately I'm far more likely at work to run into an Uncle Bob recommendation at work, than a recommendation of ACM's Digital Library.
However, refactoring code in C# is much easier than refactoring ruby because you can lean on the type system there. However writing new code in C# is often much harder to do in C# because of the constraints of the type system. So really, it ends up being a wash for me.
If you have not seen them, the reason is probably that the code was tested well enough before you looked for the bugs.
That's the point though. With dynamic typing you would only (hopefully) catch this with manually written tests. With static typing you get that feedback for free at build time.
How would that pass any code review, regardless of static or dynamic typing?
What kind of clown show of a programming org are you working at?
This is morally equivalent to "There's no point to having a safety on a gun, because the safety won't stop you from bashing someone in the face with the gun." If you really want to, you can throw exceptions or crash the process or call exit() or call system("shutdown -h now") anywhere in your codebase. That has nothing to do with a type system.
See, When I'm throwing together apps to clean up configurations, I am Pythonifying XML often. And when handling different return values, reshaping it into the useful components I need and trying to analyze data (and dealing with different return formats depending on number of results, aka a dict if there is one value, or a list(dict) if there are more) I have to constantly remember if I am going to be getting a list(dict(dict(dict(str)))) or just a dict(dict(string)), and so on. But that's me cobbling together scripts and not understanding the API by heart well enough.
This is false.
TypeScript, Swift, and Rust are commonly used and support non-nullable references.
> those languages, don't help you because you are dealing with real world data where inputs to your system can be null or not so you end up using some type system escape hatch anyway.
You don't need escape hatches to deal with "real world data" that may be missing some values. This blog post is my favorite detailed comparison of handling "real world data" in static vs dynamic languages: https://lexi-lambda.github.io/blog/2020/01/19/no-dynamic-typ...
Rust's serde_json docs on "Operating on untyped JSON values" are also a pretty good description of working with "real world data" where you want to examine an arbitrary document: https://docs.serde.rs/serde_json/#operating-on-untyped-json-...
The only requirement for working with "real world data" in a static non-nullable language is to choose whatever kind of behaviour you want when working with the data. Everything you can do with null references, you can do better with option types; there is nothing that null references uniquely permit.
1. Python with mypy has `strict_optional`. On by default.
2. C, being “portable assembler” is not really statically typed.
3. Java has had Optional for years, although it’s not the most pleasant to work with it does exist. And JVM languages like Kotlin go well beyond this.
4. C++ has `not_null`.
5. C# supports type-system enforced non-nullable types since 8.0.
You said:
> languages that disallow nulls, if you are one of the 10 programmers on earth working in one of those languages
I hope it’s clear that you are simply incorrect. There are plenty of tools to eliminate nullable references in modern mainstream languages.
Most importantly in C++ only pointers can be null. You can return value types that are not nullable.
There have been people writing at least two of those languages everywhere I've worked for a while. Most of my professional colleagues can write at least one of these comfortably. I'm extremely confident in being able to hire programmers for all of these. They're all in use at every major tech company.
If you really want to stick your head in the sand and cry about how nothing can be better until they're literally top of the charts, I can't stop you, but they're certainly not rare. There's good stuff out there. Lots of people are using it. You can too.
If you'd rather trade links to charts, I trust Stack Overflow's developer survey's methodology a lot more than TIOBE's. 30% of respondents said they've worked with TypeScript, and that jumps to 36% in the professional developer subset. Rust is 7%/6%. That's a hell of a lot more than 10 developers.
https://insights.stackoverflow.com/survey/2021#technology-mo...
They also got 15% of developers who aren't using TypeScript want to use it, and 14% for Rust:
https://insights.stackoverflow.com/survey/2021#most-loved-dr...
My country has about 15% black people about about 7% asian people. My country has about 4% LGBT people, and my city has about 15% LGBT. It would be really weird to hear someone say that black, asian, and LGBT people are not common, especially after knowing and working with plenty of them.
You clearly have little experience with such languages, then.
It makes you actually have to consider scenarios where a variable can be none or not and try to push the validation up closer to where it entered the system.
Also in the opposite direction, many dynamically typed language allows specifying types if you want to including python.
x still has static type, the compiler just infers it based on the assignment, the type information is still there. Agree that implicit/unsafe casting is still and issue in some languages though.
I suppose it's not even necessary to argue about experience fixing them or not, just the fact that those are runtime errors rather than compile-time (and so we presume not shipped) shows it reduces bugs doesn't it?
And Unlike typescript my code doesn't need to be transpiled at all since it is already vanilla JS.
I want a shirt with this on it.
It may not be very comprehensive static typing but it is static typing none-the-less.
These are two very different outcomes.
If the remaining bugs are unrelated to the class of bugs that were eliminated entirely, then the difficulty in finding them has little bearing on the outcome, since we’re now talking about an entirely different class of bugs.
- in JS, your code will run with the bug then do something catastrophic during runtime that you can then notice and trace to the core issue
- in Java it won't compile, so you fix it so it compiles and runs, then it'll hit you in like 2 hours of runtime with a NPE or something and you'll have no idea what caused it
Maybe Kotlin, Rust, and the like solve that sort of thing better but I've yet to be convinced.
It seems you're describing an orthogonal issue, and it's unclear why type checking is a Bad Thing or even related to the NPE at all.
Let's say I work on an assembly line, and must place physical parts into a machine that assembles a larger part. There are many ways this machine can break down - I could put the wrong parts in, leading to a complete failure, or some part of the machine could malfunction independently.
- We could implement part validation on the assembly machine to make sure it's impossible to insert the wrong parts. This eliminates failures related to incorrect part insertion.
- Unrelated to this, a drive belt starts to wear out and slips every so often, leading to a slight slowdown in a conveyor belt, which ultimately leads to a botched item.
The way I read your argument, you would say that part validation is bad, because it's easier to diagnose a meltdown when incorrect parts are inserted by the operator than it is to determine that the drive belt is slipping.
Except the drive belt slipping is not related to operator error, and would have happened whether part validation was happening or not.
This is hopefully obviously nonsensical - better to reduce the overall error rate by implementing part validation than to leave two avenues for error. Before part validation, the machine could fail because of operator error (common) or drive belt failure (uncommon). After part validation, only the uncommon error occurs.
This is better than no validation at all, even if drive belt failure is harder to identify than the machine screeching to a halt when the wrong parts are inserted.
What am I missing?
All the other claims from readability to understandability to refactoring to less bugs, all come with an “it depends” caveat. Sometimes the claims are true, sometimes they’re not. It’s also not possible to say “but in most cases claim X holds”.
The thing I’ve never understood yet in this debate is in my experience, the people who have argued about correctness have universally been below par at getting to the bottom of requirements. Which leads to “great, you correctly built the wrong thing. And you took forever to do it.” Which isn’t doing our profession any good in the eyes of other professions who depend on us.
Static typing reduces bugs because it aids your understanding.
Not sure though.
I remember working 2012 on a SaaS app, and I wasn't the only guy anymore doing frontend stuff with JS. I knew my objects, but my colleagues did not. How to you document object APIs? TypeScript really shines in large projects with lots of devs.
def add_item_to_cart(item)
vs void add_item_to_cart(IItem item)
They are equally easy to understand. The first is easier to read.I guess you now need to read through the implementation or docs. The first is much easier to read incorrectly.
The only citation I have is the tenuous grip I have on my own sanity - I could have more correctly talked about the incredible amount of mental overhead this has _for me_, but read the rest of the thread and you’ll see that this isn’t an uncommon experience. As I said, if you can work around this then you have my respect.
What you see in a statically typed language: IBlaha blah. What could blah be? An IBlaha. What could IBlaha be? Anything! The type has not gained you anything.
> The only citation I have is the tenuous grip I have on my own sanity
That's an argument from authority where you are the authority. It doesn't work on HN since we are all skilled developers. I've also been a software developer for decades and I can count on one hand the times static types has provided a tangible benefits.
Of course you could say the same thing about sane naming dynamic naming conventions for your declarations in a dynamically typed language - and you wouldn’t be wrong, but a compiler won’t help you in the case of human error. All I’m interested in is offloading as much complexity onto the tools at my disposal, so I can focus on what’s important.
On my citation … that was tongue in cheek and I thought it was obvious. I don’t have a citation, this is all my own experience. For the third time, if you can work your way around this you have my respect.
Dynamically typed languages are very popular so it seems that many developers can work their way around dynamic typing.
That definition is rarely as accessible as an explicit type though. For example take an API response or any third party library. Determining the data type isn't as quick as simply scanning a function for the object definition.
- Dynamically typed languages are very popular so it seems that many developers can work their way around dynamic typing.
As someone who has spent a fairly even mix of their career using typed/untyped languages, I think this is due to a few reasons:
- Lower initial learning curve.
- Lower barrier to entry.
Those are real benefits, but I would argue most projects quickly hit a point where they benefit from static analysis.
Having worked with 100s of devs at this point, I'm yet to meet one that after learning a typed language and using it for a sufficient period of time (more than a few months) wants to use an untyped language for anything outside of small scripts.
You're confusing Java-type extreme (and also mostly strawmanned) application of OOP with static typing.
Not every type in your program has to be AbstractFactoryProxyBeanInterface, and if you don't write code like that it's either obvious or some kind of extension interface for non-core code.
You've never worked with vaguely named variables? What you are suggesting is guessing the data type based off the name.
- What type of object? The type that can be added to a cart.
Okay sure, but what precisely is that?
- Nobody just throws random objects at a function.
I couldn't agree more - so the follow up question is what is the fastest way to get familiar with what type of input or output this function returns?
- They are familiar with the code in general and they know what to do.
For very small projects with very small teams after some onboarding time perhaps, but outside of this I would disagree.
Code changes over time, parts that you use to know intimately get changed subtley and erode knowledge away. Having types in place highlights these changes if your assumptions are incorrect.
It doesn't matter "what precisely" is the the thing that you are adding to the chart, and a static type system won't tell you that either. There could be be any of 1000 things that implement IItem. And probably half those things just throw exceptions for methods they aren't actually able to implement.
addItemsToCart(items)
Vs Type ItemCode: string;
Type ItemDetails = {...};
addItemsToCart(items:ItemCode[])
or, for a slightly different implementation: addItemsToCart(items:Record<ItemCode, ItemDetails>)
If you only use trivial examples, types seem silly. But in real examples they become more useful. In this case looking at the function signature give you immediate information about the implementation that is missing from the untyped version.EDIT: please excuse formatting, I'm on mobile and cannot get it to add spaces before the last code block
You need the same tests from a typed system in a non-typed. You _don't_ need all the tests from a non-typed system in a typed system.
Writing tests to enforce types just hand-rolls a type system, in my experience.
You do though, because invariably people violate the LSP and just "throw Unimplemented" in the methods required by the interface they can't figure out how to implement. In other words all system are duck typed in reality.
Not sure what typing system you're referring to, but it sounds very half-baked at best. I'm using Rust fwiw.
I cannot access any field or method that does not exist. Even dynamic traits are compile time enforced, but i think we can largely have this discussion around static dispatch.
You can do that with Python (sometimes) because many libraries have type hints today, so even if you don't use types yourself, the type checker can infer them in your code and help you out.
The same way Rust checks for object lifespans with the borrow checker, which is distinct from the compiler and type system.
The same way valgrind for C can check for use after free.
The same way errorprone can look for null checks in Java.
This is a well tested and proven technique. Static code analysis is a staple of the industry, when it comes to automated code analysis.
All of the examples you gave are from static languages, where the information is known at compile time (except for valgrind, which requires a runtime). The parent to my original post was claiming that you can have the same tooling for Ruby.
Also, you're wrong about Rust. Lifetimes are part of the type system.
void add_item_to_cart(auto item)
will still statically verify that item has the required syntax; C++ is very poor on this aspect on only doing the verification at instantiation time, languages with more sophisticate typing systems can infer the correct type from tome add_time_to_cart definition alone.And when we understand this, we can weigh it up with alternative tools for thinking and communicating!
Would this 10 line shell script be better in a statically typed language? Well maybe not because I can hold all of 10 lines in my head, there's nothing else to communicate.
Would this CRUD app using Django/Rails be better with static types? Well the framework has defined a structure that communicates properties of the code to me, I don't need types written down because I already know them.
Would this complex parsing process of untrusted data into a trusted and verified format benefit from static types? Yeah probably, testing will be tricky and code review for security is hard, types will help reason about the possible states of the system.
There are lots of alternatives to static types: documentation, testing, frameworks, design patterns, code review, pair programming, error messages, and so much more. I'm generally a fan of static types and find them very useful in a lot of development, but they are a tool in a big toolbox.
In a sense "type coverage" is analogous to test coverage.
function myFunction(user, security) { }
good luck finding out what user and security actually is. In static typing, it's all there.
That’s a dynamic typed language with comments