Hacker News new | ask | show | jobs
by z9znz 1302 days ago
Raising an exception is not returning a value; it is essentially a GOTO.

A pure function has no side effects. Maybe it takes an input, and maybe it returns a result. But whatever it does internally has no affect on the outside world. Raising an exception is definitely a side effect.

Worse yet, in TFA example it wasn't even necessary to use an exception in place of a returned state. I would argue that the exception example was, irrespective of functional vs imperative principles, a bad use of exceptions.

1 comments

Yeah, I agree that in that Python example, use of exceptions is awful (and the entire example is awful, I think).

> But whatever it does internally has no affect on the outside world. Raising an exception is definitely a side effect.

However, would you please elaborate, how do exceptions affect the outside world, in your opinion? As far as I understand, exceptions only "return exceptional value" up the call stack, to the calling function. Of course, barring total program crash in case of unhandled exception, but unhandled exception is a programmer's error, like integral division by zero or infinite memory allocation loop.

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.

Lean maps returns into exceptions.

https://leanprover.github.io/about/