I go a little back and forth on this with my experience in F#, which relies heavily on inferred types. You can write <i>a lot</i> of F# before you need to add type annotations, but eventually, things become a spiderweb. The key issue is when you make a 'small' change to some method/value, the changes ripple through the program creating confusing errors sometimes where the compiler is trying to knit things together.<p>After a while, I found myself adding back types in a decent number of places to "anchor" the type inference, indicating that a certain type/signature is fixed and a change should be carefully considered.<p>I still don't know how folks deal with these kinds of changes in weakly typed languages without always allowing bugs to pour into their code over time. But I do love the "move fast" and low boiler-plate aspects of "typeless" coding.
I love this before/after example of TypeScript being removed from a library as a clear illustration that only occasional simple type annotations are needed:
<a href="https://github.com/hotwired/turbo/pull/971/files">https://github.com/hotwired/turbo/pull/971/files</a><p>Looks like so much useful documentation for understanding the code, assistance for refactoring, and static checking (which are like always running, fast and exhaustive unit tests for free) for a modest amount of type annotations that each person on the team would need to recreate in their heads anyway. Static type checking is so useful for catching runtime errors to do with optional values and index/key not found error too, that always create a load of edge cases.<p>Editing JavaScript with no types is scary, especially when you didn't write it. You're mostly guessing what the types must be and hoping your running code exercises enough edge cases to catch any problems. "You should write more tests" as an alternative rings hollow for me because (for the things they will check for you) types are more concise, exhaustively check all values, are ran continuously, are fast, aid with automatic refactoring, document the code in the same file its in, and will locate exact snippets that are causing errors. More often than not, test mostly check happy paths, aren't even close to as exhaustive, and nobody is writing tests that check every combination of function parameters being null vs non-null, string vs number etc. (which would be a huge amount of noise in your test suite that would slow down refactoring too). Types are a compliment to tests as well, not an alternative.<p>I can't relate to arguments about types getting in the way (as long as you avoid trying to get too clever like with complex generics and TypeScript conditional types). Usually when this happens, it's because it's hard to reason the runtime edge cases in your own head and there's probably a simpler way to write it. I'd love a good non-niche example to the contrary. Worst case in TypeScript, you can use `any` as an escape hatch anyway so as long as you can add types to most of your program it's not a strong reason against for me.
I really like the balance struck by Ada's type system, especially Application-Defined types to model the problem domain - <a href="https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Developer/chapters/05_Type_System.html#application-defined-types" rel="nofollow">https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Devel...</a><p>Other effective features of Ada's type system:<p>- type ranges (<a href="https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Developer/chapters/05_Type_System.html#type-ranges" rel="nofollow">https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Devel...</a>)<p>- generalized type contracts using subtype predicates (<a href="https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Developer/chapters/05_Type_System.html#generalized-type-contracts-subtype-predicates" rel="nofollow">https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Devel...</a>)<p>- attributes (<a href="https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Developer/chapters/05_Type_System.html#attributes" rel="nofollow">https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Devel...</a>)
Since adopting typscript, the way I usual approach my development is that I clearly define data types on ui and then the implementation details tend to reveal themselves.<p>There is a quote I saw somewhere, forget where, but it was basically: define your data structure and the algorithms will reveal themselves.
I'm not sure if this is obvious to the average developer and I'm late to the show, but a few years back things clicked for me when I grokked that <i>types exist no matter how you feel about them.</i><p>You still have to reason about the types no matter what. But writing it down empowers you to offload a lot of mental load to the computer, asking it to check your work, give you feedback as you go. It also communicates intent to other humans who read it.<p>One reason I like TypeScript is that there remains a, "just trust me" escape hatch when I'm testing/hacking something and don't want to "show my work" by writing out some complex type. (Funny how "show your work" is something math teachers have to fight many students to do. This feels like the same thing). But that complex type always existed. You're just saying "trust me on this." Writing it down doesn't conjure the type into existence.<p>Rust also clicked for me when I understood that it's really just <i>more</i> of the same thing: memory management, lifetimes, etc. exist no matter what. But it offers a way to show your work, formalizing intent, so that the checker/compiler can worry about the boring problems for you.<p>In this sense, the issue is really about making decisions on just how much "trust me on this" is appropriate.
Python does have something like interfaces and it’s called Protocols <a href="https://docs.python.org/3/library/typing.html#typing.Protocol" rel="nofollow">https://docs.python.org/3/library/typing.html#typing.Protoco...</a><p>Other classes can count as implementors of protocols without needing to subclass them, and static type hints will notify you if you make a function which accepts a protocol as an argument and pass something which doesn’t implement it
There are still so many devs who don't want to deal with types, and they love Ruby and Python, as well as JavaScript without types.<p>It is quite difficult to work on large Ruby or Python projects; interfaces are not determined, and figuring out what's happening is painful.<p>Still, so many devs love it, and they wanna keep working with Ruby and Python in this way.<p>I kind of feel that experienced devs move on, and give up projects without types.
This is stretching the definition of "tool." I would go far in agreeing that types are fundamental to the vocabulary of software design. But calling them a basic tool feels off. Is akin to saying rooms or units are a basic tool of building design. I can see arguments for it, but tools typically refer to the various things that actively do something. Hammers and such. Maybe a level bar?
(2018) btw.<p><i>I saw this much earlier in the day and dismissed it. I just now read more of it. I was first off-put by the title, but then it goes on to say:</i><p>> [...] design is about everything that’s leftover after we remove all the function bodies. It worth taking a moment to think about what that looks like. Here are some of my own immediate thoughts: 1. Everything that’s left is just types. [...]<p><i>Which reaffirms my suspicion that "Types!" are this writer's hammer for all of programming's nails, or possibly a click-bait writing device. There's probably a better article in here trying to get out as the end notes mention. [Maybe read them first.]</i><p><i>But I couldn't even agree with the first one:</i><p>> I meant for the main point of today’s essay to be universal: hence my emphasis that types are central to design even in dynamically typed languages. [...]<p><i>I can't say I've ever considered types/signatures when sketching out a design. That comes much later in prototyping and actual implementation--I suppose if I used Haskell that might be different.</i>
I’m reading Type Driven Development with Idris and this post really resonates with what I’m learning. Especially the idea of writing down the function’s type to be guided how to implement it.
Our 1 mln line legacy project was written without type hints, often preferring arbitrary arrays with keys, and so:<p>1) despite having tests, almost every day there's some bug in production because code expects data X but it receives data Y - this class of bugs is entirely non-existent in strict/static languages<p>2) to have some degree of control, they added a linter which infers and validates types, but it doesn't catch everything (see #1) and this step takes like 5 minutes - defeating the whole idea of "dynamic languages don't need compilation so you're faster with them", because modern static compiled languages can compile incrementally in a few seconds and you already have 100% type information without waiting for 5 minutes each time<p>3) refactoring code is very hard, because without proper typing, it's hard to find all instances of usage of data X or property X without manually researching tons of code (data flow); in languages with proper typing, IDE can find it all immediately, but ours fails to catch a lot of stuff<p>So in the end, in my experience:<p>1) lack of types allows for quicker debugging/hotfixing if you don't care about data/logic validity (because you don't run tests or linters anyway)<p>2) you waste more time and suffer more in the rest of the situations<p>So we started adding type hints all over the place, and banned arbitrary arrays with "magic" keys in public APIs.<p>I sincerely don't understand people who push against using types, it's like they never worked on large projects in teams, or never maintained a project for more than a few months. Or maybe they have some secret sauce I'm not aware of?
Grammar is the basic tool of writing. Atoms are the basic tool of physics. Etc.<p>What I think this post is reaching for is not types, and it’s beyond even abstract data structures. It's really templates, which is a general concept that gets used in every design scenario. Numbers are templates, integers are templates, bits are templates. We can describe how the idea works to each other and partly to the computer but it’s up to the programmer to animate them.<p>Once you understand the concept you see it everywhere. Super Mario is full of templates: you jump on an enemy’s head, or sometimes it doesn’t work but gives you a clue to do something else. Templates are nodes of understanding.
> One of Haskell’s most basic innovations (well, I’d guess it probably wasn’t first, but compared to other relatively mainstream languages) was the ability to write the type of a function separately from writing the function body.<p>Anyone familiar with the initial versions of C / C++? Both languages have offerd the ability to define your function signatures in header files for as long as I have known them, so if you wished to separate from the actual function body you should have been able to do that.<p>But then Haskell can hardly have been the first mainstream? But maybe someone with more ancient knowledge could share some insights - thx.
Could generalise it to 'data is the basic tool of software design', then the article would have to muddle shape, structure and type into a single type concept.
A lot of people are complaining that this seems too "obvious" in one way or another, but I think it can be useful to write down and be explicit about such an important underlying fact of programming. Completely understanding the types in your program forces you to think carefully about the domain.
> One of Haskell’s most basic innovations [cut] was the ability to write the type of a function separately from writing the function body.<p>I don't get it, what's the difference between this and C's function declaration?
Denotational Semantics is the basic tool of software design.<p>Meaning, model the whole thing in math, then you know whether the implementation matches the design.
Author is dead wrong, just enamored with functions.<p>Functions can be seen as basic tools of software design only if you completely leave out IO and asynchronous nature of the communication.<p>That’s why Haskell is so beautiful and understandable when you work with pure data, and suddenly becomes so cumbersome and cryptic when you need to work with IO and async communication. Because functions ARE NOT basic tools of software design.<p>Actors that asynchronously pass each other messages are.