# Refactoring Nested Case Expressions in PureScript

I often find myself writing “branching code” in PureScript. This results in nested case expressions that are akin to JavaScript’s pyramid of doom. Take this function for example…

```
compileToConfig :: String -> Either Error SerializedConfig
compileToConfig story =
case parseManyStories story of
Left err -> Left $ parseErrorToError err
Right s -> case Array.head s of
Just ss -> Right $ SerializedConfig {
config : GameConfig {
startLocation : (view _name ss),
aliasGroups : mempty
},
stories : s
}
_ -> Left $ error $ "No story parts could be parsed from the input."
parseErrorToError :: ParseError -> Error
parseErrorToError err = error $
parseErrorMessage err <> ". " <>
(show $ parseErrorPosition err)
```

It has two possible failure cases - The first is a parse error, and the second is a content error. Let’s clean this function up by flattening the pyramid.

The first thing to notice is that the the return type is `Either`

, and that the two possible failure points return `Either`

and `Maybe`

. The Eithers differ in their `Left`

constructor though - one returns a `ParseError`

and the other returns a regular `Error`

. If we can convert `Either ParseError`

into `Either Error`

then we can flatten the computation by taking advantage of Either’s Monad instance and do-notation.

It turns out we can easily convert `Either ParseError`

into `Either Error`

by using Bifunctor’s `lmap`

function. `lmap`

will run a function only on the `Left`

value of `Either`

, and leave the `Right`

value alone. So to start, we can run the `parseManyStories`

function as follows…

```
compileToConfig :: String -> Either Error SerializedConfig
compileToConfig story = do
stories <- lmap parseErrorToError $ parseManyStories story
```

The next failure point is the `Array.head`

function. Since `head`

returns `Maybe a`

we can’t bind the results of this computation inside our function. That is, we can’t do the following…

```
compileToConfig :: String -> Either Error SerializedConfig
compileToConfig story = do
stories <- lmap parseErrorToError $ parseManyStories story
-- This will not work.
firstStory <- Array.head stories
```

This is because we’re running our function in the “Either context” - function calls with the form `x <- fn`

inside `compileToConfig`

MUST return a `Either Error a`

. So we need to find a way to convert a `Maybe a`

into an `Either Error a`

. Let’s continue rewriting the function, and use PureScript’s typed-hole feature to ask the compiler if a function exists to convert `Maybe a`

into an `Either Error a`

.

```
compileToConfig :: String -> Either Error SerializedConfig
compileToConfig story = do
stories <- lmap parseErrorToError $ parseManyStories story
firstStory <- ?whatgoeshere noStoryPartsError (Array.head stories)
pure $ SerializedConfig {
config : GameConfig {
startLocation : (view _name firstStory),
aliasGroups : mempty
},
stories : stories
}
where
noStoryPartsError =
error $ "No story parts could be parsed from the input."
```

Now the compiler will annotate the `?whatgoeshere`

typed-hole with the following message…

```
[PureScript]
Hole 'whatgoeshere' has the inferred type
Error -> Maybe ParsedStory -> Either Error ParsedStory
You could substitute the hole with one of these values:
Data.Either.note :: forall a b. a -> Maybe b -> Either a b
Unsafe.Coerce.unsafeCoerce :: forall a b. a -> b
in the following context:
noStoryPartsError :: Error
stories :: Array ParsedStory
story :: String
in value declaration compileToConfig
```

Which tells us there is a function named `note`

in `Data.Either`

that’s exactly what we need. Here’s what our final refactored function looks like.

```
compileToConfig :: String -> Either Error SerializedConfig
compileToConfig story = do
stories <- lmap parseErrorToError $ parseManyStories story
firstStory <- note noStoryPartsError (Array.head stories)
pure $ SerializedConfig {
config : GameConfig {
startLocation : (view _name firstStory),
aliasGroups : mempty
},
stories : stories
}
where
noStoryPartsError =
error $ "No story parts could be parsed from the input."
```

No more pyramid!

As a side note, this same technique can be applied to effectful functions. Some examples of computational contexts that implicitly handle failure are `Aff e a`

, `Except`

, `ExceptT`

, or any Monad with a `MonadError`

instance.