Other tools for Monadic error handling
Tags: typescript, promise, javascript, monad
Published: 2021-02-08 20:12:33


[In the previous article](https://dev.to/airtucha/promise-and-other-tools-for-monadic-error-handling-480h) we already gained some intuition regarding monadic error handling with `Promise`, it is time for us to move forward. JavaScript doesn't have native solutions for monadic error handling beyond `Promise`, but there are many libraries which helps to fulfil the functionality. [amonad](https://www.npmjs.com/package/amonad) has the most similar to the `Promise` API. Therefore it is going to be used for the following examples.

Abstraction which represents the result of computations which can possibly fail is commonly known as `Result`. It is like immediately resolved `Promise`. It can be represented by two values: `Success` contains expected information, while `Failure` has the reason for the error. Moreover, there is `Maybe` as known as `Option` which also embodied by two kinds: `Just` and `None`. The first one works in the same way as `Success`. The second one is not even able to carry information about the reason for value's absents. It is just a placeholder indicated missing data. 

## Creation

`Maybe` and `Result` values can be instantiated via factory functions. Different ways to create them are presented in the following code snippet.

```typescript
const just = Just(3.14159265)
const none = None<number>()
const success = Success<string, Error>("Iron Man")
const failure: Failure<string, Error> = 
  Failure( new Error("Does not exist.") )
```

`NaN` safe division function can be created using this library in the way demonstrated below. In that way, the possibility of error is embedded in the return value.

```typescript 
const divide = (
    numerator: number, 
    quotient: number 
): Result<number, string> => 
    quotient !== 0 ?
        Success( numerator/quotient )
    :
        Failure("It is not possible to divide by 0")
```

## Data handling

Similarly to `Promise`, `Result` and `Maybe` also have `then()`. It also accepts two callback: one for operations over enclosed value and other one dedicated for error handling. The method returns a new container with values processed by provided callbacks. The callbacks can return a modified value of arbitrary type or arbitrary type inside of similar kind of wrapper.

```typescript 
// converts number value to string
const eNumberStr: Maybe<string> = Just(2.7182818284)
    .then( 
        eNumber => `E number is: ${eNumber}` 
    )
// checks if string is valid and turns the monad to None if not
const validValue = Just<string>(inputStr)
    .then( str => 
        isValid(inputStr) ?
            str
            :
            None<string>()
    )
```

Besides that due to the inability of dealing with asynchronism, availability of enclosed value is instantly known. Therefore, it can checked by `isJust()` and `isSuccess()` methods. 

Moreover, the API can be extended by a number methods to unwrap a value: `get()`, `getOrElse()` and `getOrThrow()`. `get()` output is a union type of the value type and the error one for `Result` and the union type of the value and `undefined` for `Maybe`. 

```typescript
// it is also possible to write it via isJust(maybe)
if( maybe.isJust() ) { 
    // return the value here
    const value = maybe.get(); 
    // Some other actions...
} else {
    // it does not make sense to call get() 
    // here since the output is going to be undefined
    // Some other actions...
}
```

```typescript
// it is also possible to write it via isSuccess(result)
if( result.isSuccess() ) { 
    // return the value here
    const value = result.get(); 
    // Some other actions...
} else {
    // return the error here
    const error = result.get(); 
    // Some other actions...
}
```

## Error handling 

The second argument of the `then()` method is a callback responsible for the handling of unexpected behaviour. It works a bit differently for `Result` and `Maybe`. 

In the case of `None`, it has no value, that's why its callback doesn't have an argument. Additionally, it doesn't accept mapping to the deal, since it should produce another `None` which also cannot contain any data. Although, it can be recovered by returning some fallback value inside of `Maybe`.

In the case of `Failure`, the second handler works a bit similar to the first one. It accepts two kinds of output values: the value of Throwable as well as anything wrapped by `Result`.

Additionally, both of them are also capable of handling callbacks returning a `void`, it can be utilized to perform some side effect, for example, logging. 

```typescript 
// tries to divide number e by n, 
// recoveries to Infinity if division is not possible
const eDividedByN: Failure<string, string> = 
    divide(2.7182818284, n)
        .then( 
            eNumber => `E number divided by n is: ${eNumber}`,
            error => Success(Infinity)
        )
```

```typescript 
// looks up color from a dictionary by key,
// if color is not available falls back to black
const valueFrom = colorDictionary.get(key)
    .then( 
        undefined,
        () => "#000000"
    )
```

Similarly to previous situations, it is also possible to verify if the value is `Failure` or `None` via `isNone()` and `isFailure()` methods.

```typescript
 // it is also possible to write it via isNone(maybe)
if(maybe.isNone()) {
    // it does not make sense to call get() 
    // here since the output is going to be undefined
    // Some other actions...
} else {
    // return the value here
    const value = maybe.get(); 
    // Some other actions...
}
```
```typescript
// it is also possible to write it via isFailure(result)
if(result.isFailure()) { 
    // return the error here
    const error = result.get(); 
    // Some other actions...
} else {
    // return the value here
    const value = result.get();
    // Some other actions...
}
```

## Which one should be used?

Typical usage of `Maybe` and `Result` is very similar. Sometimes it is hardly possible to make a choice, but as it was already mentioned there is a clear semantic difference in their meanings. 

`Maybe`, primary, should represent values which might not be available by design. The most obvious example is the return type of `Dictionary`:

```typescript
interface Dictionary<K, V> {
    set(key: K, value: V): void
    get(key: K): Maybe<V>
}
```

It can also be used as a representation of optional value. The following example shows the way to model a `User` type with `Maybe`. Some nationalities have a second name as an essential part of their identity others not. Therefore the value can nicely be treated as `Maybe<string>`.

```typescript
interface Client {
    name: string
    secondName: Maybe<string>
    lastName: string
}
```

The approach will enable implementation of client's formatting as a string the following way.

```typescript
class VIPClient {
    // some implementation
    toString() {
        return "VIP: " + 
            this.name + 
            // returns second name surrounded 
            // by spaces or just a space
            this.secondName
                .then( secondName => ` ${secondName} ` )
                .getOrElse(" ") + 
            this.lastName
    }
}
```

Computations which might fail due to obvious reason are also a good application for `Maybe`. Lowest common denominator might be unavailable. That is why the signature makes perfect sense for `getLCD()` function:

```typescript
getLCD(num1: number, num2: number): Maybe<number>
```

`Result` is mainly used for the representation of value which might be unavailable for multiple reasons or for tagging of a data which absents can significantly affect execution flow. 

For example, some piece of class’s state, required for computation, might be configured via an input provided during life-circle of the object. In this case, the default status of the property can be represented by `Failure` which would clarify, that computation is not possible until the state is not initialized. Following example demonstrates the described scenario. The method will return the result of the calculation as `Success` or “Data is not initialized” error message as `Failure`.

```typescript
class ResultExample {
  value: Result<Value, string> = Failure(“Data is not initialized”)
  
  init( value: Value ) {
    this.value = Success(value) 
  }

  calculateSomethingBasedOnValue(){
    return this.value.then( value =>
        someValueBasedComputation( value, otherArgs)
     )
  }
}
```

Moreover, `Result` can replace exceptions as the primary solution for error propagation. Following example presents a possible type signature for a parsing function which utilizes `Result` as a return type.

```typescript
parseUser( str: string ): Result<Data>
```

The output of such a function might contain processed value as `Success` or an explanation of an error as `Failure`.

# Conclusion

`Promise`, `Result` and `Maybe` are three examples of monadic containers capable of handling missing data. `Maybe` is the most simple one, it is able to represent a missing value. `Result` is also capable to tag a missing value with an error message. `Promise` naturally extends them with an ability to represent data which might become available later. Moreover, it can never become available at all. That might happen due to error which can be specifically passed in case of rejection. So, `Promise` is the superior one and it can basically model all of them. However, specificity helps to be more expressive and efficient. 

This approach to error handling is a paradigm shift since it prevents engineers from treating errors as exceptional situations. It helps to express them as an essential part of the execution. You know, from time to time all of us fails. So in my mind, it is wise to follow a known principle: "If you are going to fail, fail fast".