Parsing a component of a regex followed by an optional quantifier character
Whenever I find myself using a lambda, I usually replace it with a local definition:

factor = unit &gt;&gt;= factor'
 where
  factor' x
    =  try (char '?' &gt;&gt; return (Or Epsilon x))
   &lt;|&gt; try (char '+' &gt;&gt; return (Then x (Star x)))
   &lt;|&gt; try (char '*' &gt;&gt; return (Star x))
   &lt;|&gt; return x


Since you only need to look at one character to pick the right branch, you don't need to use try everywhere:

factor = unit &gt;&gt;= factor'
 where
  factor' x
    =  (char '?' &gt;&gt; return (Or Epsilon x))
   &lt;|&gt; (char '+' &gt;&gt; return (Then x (Star x)))
   &lt;|&gt; (char '*' &gt;&gt; return (Star x))
   &lt;|&gt; return x


Finally, we can get rid of the extra parens by using *&gt; instead of &gt;&gt;, and we can replace return with pure:

factor = unit &gt;&gt;= factor'
 where
  factor' x
    =  char '?' *&gt; pure (Or Epsilon x)
   &lt;|&gt; char '+' *&gt; pure (Then x (Star x))
   &lt;|&gt; char '*' *&gt; pure (Star x)
   &lt;|&gt; pure x


This works because *&gt; has higher precedence than &lt;|&gt; which has higher precedence than &gt;&gt;.

I find this easier to read. We've restricted the bulk of our code to Applicative operations, and we only need to use Monadic bind at the beginning where factor' has a direct dependency on unit.

EDIT: We don't need try anymore because char x only consumes a character if the next character in the input stream matches x. Furthermore, each of your branches can either completely match or it can completely fail, and there's no danger of overlap between rules. As an example, here's a simple parser that, like yours, has simple match-or-fail parsers that consume only one character and do not overlap:

data Foo = One | Zero
  deriving (Eq, Show)

foo =  char '1' *&gt; pure One
   &lt;|&gt; char '0' *&gt; pure Zero


If we decide to add a new rule that does overlap, we'll have a problem:

data Foo = One | Zero | Ten
  deriving (Eq, Show)

foo =  char '1' *&gt; pure One
   &lt;|&gt; char '0' *&gt; pure Zero
   &lt;|&gt; char '1' *&gt; char '0' *&gt; pure Ten


Now our first and last rule overlap! We can never successfully parse a Ten:

&gt; parse foo "" "1"
Right One
&gt; parse foo "" "10"
Right One


One's first instinct might be to switch the order of the rules, but this just introduces a new problem:

foo =  char '1' *&gt; char '0' *&gt; pure Ten
   &lt;|&gt; char '0' *&gt; pure Zero
   &lt;|&gt; char '1' *&gt; pure One

&gt; parse foo "" "10"
Right Ten
&gt; parse foo "" "1"
Left (line 1, column 2):
unexpected end of input
expecting "0"


Since the first char '1' has already consumed a character, we've committed to that branch. This is the case where you want to use try; where you might need to backtrack after initially committing to some rule that consumes input:

foo =  try (char '1' *&gt; char '0' *&gt; pure Ten)
   &lt;|&gt; char '0' *&gt; pure Zero
   &lt;|&gt; char '1' *&gt; pure One

&gt; parse foo "" "0"
Right Zero
&gt; parse foo "" "1"
Right One
&gt; parse foo "" "10"
Right Ten


Because many parsers are more complicated than char, it can be easy to just add try without thinking. This can make your parser less efficient because it may have to backtrack many times before it makes progress. It can be worth your time to investigate whether a try is really necessary.