content: import Data.List (mapAccumR)
  import qualified Data.Map.Strict as M
  import Data.Maybe (maybe)
  
  fromRoman :: String -> Maybe Int
  fromRoman cs =
    let go l r
          | l > r = (- r, l)
          | otherwise = (r, l)
     in traverse (`M.lookup` mapRoman) cs
          >>= ( Just . sum . ((:) <$> fst <*> snd)
                  . mapAccumR go 0
              )
  
  mapRoman :: M.Map Char Int
  mapRoman =
    M.fromList $
      zip
        "MDCLXVI "
        [ 1000,
          500,
          100,
          50,
          10,
          5,
          1,
          0
        ]
  
  --------------------------- TEST -------------------------
  main :: IO ()
  main =
    putStrLn $
      fTable
        "Decoding Roman numbers:\n"
        show
        (maybe "Unrecognised character" show)
        fromRoman
        [ "MDCLXVI",
          "MCMXC",
          "MMVIII",
          "MMXVI",
          "MMXVIII",
          "MMXBIII"
        ]
  
  ------------------------ FORMATTING ----------------------
  fTable ::
    String ->
    (a -> String) ->
    (b -> String) ->
    (a -> b) ->
    [a] ->
    String
  fTable s xShow fxShow f xs =
    unlines $
      s :
      fmap
        ( ((<>) . rjust w ' ' . xShow)
            <*> ((" -> " <>) . fxShow . f)
        )
        xs
    where
      rjust n c = drop . length <*> (replicate n c <>)
      w = maximum (length . xShow <$> xs)
  