{{ message }}

Instantly share code, notes, and snippets.

# davidchambers/comment.md

Created Jun 2, 2016
Quick introduction to chaining monads from a pull request review
```var convertPercentage = function(percentage) {
if (percentage == null) {
return null;
} else {
return parseFloat(percentage.replace(/[^-\d.]/g, ''));
}
};```

Writing functions which have a special case for `null`/`undefined` is undesirable for several reasons:

• it increases the amount of code which must be written and maintained;
• it increases the number of code branches for which tests must be written; and
• it allows errors to propagate silently.

Let's start by considering the types. We expect the input to be a string, so let's require that. We then have:

`convertPercentage :: String -> ???`

See http://sanctuary.js.org/#types for an explanation of the notation.

So, what should the `???` be? We could make it `Number`, but then we're forced to live with the fact that `NaN` is a possible return value. `NaN`—like `null`—is problematic as it forces the caller to check the return value before using it in future computations.

The correct return type is `Maybe Number`. The Maybe type is defined as:

`data Maybe a = Just a | Nothing`

So a value of type `Maybe Number` is either a Just containing a number or it is Nothing. Regardless of which it is, we have a safe way to perform future computations on the value without first inspecting it.

```Prelude> fmap (+ 1) (Just 42)
Just 43

Prelude> fmap (+ 1) Nothing
Nothing```

In JavaScript (with Ramda and Sanctuary):

```> R.map(S.inc, S.Just(42))
Just(43)

> R.map(S.inc, S.Nothing())
Nothing()```

So, we'll make the function's type:

`convertPercentage :: String -> Maybe Number`

Now, let's implement it:

```//    convertPercentage :: String -> Maybe Number
const convertPercentage = S.compose(S.parseFloat, R.replace(/[^-\d.]/g, ''));```

`convertPercentage('~42~')` will evaluate to `Just(42)` while `convertPercentage('XXX')` will evaluate to `Nothing()`.

Now, let's revisit the `null` problem. Here's the expression at the call site:

`convertPercentage(R.path(['StandardPurchaseAPR', 'value'], balances))`

The problem is that `R.path` does not have the desired type; the function assumes that the path will always exist. Instead of using `R.path`, let's use `S.gets`:

```//    s :: Maybe String
const s = S.gets(String, ['StandardPurchaseAPR', 'value'], balances);```

So, now we have a value of type `Maybe String` which we wish to provide as an argument to a function of type `String -> Maybe Number`. The types don't line up. What do you do? Enter `R.chain` (which is the equivalent of Haskell's `>>=`). It has the following type:

`R.chain :: Monad m => (a -> m b) -> m a -> m b`

Let's make this clearer by replacing the type variables as follows:

• `m``Maybe`
• `a``String`
• `b``Number`

This gives:

`R.chain :: (String -> Maybe Number) -> Maybe String -> Maybe Number`

`convertPercentage` is of exactly the right type to use as the first argument to `R.chain`! This gives:

`R.chain(convertPercentage) :: Maybe String -> Maybe Number`

So now we have a function of type `Maybe String -> Maybe Number`, which is exactly what we wanted.

```//    s :: Maybe String
const s = S.gets(String, ['StandardPurchaseAPR', 'value'], balances);

//    n :: Maybe Number
const n = R.chain(convertPercentage)(s);```

Note that we were able to chain together two operations which may fail (nested property access and string parsing) without any error handling whatsoever. This is the beauty of monads. :)

to join this conversation on GitHub. Already have an account? Sign in to comment