You see the same thing happen in async circles. All of a sudden every single function has to be labeled async, whether it needs to or not.
You see the same thing happen in async circles. All of a sudden every single function has to be labeled async, whether it needs to or not.
Haskell’s use of monads is perhaps the ultimate in colouring (for values not functions, but that doesn’t make that much of a difference); you routinely colour everything with which pieces of state it needs to read and/or write to do its thing. The result is you either have everything painted in “global state”, which just feels like unnecessary boilerplate, or you spend most of your time defining and converting between elaborately carved pieces of said state.
People could and did invent various intricate constructions which would do the conversions and possibly even the carving for you, but overall I think most agreed it was a gigantic pain.
It’s possible the situation is fixable with a correct combination of features (records, polymorphic variants, implicit parameters, possibly even algebraic effects), as a good portion of the pain comes from not having a standard solution every library would use (they all have drawbacks). But then the checked exceptions story in Java, IIRC, is from pre-Java 5 times, and not being able to be polymorphic in the exception signature of a callback you take is really stupid however you look at it, as well as also a case of inadequate language features.
And later on, checked exceptions were incompatible with new stuff like lambdas and streams
Zig actually does pretty well on this front. you can write a library that accepts functions that return errors, and your functions will be specialized to return those errors in addition to their own if you don't handle them. Callers who want to handle errors exhaustively with a switch will get compile errors if the error set changes and they fail to handle new errors.
In a similar vein, as another commenter above said, really the only problem with function coloring is that we're not treating it polymorphically.
public class SomeClass<E extends Exception> {
public someMethod() throws E {}
}
is valid java, I'm pretty sure.The other problem with this approach is that it requires the caller to explicitly specify all the exceptions thrown by the function/object being wrapped. So every time you do map() or fold(), you have to spell out the full exception spec for the closure you're passing in. And god help you if there's nested maps...