When I first started writing Haskell, there was a tendency that my codes will shift to the right that it becomes really hard to read. This is mostly due to the use of
Either, and it is often that I need to unwrap value from these constructs and make a decision based on the unwrapped value.
case mDash of Nothing -> sendErrorResponseString "Can't find dashboard" Just dash -> do case dashboardPanels . entityVal $ dash of Nothing -> sendErrorResponseString "Panel empty" Just panels -> do case Safe.atMay panels panelIdx of Nothing -> sendErrorResponseString "Can't find panel at index" Just panel -> do case menuPanelCarr panel of Nothing -> sendErrorResponseString "Can't find panel at index" Just contentArray -> do case Safe.atMay (contentArrayArr contentArray) widgetIdx of Nothing -> sendErrorResponseString "Can't find panel at index" Just cObj -> sendSuccessReponse cObj
One thing to look for when you have this mess of nesting is to check if it is returning the same type. For example, I am always returning an error message on
Nothing. Although the type here is
IO (), if you squint hard enough what I intend to return is the error string.
We also note that all those nesting are resultant of
Maybe, and we know
Maybe is a
Monad. It would be nice if we can somehow take that into advantage.
example :: Maybe String example = do x <- getMaybeX y <- getMaybeY concat [show x, show y]
Nothing that will short-circuit the entire checks and return
Nothing, that is the same idea as case splitting on
Maybe with our terrible nesting example at the top.
With the help of monad transformers (since we are working in
IO monad at this point), we could make use of
Control.Monad.Trans.Either to clean up our codes. Below works pretty much the same as the above except that we are operating in
result <- runEitherT . hoistEither $ do mPanels <- maybe (Left "Can't find dashboard") (Right . dashboardPanels . entityVal) mDash mPanel <- maybe (Left "No panels") (\panels -> Right $ Safe.atMay panels panelIdx) mPanels mContents <- maybe (Left "Can't find panel") (Right . menuPanelCarr) mPanel mContentObj <- maybe (Left "No contents") (\contents -> Right $ Safe.atMay (contentArrayArr contents) widgetIdx) mContents maybe (Left "Can't find widget") Right mContentObj case result of Left err -> sendErrorResponseString err Right cObj -> sendSuccessResponse cObj
Instead of running an action on
Nothing, we simply return
Left String result so that we can return error message at the point of failure.