11 ms·
I dunno, I find static types to be a core design tool that are highly beneficial from the earliest stage of a project. They help you probe a problem space as yo
by bjz_ 10y ago
I dunno, I find static types to be a core design tool that are highly beneficial from the earliest stage of a project. They help you probe a problem space as you develop, and offload a great deal of cognitive burden to your tools. I actually find myself working a great deal slower on new dynamically typed projects because I have to juggle a great deal more context in my head, and am encumbered by larger test suites that are more difficult to change.
Note that I am referring to type systems with succinct syntax, type inference, structural typing and ADTs.
- jstimpfle 10y agoIf you can't keep the "types" of your design in your head, your design might be broken. I have a real bad short-term memory and can probably juggle less things in my head than most programmers. Yet with good taste for design / decomposition that's not much of a problem. Most of my code now is structured as tables, which greatly simplifies many transformations. There is no pressing need for typing tables, and anyway most of these type systems really suck at "tables transformation algebra". Here is a little list of drawbacks of static type systems with features like you describe (parametric types, ADTs...): Type dependency hell (banana, monkey, jungle, etc), having to fight/convince the type inferencer to accept any but the simplest things, having to type many things twice. Looking at Haskell, "almost everything is inferred" is a lie in my experience. You are encouraged to write code using higher-order and highly parametric functions. If you do, the error messages you'll get if you don't annotate are plain sick. Another problem is that the type system provides no convenient escape hatches, while it's not powerful enough for typical programs. Many "successful" example designs I've seen are either _really_ simplistic (to the point where it's obvious that the better design wasn't chosen simply because the type system makes it really inconvenient), or have some _really_ terrible internals hacking in it (which might be the largest part, be impossible to understand by most developers, and break with the next compiler version). Looking at C++, a practical example is vector. It's a useful tool (automatic memory management), and very likely to be all over in your function signatures. This is very bad in terms of modularity / reusability, because your functions are not defined using reasonably general types (pointers to arrays). Many of these very real pains just aren't there with Python code, which has better modularity at the cost of being a little sloppier. These days I look at static types mostly as tools for generating fast machine code (I doubt that much more than C types is needed here) and as tools for enabling nice syntax (OOP protocols like in Python can do that too, but they are very dirty). These things however are only needed at the very optimized parts of your average project. So I think a good way in many cases is to go the Python route, and check types dynamically at strategic points to assert value integrity and enable transition to highly optimized (with static types) routines.
- vilhelm_s 10y agoI'd be interested to hear more about Haskell projects where the type system causes bad designs. Maybe you could write a blog post about it or something?
- jstimpfle 10y agoI will think about it, but would probably not be comfortable with it. For now a few words that I hope I won't regret later (apart from the things I mentioned): The bulk of invariants / assertions in many real-life programs can't be expressed with mainstream type systems at all. They are too specific or only come to life dynamically. Things like: This array of integers is sorted ascendingly, and that index is a valid index into this other array which points to a number between 48 and 58. Or this table's column over there contains only values that are also present in that other table that just happens to be present now (but must be expunged from working memory after the next subtask). Checking if an integer fits in 8 bits and then converting to a byte is no safer in Haskell than in any other language. So much to put the usefulness of mainstream types for static correctness in relation. Now much of the tinkering that is going on in Haskell projects seems to be shoehorning yet one more real-world invariant in another crazy type. Maybe the next brilliant idea works for one more toy case in the end. Great, but the cost/benefit ratio is just bad. I think the most important thing in software architecture is modularity, because the most pressing need in software development is the need for change. If my project is very modular, it's sufficient to have clear entry points to each module with very simple data types in the signature. The internals can be a dirty hack and rewritten later, or the module thrown away when the idea turns out bad. Interfaces on the other hand can't be changed as easily since they affect much more code. So a good approach is making interfaces that accept tables, augmented by a textual description of invariants so the user easily "gets" the meaning of the data and can reshape/preprocess the data as needed. This enables isolated rewrites of modules. It also enables creative use of data in unforeseen ways. Clever types on the other hand have a very strong influence not only on the design of module interfaces but also on the dependent modules' implementations, because 1) it's more cumbersome to reshape complex structures in a way suitable for clean implementation, and 2) because type systems can't usually transfer invariants to reshaped representations (and after all the goal was to let the compiler handle the invariants, so reshaping is a no-go). In short, complex types _kill_ modularity because they have far too much influence on data layout. For real world programs, what Haskell is great at is statically finding trivial interface usage errors like, don't call this with a NULL pointer, or this parameter must be a functor instance (because the actual data might be too ugly to admit). Turns out that that's all nice and well, but the machinery is a huge cognitive and economic overhead, while running some python client code of a sane API once typically brings such usage errors to light just as well (profane things. TypeError raised. Seems it tried to iterate over an int, so you must have gotten the argument order wrong etc).