Search code examples
haskelltypesalgebraic-data-types

Should Maybe's be used to hold error messages?


I have a Haskell function which takes user input and another function which validates this input. Of course, the validation could fail, in which case I would like to return an error message giving some feedback on what was done incorrectly.

I know that there are many ways that I could do this. After the little experience I have, it seems like the best way is to use Either String a. What is throwing me off is that I don't care about the a. Either it fails and I would like to store more information, or it succeeds. The a is wasted.

Is using Maybe String an acceptable way to store an error message? It feels backwards to me, but completely ignoring the value in the right of an Either feels pretty bad too. What is canonical here?


Solution

  • I encourage the use of Except String () (or Either String ()) over Maybe String, for a few reasons:

    • You will probably find later that your it is convenient for your validation function to return some parts of the data structure. For example, while validating that a String is a phone number, you might want to return the area code, first, and second parts of the number, giving a validation type like String -> Except String (Int, Int, Int) or similar. Making validators that don't return anything interesting have type Foo -> Except String () makes them just a special case of this pattern -- and therefore easier to fit together.
    • Continuing the "fit together" part, you may later find that you want to construct one big validator out of smaller ones. Perhaps you have a validator that checks that a person has specified a valid age and birth date, and want to build a validator out of this that also checks that the age is about right for the birth date. The Monad instance for Either will help here; for example:

      validatePerson p now = do
          age <- validateAge p
          date <- validateBirthdate p
          validateMatchingAgeAndDate age date now
      

      Or perhaps there are two ways for some value to validate properly and you want to allow either. Then bigValidator v = option1 v <|> option2 v is a cheap and cheerful way to combine the two ways of validating.

      As a side benefit, these methods of combining existing validators to make bigger ones will be instantly recognizable to other Haskellers.

    • There is a very strong convention that Nothing is a failure. Using the opposite convention is not a problem, necessarily, but could be confusing to other contributors and potentially to yourself far in the future.