8 ms·
Types are a basic tool of software design (2018)
- karmakaze 2y ago(2018) btw. 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: > [...] 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. [...] 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.] But I couldn't even agree with the first one: > 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. [...] 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.
- nightski 2y agoI am skeptical. You don't even know if a relevant piece of data is say a number or text before you start writing out code? You don't think at all about how you will group data together? You don't think about the operations that will be performed on that data and how they will transform it? What do you think about when it comes to design?
- acchow 2y agoLocation of data, policies, cross-cutting concerns, latency/performance requirements, etc. “Much later” is probably an exaggeration. You get to types “soon after” the initial design.
- qayxc 2y agoTypes are a useful design tool to explore the problem domain first, while the rest are details that emerge from that. Sure, if one starts with architecture, you're right. The software architect needs to consider these first. The programmer on the other hand ideally expects those as inputs. When tasked with wearing on multiple hats (architect, programmer, user), I agree with your point. From a programmer's point of view, however, (abstract) types are IMHO indeed a much more useful tool as they cover many of the other concerns (policies, performance, etc.), too.
- acchow 2y agoIn my experience, a software engineer wears all the hats nowadays. Even crossing into product design and user experience.
- nightski 2y agoIt's interesting that you think about those things without even understanding how you are going to solve the user's problem first. Aka functional requirements.
- packetlost 2y agoI find this really hard to believe to be honest. Even dealing with things as rudimentary as a file handle are really thinking about a type of some abstract thing, even if you don't really conceptualize it as such. Types are the way to put a name on that abstract classification, so in dynamically typed languages even the name of a variable or function assigns a classification to the data it holds/returns, even if it's only really enforced by the programmer. There are basically no untyped programming languages for a reason, types are a fundamental concretization. In fact, I would argue there is no such thing as untyped programming, there's just different degrees to which the programmer is the enforcer of correctness.
- oh_my_goodness 2y agoSure, I think the author overstates his/her case and "finds the One Ring". But it's not a dumb article. "Design is everything that's leftover after we remove all the function bodies" seems like a useful perspective.
- slowmovintarget 2y agoExcept it's not. The word "design" originated from the notion of marking out or pointing out. To do it well is to take the problem apart into small challenges that can be solved, with a plan on how to make the pieces hang together again. Design often includes algorithms, which don't devolve merely to types. So this statement only makes tangential sense if all you're ever building is CRUD applications.
- boscillator 2y agoI pretty much always start with types/signatures when sketching out a design. I guess if there is a complicated algorithm, I might start with pseudo code for it, but even then, I probably want to start with what it's inputs are and what it's outputs are.
- oh_my_goodness 2y agoI'm glad we finally found the 1 basic tool of software design. We can for sure stop thinking and arguing about that.
- seanwilson 2y agoI love this before/after example of TypeScript being removed from a library as a clear illustration that only occasional simple type annotations are needed: https://github.com/hotwired/turbo/pull/971/files https://github.com/hotwired/turbo/pull/971/files 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. 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. 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.
- benatkin 2y agoThat PR is great. Not only do they get rid of TypeScript, they get rid of prettier and some gratuitous trailing commas. https://github.com/hotwired/turbo/pull/971/files#diff-865b8f8ec486a7b449d8d47d9666c452d234fab19927dc49af9439d5d5b00744L172 https://github.com/hotwired/turbo/pull/971/files#diff-865b8f... I’m not joking. I prefer the after. Edit: thanks. Yes, commas, not comments.
- hyperhello 2y agoGrammar is the basic tool of writing. Atoms are the basic tool of physics. Etc. 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. 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.
- bromuro 2y agoI’ve lost you at the template part: do you mean _models_?
- xvinci 2y ago> 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. 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. But then Haskell can hardly have been the first mainstream? But maybe someone with more ancient knowledge could share some insights - thx.
- PittleyDunkin 2y agoI wouldn't characterize a forward declaration of a function as a distinct definition of its types—any types in the forward declaration must match those in the implementation; there's no way to implicitly refer to the types used in the forward declaration to ensure a single place of typing.
- cess11 2y agoArguably COBOL has it with its data division, as does Ada.
- codr7 2y agoCommon Lisp supports something along those lines, mostly for optimization purposes.
- bionhoward 2y agoPython does have something like interfaces and it’s called Protocols https://docs.python.org/3/library/typing.html#typing.Protocol https://docs.python.org/3/library/typing.html#typing.Protoco... 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
- cess11 2y agoCould 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.
- algorithmsRcool 2y agoI go a little back and forth on this with my experience in F#, which relies heavily on inferred types. You can write a lot 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. 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. 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.
- scalaisneat 2y agoI agree with this sentiment but view it inherently as a feature of languages like f# and Haskell - That small ripple at compile times makes trusting refractors easier. After having experienced this, languages like python become really challenging to grok without heavy unit testing.
- kibwen 2y agoThere's also a middle ground which is to support function-local type inference, but not inter-function type inference, which guarantees that such an "anchor" is never all that far away (especially never in another file). This is the approach Rust uses.
- MoreMoore 2y agoI think what a lot of people miss is that this ripple effect always exists. Whether you have strong types or weak types, it's hard and often impossible to avoid. All strong types do is make it explicit at compile time (and with static analysis), instead of leaving it to be discovered in tests or at runtime. This is why I'll use Python for small one-off scripts but anything that will be in regular use I prefer to do in Java. Even though I have many more years of experience with Python.
- taeric 2y agoThis 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?
- motorest 2y ago> 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. Sometimes I wonder if this type of article is just a rehash of object-oriented programming articles from the 90s where the keyword "class" is search-replace'd by "type".
- Waterluvian 2y agoI'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 types exist no matter how you feel about them. 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. 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. Rust also clicked for me when I understood that it's really just more 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. In this sense, the issue is really about making decisions on just how much "trust me on this" is appropriate.
- fellowniusmonk 2y agoMereological Nihilism demonstrates that most types are an illusion. Space, Time, Minds. Those exist. Probably :)
- boscillator 2y agoA 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.
- motorest 2y ago> Completely understanding the types in your program forces you to think carefully about the domain. Isn't it the other way around? Types derived from analysing the problem domain? That's the basic premise of a bunch of software design techniques.
- boscillator 2y agoYes, understanding comes first and types come second. But being able to comfortably create types for your domain is a good sign you have sufficiently analyzed it.
- leptons 2y agoI enjoy coding in assembly language, where types are not really a thing. Yeah, it really forces you to think carefully about the domain. I'm not talking about a little bit of in-line assembly, I've written entire UI, device drivers, and everything else in assembly - many thousands of lines of code. Maybe this is why I don't feel like Typescript has solved anything much for me when I work with front-end code.
- game_the0ry 2y agoSince 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. There is a quote I saw somewhere, forget where, but it was basically: define your data structure and the algorithms will reveal themselves.
- motorest 2y ago> 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. That is aligned with the basic premise of Domain-Driven design: define a model of the problem domain, and everything else just falls in place. I wonder why these articles reinvent the wheel with functional programming hand-waving when DDD has been screaming this for years.
- PittleyDunkin 2y ago> I wonder why these articles reinvent the wheel with functional programming hand-waving when DDD has been screaming this for years. These are two different levels of abstraction. If you're still enraptured by the fundamental mechanisms of computation (i.e. functional programming) you're not going to see the value in trying to figure out the best way to build via composing mechanisms of computation, whether it's functional, imperative, procedural, logic-based, lisp-based, object-oriented, message-passing, whatever. There's a reason why there aren't many DDD frameworks out there—it doesn't really benefit from tying to specific technologies/languages/ecosystems in the same way that the culture of "build shit as fast as possible" attitude dovetails well with the goals of Rails. I don't see why this is surprising; people re-discover old lessons daily, and petty details will always be conflated with broader themes and patterns—it's just how the human brain works. The discussion is what keeps us thinking about those old lessons. This is just a person's blog, not a journal trying to represent an industry.
- motorest 2y ago> There's a reason why there aren't many DDD frameworks out there—it doesn't really benefit from tying to specific technologies/languages/ecosystems in the same way that the culture of "build shit as fast as possible" attitude dovetails well with the goals of Rails. I think your comment fits the "not even wrong" category. It goes beyond nonsense. There is no DDD framework because the very idea of a DDD framework makes absolutely no sense at all. DDD is an approach to software design, not something you build and reuse. DDD is then used to specify what types to define to put together a model of the domain. Again, there is absolutely no point to have a framework to define these. Lastly, DDD defines interfaces for domain services, and imposed absolutely no restrictions on how to define those. This has nothing to do with frameworks. Development speed is also irrelevant and unrealistic. Not even wrong.
- deleted 2y ago[deleted]
- arialdomartini 2y agoI’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.
- arialdomartini 2y agoPS: Type Driven Development with Idris is mentioned in the final notes. OP, I could not recommend it more!
- k3vinw 2y agoIs it too simplistic or abstract to think of types as merely contracts?
- TypingOutBugs 2y agoI think that works!
- rubenvanwyk 2y agoInteresting opinion I've not heard before, or to add to your point then basically types are merely just "interfaces".
- tbct 2y agoSurely, however from a software architecture perspective types as contracts, assuming pure functions and maximally restrictive types, is a good model for reasoning about a system. A function such as: addIfEven: Int -> Int -> Maybe Int even without knowledge of the implementation lets us know our caller has 'entered into a contract' where addIfEven will reduce two integers depending on some conditions. This contract then becomes two-wayed: If I modify the implementation of the function there's a good chance I'll be forced to modify the signature, thereby letting me know statically that I've broken the contract with the callsite. I'd recommend checking out https://lexi-lambda.github.io/blog/2020/08/13/types-as-axioms-or-playing-god-with-static-types/ https://lexi-lambda.github.io/blog/2020/08/13/types-as-axiom... for a far better write-up than this comment on why type-driven development is like unlocking a cheat-code for development.
- renox 2y ago> One of Haskell’s most basic innovations [cut] was the ability to write the type of a function separately from writing the function body. I don't get it, what's the difference between this and C's function declaration?
- xigoi 2y agoIn C, you still need to write the types in the definition.
- andsoitis 2y agoI really like the balance struck by Ada's type system, especially Application-Defined types to model the problem domain - https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Developer/chapters/05_Type_System.html#application-defined-types https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Devel... Other effective features of Ada's type system: - type ranges (https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Developer/chapters/05_Type_System.html#type-ranges https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Devel...) - generalized type contracts using subtype predicates (https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Developer/chapters/05_Type_System.html#generalized-type-contracts-subtype-predicates https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Devel...) - attributes (https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Developer/chapters/05_Type_System.html#attributes https://learn.adacore.com/courses/Ada_For_The_CPP_Java_Devel...)
- ddyevf635372 2y agoThere 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. It is quite difficult to work on large Ruby or Python projects; interfaces are not determined, and figuring out what's happening is painful. Still, so many devs love it, and they wanna keep working with Ruby and Python in this way. I kind of feel that experienced devs move on, and give up projects without types.
- bowsamic 2y agoThese days most Python devs I know use types
- neverartful 2y agoThe funny thing is that even for devs who don't want to declare their types will still have think about them to some extent. Even if it's correcting run-time errors indicating that some function or property doesn't exist for type 'X'. Plus, they get to guess at the types that are expected for various function calls in their own projects. Oh sure, you can add comments that document it right above/below the function, but do you remember to keep those up to date while you're in the middle of refactoring?
- status_quo69 2y ago[dead]
- vladimir-vg 2y agoAuthor is dead wrong, just enamored with functions. Functions can be seen as basic tools of software design only if you completely leave out IO and asynchronous nature of the communication. 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. Actors that asynchronously pass each other messages are.
- lincpa 2y ago[dead]
- kgeist 2y agoOur 1 mln line legacy project was written without type hints, often preferring arbitrary arrays with keys, and so: 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 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 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 So in the end, in my experience: 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) 2) you waste more time and suffer more in the rest of the situations So we started adding type hints all over the place, and banned arbitrary arrays with "magic" keys in public APIs. 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?
- shae 2y agoDenotational Semantics is the basic tool of software design. Meaning, model the whole thing in math, then you know whether the implementation matches the design.