|
I really like your answer. I think it's very important to start with actual problems and then work your way towards a solution rather than vice-versa, because I've always been more troubled by all the bad things people do with rich types[1] rather than notice the importance of problems they do solve. On the other hand, any powerful general tool built into the language can be more trouble than it's worth. Macros are a good examples, but types can be worse. Macros are local to an expression, but types carry the magic with the variable. Sometimes types just carry restrictions, which is OK, but when traits/typeclasses/implicits are involved, they carry actual "bad" magic. So while other specific solutions may take time to implement, codebases live for a long time, so I'd rather wait and suffer some inconvenience, than have people turn a codebase into an unmaintainable, magical mess (which is what I've heard large companies using Scala say it's done to them). Linear types and intersection types are indeed less prone for abuse, and are mostly "restriction-only", so they seem safer, and are less likely to be abused for data types rather than cross-cutting concerns. Another approach -- my favorite -- is exactly Checker. I agree that at this point in time its UX is pretty bad, but it really doesn't have to be. It's mostly a result of the U-of-W putting more effort into the actual mechanics rather than the UX (which, IMO, is a mistake, but they're a university and naturally care more about research...). The Java annotation-processing mechanism generally works very well with IDEs. Another question is, how bad those problems you mentioned really are. Concurrency bugs are terrible, and simple data typing bugs are usually a non-issue, but tainting/resource-use (i.e. solvable with intersection/linear types respectively) may be bad enough. I really wish someone would conduct an empirical research comprising a taxonomy of bugs, their frequency, and their cost. Such data would be infinitely more useful for language design in the industry than, say, all research going into session types (not trying to pick on session types research, but I hope you catch my meaning when I speak of actual applied utility). BTW, I think any interesting idea is worth working on. The problem is that a language is a very expensive tool -- and a risky one -- so when it comes to industrial, rather than academic, use a lot of care should be taken when picking one, so it's better to err on the side of caution. [1]: See Scala's collections as an extreme example. Is it really necessary to go through all that trouble of unreadable code just to save us a cast here and there that has never really bothered anyone? |
I do think implicit conversions allow too much; IMO we need typeclasses and extension methods. So my ideal design for a language would include implicit parameters but not implicit conversions, and instead have dedicated support for extension methods. That said we do need to be able to use typeclasses without disrupting method application syntax (i.e. support the use case that means Spray prefers the implicit-conversion-based "magnet pattern" rather than a typeclass).
More generally I think there will be languages that allow too much and languages that ban too much, and we need languages on both sides of the line to allow us to converge on something better. Most places I've worked have had a strong code review/pull request culture which may have coloured my experience - I just haven't seen the Scala maintainability problems you talk about.
(If I thought the complexity of collections was necessary to avoid casts then I would defend it - casts can fail and so it's very nice to have a codebase where they're banned entirely (enforced with wartremover). But I don't think it is - FWIW Odersky's asked for proposals for scala collections in version 3, and I think there's a substantial push towards at least some simplification - perhaps replacing CanBuildFrom with a 1-parameter typeclass that would retain most of the power and simplify the code. The showing-off cases like BitSet#map would require one more method call, but no casts)