8 ms·
I'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?
by vilhelm_s 10y ago
I'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).
- coldtea 10y ago>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. Which is beside the point, since partial coverage of invariants is still very useful. For when it's not, there's Idra, Coq and such.
- jstimpfle 10y agoHaving a small subset of invariants checked by the type system for sure is beneficial, however I was saying that the cost/benefit ratio is just not good enough (IMO of course: as a non Haskell expert, who is not a genius and has only spent 2-3 years learning it...)
- dllthomas 10y ago> 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 These are difficult to encode in a way that lets the compiler check the code that sorts or calculates an index. They are trivial to encode in a way that simply tags the values. I still need to be concerned with making sure my sorting is correct, but that's a very isolated concern. I still find it very useful to know whether I've called a function on the array that depends on it being sorted, and that doesn't require any heavy lifting at all.
- jstimpfle 10y agoI haven't had good experiences with tagging. There are just too many "tags" (often one per line or group of lines). They also have disadvantages in that they separate data which is only different by some aspects, but not most others. (For a simple example, the printing function is not interested in a "safe HTML" Tag. The effort needed for constant tagging and untagging (or using "Stringy" classes, or the cognitive overhead you need to pay if you found a clever way to remove the syntactical burden) is too big except in very few cases.
- 10y ago