|
Raising an exception affects control flow, which is the execution state of the program. TFA example misuses exceptions as just another return value, requiring the caller to have additional code to handle "normal" and "other" return values. So as we and others seem to agree, that is not even a good example. At this point we kind of get stuck in the whole discussion about exceptions and how to handle them. This is probably a hot area that I should avoid getting into, but here is an _example_ of how to handle errors without having exceptions (and without impacting execution states): https://go.dev/blog/error-handling-and-go Function application, where you apply a function to a collection of data, requires that the application function returns a value (and does not throw an exception). As a Python example (not the prettiest, but it's Python...), imagine you have a list of strings which you want to strip the leading and trailing whitespace. x = [' a', 'b ', None] list(map(str.strip, x)) => TypeError exception because you can't strip on None To work around this, you have to do something like an anonymous function to validate your inputs before calling strip: list(map(lambda s: s.strip() if s is not None else None, x)) => ['a', 'b', None] In this case it's clear why Python throws an exception, and we were able to avoid it by validating the input during the mapping. But if a normal behavior of the mapped function was to throw an exception as just another type of result, we couldn't prevent/avoid that, and we would either not be able to apply the timer() function to a list of inputs or we would have to have a form of map() which also accepted an exception handler function and then somehow wove its results into the normal map output. Such a thing may exist, but I don't know Python well enough; and if it does exist, it would only make the language even uglier ;). [Edit: on second thought, the solution would be to make a better_timer() function wrapper which called timer() and handled the exception, then more appropriately returned [time_remaining, complete_flag]. Then we would pass better_timer() to the map().] There are even nicer benefits to purity if you are using higher order functions that may be called later/elsewhere. Instead of taking data through some series of transformations, you take data and build a series of operations which can later be used in a transformation. Then somewhere more toward the edge, well beyond your core business rules, you execute the series of transformations (functions) on your starting data to get an end result. This approach can't be used all the time, but there are cases where it works well. But like the map() example above, if you have to also handle exceptions as normal results returned from functions, then you have to have additional exception handler functions that you carry around with the other functions in case they are needed. Yuck. |
https://leanprover.github.io/about/