Overly complicated parser combinator I recently started to learn Haskell in order to write some small tools for me. Currently I'm working on a program which calculates how much steam a (railcraft) boiler of a given size outputs per tick.

In the console the user inputs a string of the following format: S | L | BL-Size | BH-Size, example BH-3x3x4

I then use ParserCombinators from the Text.ParserCombinators.ReadP library to parse this string into a custom type. However I think my parser is overly complicated. Any suggestions how to simplify the following code?

module Boilers (Boiler(..), Tank(..)) where

data Boiler = Hobbyist | Locomotive | Boiler (Int, Int, Int) Tank deriving (Show)
data Tank = HighPressure | LowPressure deriving (Eq, Show)

module BoilerParser where

import Data.Char
import Text.ParserCombinators.ReadP
import Control.Applicative
import Boilers
import Data.Maybe

boiler :: ReadP Boiler
boiler = do
  b &lt;- standard &lt;++ hobbyist &lt;++ locomotive
  maybe pfail return b

standard :: ReadP (Maybe Boiler)
standard = do
  satisfy (\c -&gt; toUpper c == 'B')
  t &lt;- fmap (\t -&gt; case toUpper t of
    'H' -&gt; Just HighPressure
    'L' -&gt; Just LowPressure
    _   -&gt; Nothing) get
  satisfy (== '-')
  s &lt;- BoilerParser.size
  case t of
    Nothing -&gt; pfail
    (Just a) -&gt; return (Just (Boiler s a))

hobbyist :: ReadP (Maybe Boiler)
hobbyist = fmap (\c -&gt; if toUpper c == 'H' then Just Hobbyist else Nothing) get

locomotive :: ReadP (Maybe Boiler)
locomotive = fmap (\c -&gt; if toUpper c == 'L' then Just Locomotive else Nothing) get

size :: ReadP (Int, Int, Int)
size = do
  x &lt;- size' 1
  satisfy (== 'x')
  y &lt;- size' 1
  satisfy (== 'x')
  z &lt;- size' 1
  return (x,y,z)

size':: Int -&gt; ReadP Int
size' ds = fmap read (count ds (satisfy isDigit))
boiler :: ReadP Boiler
boiler
  =   Hobbyist &lt;$ char 'H'
  &lt;|&gt; Locomotive &lt;$ char 'L'
  &lt;|&gt; flip Boiler &lt;$ char 'B'
    &lt;*&gt; (HighPressure &lt;$ char 'H' &lt;|&gt; LowPressure &lt;$ char 'L')
    &lt;*&gt; ((,,) &lt;$ char '-'
      &lt;*&gt; (read &lt;$&gt; munch1 isDigit) &lt;* char 'x'
      &lt;*&gt; (read &lt;$&gt; munch1 isDigit) &lt;* char 'x'
      &lt;*&gt; (read &lt;$&gt; munch1 isDigit))


By changing the internal definition of Boiler you can clean up that flip.

By also changing the user interface you can use the Read instance deriver:

data Boiler = H | L | BL Int Int Int | BH Int Int Int deriving (Read, Show)


Your example would then look like BH 3 3 4.

By the way, whereever you're using the Eq instance of Tank, consider pattern matching instead.