You can do this using applicative functors. Usually a template has the form:
import Control.Applicative f <$> a1 <*> a2 <*> a3
f is some function that takes 3 arguments in this case, and a1 , a2 and a3 are applicative functors over values ββthat can be passed as arguments to f , for example, if f :: Int -> Int -> Int -> Foo , a1, a2, a3 can be of type Parser Int . The functors a1, a2, a3 will be applied sequentially, and their results will be collected and displayed by the function f .
In your case, you want:
numbers :: Parser (Int, Int, Maybe Int) numbers = (,,) <$> number <*> (char ':' *> number) <*> optionMaybe (char ':' *> number)
(,,) is the constructor of a 3-tuple, so it takes 3 arguments and returns a 3-tuple. Passing 3 parsers with the template <$>..<*>.. removes the use of the 3-tuple constructor in the functor used here, which in this case is Parser , so the whole expression returns the result of the displayed function wrapped in a functor, Parser (Int, Int, Maybe Int) .
You can also use liftA3 f a1 a2 a3 instead of f <$> a1 <*> a2 <*> a3 ; two expressions are equivalent.
PS. You can also define number using applicative functors (the monad interface is more "heavy" and I personally try to avoid it):
number :: Parser Int number = read <$> many1 digit <?> "number"