Keep successful values instead of throwing them away when validating
Note
When validating, many programs throw away the successful results of a validation if it's successful (for example by validating with a boolean function). What we can do instead is to parse the "good" value into a type of our own and continue using it in our program.
Functional languages can perform this very well with types and parser combinators.
We should be weary of shotgun parsing - mixing parsing and validating in one place, with the business logic as well, hoping that one of them catches any errors that may occur.
An example of parsing a passport and returning a value of the Passport type
data Passport = Passport
  { birthYear :: Int,
    issueYear :: Int,
    expirationYear :: Int,
    height :: String,
    hairColor :: String,
    eyeColor :: String,
    passportId :: String,
    countryId :: Maybe Int
  }
passportParser :: Parser Passport
passportParser =
    Passport <$$> byrParser -- <$$> builds the type
      <||> iyrParser -- <||> required field
      <||> P.try eyrParser -- P.try - lookahead parser
      <||> P.try heightParser
      <||> P.try hairColorParser
      <||> P.try eyeColorParser
      <||> passportIdParser
      <|?> (Nothing, Just <$> countryIdParser) -- <|?> optional field