Is an exception less (pure) than a Result?
Tags: discuss, functional
Published: 2020-09-17 18:07:46

Recently I saw a video about the [Railway Oriented Programming](https://fsharpforfunandprofit.com/rop/) and I can't help myself but I ask "why"? So I searched and found [Against Railway-Oriented Programming](https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/) from the same author. Still, I have to ask: "**Why should I teach how to compose functions that don't match** when I can simply throw an exception and **have all of this for free**? And it is not just this video. With recent OOP hate and functional passion, I hear it all the time how the [explicit result is better](https://dev.to/wemake-services/python-exceptions-considered-an-anti-pattern-17o9) because then the function is pure and returns always the same output data for the same input data. And then I come to the question. Is that different from the exceptions?

From what I know the function is pure when it returns the same result for the same arguments. And if the function always throws the same exception for the same input arguments [it is pure](https://stackoverflow.com/questions/40305162/can-a-pure-function-throw-an-exception). Even [reducers in Redux](https://redux.js.org/basics/reducers) are considered pure if they don't have side-effects and they throw exceptions all the time (famous _undefined is not an object/function/..._). So there has to be something else. Something why one would like to abandon the simplicity of throwing exceptions with the whole railway and even call it a better approach. Below is an example to try to find it.

```typescript
type Result = 
  | {success: true, processedData: ProcessedData}
  | {success: false, error: string};

/**
 * Pure function with the "Result" type.
 */
function pureFunction(user?: User, data: Data): Result {
  if (!user) {
    return {success: false, error: 'User is not authenticated'}
  }

  return {success: true, processedData: {...data, processed: true}}
}

/**
 * The same function but "returning" an error as an exception.
 */
function maybePureFunction(user?: User, data: Data): ProcessedData {
  if (!user) {
    return throw new UserNotAuthenticated(user)
  }

  return {...data, processed: true}
}
```

Can you spot the difference? I can. It's hidden in types. **I don't know any language out there checking the types of the exceptions.** Java somehow tried with the [checked exceptions and failed](https://dev.to/chochos/i-hate-checked-exceptions-43lj), [Python don't plan it](https://github.com/python/typing/issues/71). Nobody wants to write all the possible exceptions thrown from every function (sorry, method). And still, it is the same as to write all the return types. So this is a **big advantage of the `Return` type - type safety**. The compiler will tell you that you don't handle some possible error but don't warn you about uncaught exceptions.

And then I had to ask again. Why? Why can't we have both advantages? **Why can't I just throw an exception and let the compiler infer the exceptions** for the function and **warn me about possibilities**? Then I don't need to learn how to combine functions by `bind`, `>>=`, etc. I think the idea of checked/unchecked exceptions in Java is good. Only I would name them `BusinessException` and `ProgrammerError` or something like that. And they would be inferred by the compiler.

In fact, I am not afraid to consider exceptions to be algebraic data types, [sum types](https://guide.elm-lang.org/appendix/types_as_sets.html#addition-custom-types) or [enums as defined by Rust](https://doc.rust-lang.org/book/ch06-00-enums.html). Here is my ideal API.

```typescript
try {
    return maybePureFunction(user, data)
} catch (e) {
    // here the compiler knows "e" is "UserNotAuthenticated"
    return DATA_FOR_ANONYMOUS_USER
}

// OR

// compiler infers here the function return type is kind-of "Result<ResourceNotFound | DangerousError, string>"
function something(fileName: string): string {
    try {
        return dangerousCall(fileName)  // throws "DangerousError" unhandled here
    } catch (e: FileDoesNotExist | InsufficientPermissions) {
        throw new ResourceNotFound(fileName);
    } catch (e: FileLocked) {  // isn't this actually pattern matching?
        return ""
    }
}
```

What do you think about it? And more importantly, **do you know such a language where exceptions are a proper part of the type system** and are treated as a result of the function?

#### Interesting sources
- [A pure function must have Referential Transparency](https://softwareengineering.stackexchange.com/a/334682/225599)
- [Why is catching an exception non-pure, but throwing an exception is pure?](https://stackoverflow.com/questions/12335245/why-is-catching-an-exception-non-pure-but-throwing-an-exception-is-pure)
- https://stackoverflow.com/questions/10703232/why-is-the-raising-of-an-exception-a-side-effect
- [Python exceptions considered an anti-pattern](https://dev.to/wemake-services/python-exceptions-considered-an-anti-pattern-17o9)
- https://stackoverflow.com/questions/36597079/what-is-the-purpose-of-throwing-exceptions-in-pure-functions
- https://www.reddit.com/r/scala/comments/a2odjb/can_a_pure_function_throw_exceptions/
- https://www.reddit.com/r/haskell/comments/7wblve/exceptions_in_pure_functions/
- http://lambda-the-ultimate.org/node/4544
- https://blog.jooq.org/javas-checked-exceptions-are-just-weird-union-types/
- https://itnext.io/my-personal-definitive-guide-to-java-exceptions-d2e4131393c7

<!--
Exception can be thrown by dividing zero or null pointer or memoryerror in Python so all functions can throw it. It needs to be divided to checked and unchecked.

Exceptions have stack so you know where the problem origins but with result this is hard.

Exceptions are implicit check for error, like Go's check keyword. It's like That's
```rust
fn parent(): Result<Error, u32> {
  child()?;
}
```

Go is too boilerplate so they are considering different syntax https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling.md

```go
func printSum(a, b string) error {
	x, err := strconv.Atoi(a)
	if err != nil {
		return err
	}
	y, err := strconv.Atoi(b)
	if err != nil {
		return err
	}
	fmt.Println("result:", x + y)
	return nil
}
```

```go
func printSum(a, b string) error {
	handle err { return err }
	x := check strconv.Atoi(a)
	y := check strconv.Atoi(b)
	fmt.Println("result:", x + y)
	return nil
}
```

```python
def parent() -> int:
  child()
```

Exceptions feel more like a GOTO statement given they interrupt the program flow by jumping back to the caller. -- is return like GOTO? It is doing the same. Is function call GOTO? All they are but structured. GOTO is everywhere in low-level.

Exceptions are not reliable as throwing an exception may not survive an async block. -- jop unhandled rejection in promise in js and the same with threads. But this has solution. Enforce handling of these things. Promise should have second generic parameter with error. Implicitly, informed by code.

https://en.wikipedia.org/wiki/Referential_transparency

https://www.pragmaticobjects.com/chapters/001_checked_exceptions.html


Iven Marquardt replied to you on DEV
Thanks for the answer. It forced me to search for stack unwinding and I found...
re: Is an exception less (pure) than a Result?
Yes, exceptions are a bit like goto in disguise. They also alter control flow implicitly, which is quite harmful. However, along with functional programming there is another issue. FP is about math and preserving its properties. Let's say that the following program throws an exception as soon as head is applied to an empty array:
const head = ([x]) => x;

const xs = [1,2], ys = [3,4], zs = [];

add(add(head(xs)) (head(ys))) (head(zs)); // A

add(head(xs)) (add(head(ys)) (head(zs))); // B

As a functional programmer you can reason about the expression A without taking the context into account. This is called local reasoning and one of the pillars of the paradigm. You can also rearrange the expression (and the compiler/interpreter as well), because it is essentially an equation (associativity of addition is preserved). Now the exception and the implicit change of control flow interferes with local reasoning. This isn't a problem considering the simple example above, but it becomes a huge one as your program and complexity grows.

Hope that helps.
-->