Skip to content

Instantly share code, notes, and snippets.

@boj
Created May 22, 2017 22:32
Show Gist options
  • Save boj/fedd3e9cea6f2fac43eb36806d0c680b to your computer and use it in GitHub Desktop.
Save boj/fedd3e9cea6f2fac43eb36806d0c680b to your computer and use it in GitHub Desktop.
Data.Bifunctor example of Either A B to Either A C
-- Given some function with an return type Either A B
buildTemplate :: Text -> IO (Either TemplateError [TmplExpr])
-- Another function with the return type Either A C uses the prior function,
-- but due to the fact that the type signature varies, one cannot rely on
-- monadic chaining to work correctly for the failure case.
loadWeatherTemplates :: IO (Either TemplateError WeatherTemplates)
loadWeatherTemplates =
buildTemplate enterTemplate >>= \et ->
buildTemplate inputTemplate >>= \it ->
buildTemplate menuTemplate >>= \mt ->
buildTemplate weatherTemplate >>= \wt ->
-- do stuff, but sadly it won't compile
-- expects Either A C but is actually Either A B
-- First attempt - nested case statements
loadWeatherTemplates :: IO (Either TemplateError WeatherTemplates)
loadWeatherTemplates =
buildTemplate enterTemplate >>= \et ->
case et of
Left e -> return . Left $ e
Just et' ->
buildTemplate inputTemplate >>= \it ->
case it of
Left e -> return . Left $ e
Just it' ->
buildTemplate menuTemplate >>= \mt ->
case mt of
Left e -> return . Left $ e
Just mt' ->
buildTemplate weatherTemplate >>= \wt ->
case wt of
Left e -> return . Left $ e
Just wt' ->
-- finally, work with our values
-- this is ridiculous, especially since we could be parsing 200+ templates
-- Data.Bifunctor to the rescue
-- https://hackage.haskell.org/package/bifunctors-3.0/docs/Data-Bifunctor.html
-- second allows us to map the B to a C
-- allowing us to compose our Either A B with Either A C
second :: Bifunctor p => (b -> c) -> p a b -> p a c
-- Execute each 'buildTemplate' action
-- then use 'second' to map the Bs to a C
-- and compose them together so that in the case of an Either failure it propogates the TemplateError back up
loadWeatherTemplates :: IO (Either TemplateError WeatherTemplates)
loadWeatherTemplates = do
et <- buildTemplate enterTemplate
it <- buildTemplate inputTemplate
mt <- buildTemplate menuTemplate
wt <- buildTemplate weatherTemplate
return $ second (\v -> mkWeatherTemplates { wtEnter = v }) et >>= \w0 ->
second (\v -> w0 { wtInput = v }) it >>= \w1 ->
second (\v -> w1 { wtMenu = v }) mt >>= \w2 ->
second (\v -> w2 { wtWeather = v }) wt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment