Web development is mostly the munging of data together and ensuring that you're glueing stuff together correctly. Static type systems, especially ML based ones would ensure that most of this is done correctly.
I don't suppose I could trouble you for a source for that number?
There's no free lunch. Some languages require explicit type definitions others don't.
Depends on your type system. It can prove the correctness of certain properties depending on your language.
Conversely, unit tests can pass but your code might not be correct.
Yet another under-specified commentary ;-)
If you care about correctness, use both. Codebases written in static languages still use tests.
I will never touch loosely typed stuff again. I want every keystroke checked for errors. I want autocomplete to select from possible members/fields/variables/functions/classes rather than doing constant global find/replace operations and having the customer be the beta tester
There's two popular ways to verify your programs. Types and tests. Types are EASIER then tests and types COVER more then tests.
The ideal thing is to use both, but why spend so much time on unit tests over types is beyond me.
Note that integration tests are a different beast. Why? Because you need to test things that aren't covered by either unit tests or types. In unit tests you mock out things that are external like IO, databases, and the like. In integration tests you need to test all of those things.
Integration tests are THE most important thing to use to verify your program because it's an actual test to see if your code works. But unit tests vs. types? Types win.
When I code. I never write a single unit test. Just types, and integration or end 2 end tests. It doesn't hurt to write a unit test and it's actually better. But the fact that I'm confident my code works without it shows the level of importance.
What do you think "type of values" means and encodes, and why do you think it's different from your handwritten tests?
I assume you do know that type checking is more than simply validating you're passing a string to a function that expects a string argument?
> Types also only catch certain classes of errors
Which classes of errors? Why do you think manually written unit tests are more complete?
But you now why I asked the question: people who make claims like "types make only guarantees about the type of values" usually don't understand what modern type-checking can do or what can be encoded in types. People making these claims often believe type-checking is limited to validating that you only pass strings to a function expecting a string argument, or some minor variation of that.
Thanks for your reply, this is why I asked: this kind of typechecking you describe only scratches the basics of what a robust type-checking system can do.
In practice, it can cover much more than "you returned a number from a function marked as boolean", and so it saves you from writing lots of unit tests, leaving you free to write only the few tests that make sense for your business logic.
Once you have a proper type system in place, you can do much more than:
> [...] a complicated set of business-logic functions that all return booleans [...]
You can encode more useful things in the type of the functions than just "it returns a boolean". Of course if every function you write only returns a boolean you won't get much from the type system! But that's circular reasoning...
Before mypy came along I was pretty disciplined at A) testing and B) ruthlessly clamping down on primitive obsession.
I use types a lot now but I didnt see enormous quality or productivity gains when I started using them. I suspect people who let compilers catch their bugs were always A) pretty lax testers and B) probably often let primitive obsession get the better of them.
There are only a few types of tests where I think that types actually serve as an appropriate substitute.
Imo this might be true if you're writing a lot of glue code. But I think unit tests are extremely valuable for testing pure functions that are pure logic/computation. For glue code/ensuring everything is hooked up correctly, types easily beat tests, but for pure logic tests (ideally property based testing if you can swing it) works great.
Usually the primary mistake is either using cucumber (which is awful) or surrounding the wrong interfaces with tests (usually at too low a level).
Done well they are like a mini superpower.
Once you have that and somebody has taken you around the block 3 or 4 times pairing with TDD on your app it usually starts to make more sense.
On my team after I inculcated the TDD habit properly, BDD came naturally. I didnt even encourage them to have conversations with the PO while screensharing the tests. They dont even know that's called BDD.
If your work is deeply exploratory and you don't even know what the final interface/API will look like, you shouldn't start with TDD. You should probably test as you go though, once you find some behavior you'd like to keep and pin down variations and edge cases you met during dev.
If/Once you know what the expected behavior is, you can encapsulate that knowledge in tests, that's TDD to me. Then, I can write more code that meets the need of the expected API.
It was amazing. I could refactor quickly after changing a dataclass, field name, function arguments, type, etc. I just ran mypy and it immediately told me everywhere I needed to update code to reference the new refactored type or data structure.
Then only after it was mypy clean, I ran the unit tests.
Pydantic is also helpful to enforce types on json.
I've also stopped passing around argparse namespaces. I immediately push the argparse namespace into a pydantic class (although a dataclass could also be used.)
One important consideration is the importance of the project, hobby projects are for enjoyment mostly, so there "authenticity" is really important. At the same time you shouldn't make decisions on how to develop aerospace software based on how "authentic" the development process feels.
EDIT: ... which is unfortunate, bc it's actually a decent read w/ interesting points about the relationship between (programming preferences) and (emotions / psychology).
I'll make the same remark I did last time this was posted here (it's been submitted 3 times in one week):
The author starts with this:
> I conclude with two implications of this idea: the necessity to accept other people’s preferences without judgment and the importance of finding an environment rewarding your style.
And follows it with these two things:
> Type theory maximalists should give up their aura of moral and intellectual superiority and accept that they need therapy just as badly as everyone else in the industry (if not more).
>> Haskell will still be used in 20 years, because there will always be people looking for a productive way to weaponize their autism.
So much for "without judgement".
I'm very happy to see the case being made for static typing. Dynamic types should be niche, and eventually they will be. Rails and Python only made them popular out of a reactionary minimalism thanks to types done wrong in C++ and Java ecosystems. New developers should read articles like this very early on. The more often they're written and posted, the sooner we can all move on from unnecessary type errors.
I’ve always been a strong static type guy since 1st year of university, and yes, it ties to perfectionism and even a sense of OCD: if I model the world around me, I can relax.
But here’s a take that I’ve been trying to articulate about Nix: it’s not about the static types. Nix is loosely typed, and if there is a functional language that doubles as coping strategy for recreating a quasi-perfect model of the world, it’s Nix.
So I believe any strong modelling combined with declarative syntax can be argued is a coping strategy for autism. It’s not strictly the types.
It also happens that software made this way is more reliable, breaks less.
Maybe the opposition to its use in businesses is that business needs often change rapidly, and tying yourself to any correct understanding of the world means you’re late to the party.
(I’ll then argue: there are companies that succeed at strong modelling, and companies that succeed at pivoting, and all companies need a bit of both at different times. That’s why huge companies with scaling problems and infinite runway pick Rust, and startups pick JavaScript.)
Previously author felt enlightened by sneering on stupid normies for not understanding beautiful, clean™, statically typed code.
What’s better way to show how better you’re now, that you’ve reached new nirvana, by sneering on ivory tower wizards with their arcane languages that are obviously a sign of autism (because whoever is different from, and possibly smarter than, you is obviously autistic)?
What does this say about DHH then? He's got decades of experience yet ripped TypeScript out of his codebase angrily, preferring the foggy mystery that is dynamic types. Is he a legitimate outlier? If not, then wouldn't you have to conclude he's just a really bad programmer?
There are certainly cases where you wouldn't get that negative feedback for your software, so you would never feel the need for those things. And maybe in that case you can move faster without them, but you should be careful taking those beliefs into other domains.
dynamic types: go look through code, tests, comments and project history to try figure out what this data is supposed to be
dynamic types are exhausting
Types are needed for sure, but don't make up for the fact we have to prep our own meals from time to time, even the best recipes don't cover all variations.
With types, the scope of the variation is clearly stated, and if you want to expand the variation, it should be clear how to do so, depending on the flavor (e.g. union types, sum types, generics, sub types).
I think if we start to lean on types for our all recipes, we may forget how to prepare them without instruction.
If the modelling is trivial, the ROI is much lower (although the devx benefits still make it worth it to me)
dynamic types: method parameter name gives the type and comment gives the method return type — conventions show what data is supposed to be
static types are disruptive and exhausting :-)
I see this play out in language preferences every day, INTJ (haskell, rust) and ENTJ (Golang, Java, Python).
Giving a list of the "best 3" programming languages can probably map you very accurately to a Meyers-Briggs personality.
https://news.northwestern.edu/stories/2018/september/are-you...
It's very simple: sometimes lower development effort matters more than correctness. Not all software has to run in a hostile production environment to be useful.
Just saying "dynamic typing is easier" doesn't do it for me without further qualification since that statement doesn't conform to my own experience.
If you declare a function parameter as `foo: int = None`... that is just an incorrect declaration. Of course a variable annotated as `int` can take a `None` value, but that is because any variable can take any type in Python. Within the Python type (annotation) system it is simply the case that an `int` and an `int | None` are two different things, as they are in other languages (eg Rust's `T` vs `Option<T>` types).
Mypy used to support the "implicit optional" feature you describe but now you must make nullable arguments explicitly optional. This is in line with Python's "explicit is better than implicit" design philosophy. In any case, how long does it take you to just type `foo: int | None = None`? Or you could re-enable the old behavior to allow implicit optionals with `--implicit-optional` or the corresponding config file option. It seems like you just need to configure mypy to match your preferences rather than fighting with its defaults.
To return to the broader point, I'm unsure what an "irrelevant type warning" is, but I suspect that has something to do with my lack of appreciation for dynamic typing. Can you give an example that isn't just a complaint about typing an extra 6 characters or about mypy being misconfigured for your preferences?
No, it is correct. The value None is not with the domain of type int, a parameter that can take the value None does not in fact have type int.
> This yields no value because when you say `= None` you are saying that this is an optional argument.
When you provide any default value, you are making the argument optional, but that's an orthogonal concern to the Optional[T] type, which doesn't mean “optional argument", it is simply a more convenient way of expressing Union[T, None], though with the modern type syntax, T | U is generally more convenient than either Union[T, U] or Optional[T] for U=None.
Because you can run your program to see what it does without having to appease the type checker first.
There is nothing wrong with presenting type hints or type errors as warnings. The problems arise when the compiler just flat-out refuses to run your code until you have finished handling every possible branch path.
fn main() {
let x: i32 = if true {1} else {"3"};
println!("{}", x);
}
This will not compile even though if it were allowed to execute it would, correctly, assign an integer to x. Python will happily interpret its equivalent: x = 1 if True else "3"
print(x)
Even giving the if-expression an explicit `true` constant for the condition, Rust won't accept that as a valid program even though we can prove that the result of the expression is always 1."Can not" and "will not" are kind of the same thing in this context. It's not like compilers have free will and just decide to give you a hard time. It's the language design that makes it (im)possible to run code that won't type-check.
This means you can only really rely on the parts of your program which are type correct, so...
Please excuse my pedantry: That would seem to lead to the question …
2. Dynamic typing lets you change what shape your data is without having to change type annotations everywhere.
Problem is, I think both of these are deeply flawed:
1. If you're writing something nontrivial, you probably have several layers of nested function calls, some of which may be calls to libraries that are not yours. If you're saying "anything I can do these operations on, I accept", it becomes very difficult to say what the full extent of "these operations" are. Thus it becomes hard to say whether the caller is passing in valid input. You can "just try it", but that becomes hard if you care about it working in all cases.
2. Refactoring IDEs are a thing these days. You want to change the type signature? Press the button. Even better, it will tell you everything you broke by making the change - everywhere where you're doing something that the new type can't do. Without types, sure, you can just change it without pressing the button. Now try to find all the places that you broke.
It may be possible to construct a better steelman than I have done. For myself, even trying to steelman the position, I find it incredibly unconvincing.
https://ics.uci.edu/~lopes/teaching/inf212W12/readings/rdl04...
Do failing tests show what we broke?
Any!
Then maybe the immediate feedback of consistent types conveys a false impression.
And with #2, you can get that with static typing too... Let's say a method accepts an instance of an object `Foobar`. I can change the definition of `Foobar` ("change what shape [my] data is") without having to change type annotations everywhere.
I agree with you, I guess, that I find the steel man position unconvincing.
"Less than 35 bugs were found in the 17,100 changes."
Your type system is sound or complete (cannot be both, if it's a Turing-complete language). If it's dynamically typed, then it's probably complete but not sound (many, but not all, dynamically typed language implementations have some basic static type checking). If it's statically typed, it can be either sound or complete. The difference between sound and complete is: Sound type systems will reject valid programs that they cannot prove are valid; Complete type systems will reject only those invalid programs they can prove are invalid.
In practice, the choice is more one of default these days:
Do you default to static typing and allow some dynamic (runtime) type checking? C++, C#, Java, etc.
Do you default to dynamic typing and allow some static (compile-ish time) type checking? Python, JS, Common Lisp, etc.
This lets both sides have what they really want. They want to be able to express all valid programs (a complete type system) but they want to reject all invalid programs (a sound type system). They can't have it. So you end up choosing a direction to approach it from. Either start conservatively and relax constraints, or start liberally and add constraints.
If you accept that, then the case for dynamic typing is that it's a choice to move from the too permissive extreme towards a stricter position. For me this works better (in general, though I also happily use statically typed languages like Ada) because I find it easier to add constraints to a relaxed system than to remove constraints from a restrictive system.
> I met many people whose programming approach was my polar opposite.... I don’t have any issues with their way, as long as we don’t work on the same system
Being obsessed with what your code says about you is a concern that competes with and inevitably detracts from other desirable outcomes. This is true even when working by yourself, but it is especially true and especially obvious when working with other people.
PaulKeeble•2d ago
The idea that our preferences in technology are driven by our path of learning seems relatively self evident, those that started with C/C++ verses Smalltalk verses Java verses Python as their first language likely have a very different perspective on the various trade offs of these languages. Given we don't usually learn every language and certainly not to a deep extent few have deep experiences in all the possibilities to grok the differences in practice on similar projects.
I really resonate with this, in the right environment I seem vastly more productive. I have a significant preference for low levels of control and oversight and in those environments I can take actions quickly with less discussion and often get to better answers quicker, but I hate not being able to do that and it impacts my performance disproportionately. Its one of those preferences for culture that seems vital to how I want to work and impacts productivity a great deal. Asking permission or high rigour for potentially "extra" work just grinds my gears!AnimalMuppet•3h ago
This explains, for example, why one person sees functional programming as a massive revelation, where suddenly everything "just fits", and assumes that anyone who doesn't have the same reaction hasn't "gotten it" yet. And others, despite diligent trying, never have that reaction.