This is starting to look promising. I think what would be really interesting would be to abstract out the validation logic so that it could be presented in a more declarative fashion, something like
itemPrice *> \item inputPrice ->
if inputPrice > 0 then return inputPrice
else fail ("invalid item price for item " ++ show (item,inputPrice))
itemTotal *> \item _ -> do
price <- need $ itemPrice item
qty <- need $ itemQty item
return $ price * qty
itemizedVat *> \item _ -> do
vatExempt <- need $ vatStatus . client . invoice $ item
if vatExempt then return 0 else do
total <- need $ itemTotal item
rate <- need $ vatRate item
return $ calcVatForValue total rate
total *> \invoice _ -> do
itemTotals <- forM (items invoice) $ \item -> (,) <$> need (itemTotal item) <*> need (itemizedVat item)
let (itemVals,vatVals) = unzip itemTotals
subTotal = sum itemVals
vatTotal = sum vatVals
return $ Total { subTotal, vatTotal, fullTotal = subTotal+vatTotal }
Inspired by the shake build system. Off the top of my head, so there's no system I know of that implements something like this. But it might be a nice way to declare complex validation rules, perhaps? Error handling could be handled by individual rules, so we know if there's a valid itemPrice and itemQty, the itemTotal is valid too.
It might be tricky to implement this exactly as-is, I'm using "itemTotal" as both a tag to specify a rule match and also a field name. And typing some of the input/output stuff might be non-trivial. Seems like something that could benefit from a specialized DSL.
John L.