Better to use error monad with validation in your monadic functions, or implement your own monad with validation directly in your bind?

softwareengineering.stackexchange https://softwareengineering.stackexchange.com/questions/185094

Question

I'm wondering what's better design wise for usability/maintainability, and what's better as far as fitting with the community.

Given the data model:

type Name = String

data Amount = Out | Some | Enough | Plenty deriving (Show, Eq)
data Container = Container Name deriving (Show, Eq)
data Category = Category Name deriving (Show, Eq)
data Store = Store Name [Category] deriving (Show, Eq)
data Item = Item Name Container Category Amount Store deriving Show
instance Eq (Item) where
  (==) i1 i2 = (getItemName i1) == (getItemName i2)

data User = User Name [Container] [Category] [Store] [Item] deriving Show
instance Eq (User) where
  (==) u1 u2 = (getName u1) == (getName u2)

I can implement monadic functions to transform the User for instance by adding items or stores etc, but I may end up with an invalid user so those monadic functions would need to validate the user they get and or create.

So, should I just:

  • wrap it in an error monad and make the monadic functions execute the validation
  • wrap it in an error monad and make the consumer bind a monadic validation function in the sequence that throws the appropriate error response (so they can choose not to validate and carry around an invalid user object)
  • actually build it into a bind instance on User effectively creating my own kind of error monad that executes validation with every bind automatically

I can see positives and negatives to each of the 3 approaches but want to know what is more commonly done for this scenario by the community.

So in code terms something like, option 1:

addStore s (User n1 c1 c2 s1 i1) = validate $ User n1 c1 c2 (s:s1) i1
updateUsersTable $ someUser >>= addStore $ Store "yay" ["category that doesnt exist, invalid argh"]

option 2:

addStore s (User n1 c1 c2 s1 i1) = Right $ User n1 c1 c2 (s:s1) i1
updateUsersTable $ Right someUser >>= addStore $ Store "yay" ["category that doesnt exist, invalid argh"] >>= validate
-- in this choice, the validation could be pushed off to last possible moment (like inside updateUsersTable before db gets updated)

option 3:

data ValidUser u = ValidUser u | InvalidUser u
instance Monad ValidUser where
    (>>=) (ValidUser u) f = case return u of (ValidUser x) -> return f x; (InvalidUser y) -> return y
    (>>=) (InvalidUser u) f = InvalidUser u
    return u = validate u

addStore (Store s, User u, ValidUser vu) => s -> u -> vu
addStore s (User n1 c1 c2 s1 i1) = return $ User n1 c1 c2 (s:s1) i1
updateUsersTable $ someValidUser >>= addStore $ Store "yay" ["category that doesnt exist, invalid argh"]

No correct solution

Licensed under: CC-BY-SA with attribution
scroll top