1

It seems to me that an idiomatic way to validate input data in Haskell is via an applicative chain:

mkMyData :: a -> b -> c -> Maybe MyData
mkMyData x y z =
    MyData
        <$> validateA x
        <*> validateB y
        <*> validateC z

where the validation functions themselves return Maybe values. To make my smart constructor mkMyData more flexible, I would like it to return MonadThrow. That is,

mkMyData :: MonadThrow m => a -> b -> c -> m MyData

Does this require each of the validation functions to return MonadThrow instead of Maybe? Or is there some way to convert the specific Maybe result of each validation into the more general MonadThrow without breaking up the applicative structure and greatly complicating the code?

Or maybe put differently? Is it worthwhile to strive for the more general MonadThrow return type in basic library functions, at the expense of more complex, less idiomatic code?

Ulrich Schuster
  • 1,670
  • 15
  • 24
  • 2
    My gut feeling is that that would be premature generalisation. On the one hand, in the general case you don't know what you want to throw. On the other hand, if you happen to know what you want to throw in a concrete situation you can reimplement `mkMyData` using a different `MonadThrow` instance (see also [amalloy's answer](https://stackoverflow.com/a/62363846/2751851)), or perhaps slip in a `maybe (throwM myErr) return` somewhere appropriate. – duplode Jun 13 '20 at 18:20

1 Answers1

2

The answer to this is the same as your last question. The type you propose for your new validation function,

mkMyData :: MonadThrow m => a -> b -> c -> m MyData

means that it is able to work in any monad at all, so long as that monad has a way to throw things. If the implementation of that function relies on being able to return Nothing or Just results explicitly, then it will not satisfy that condition.

Instead, you must rewrite the functions that currently return Maybe a to rely on MonadThrow instead. For example, instead of

validateA :: a -> Maybe t
validateA x | acceptable x = Just $ convert x
            | otherwise = Nothing

you will need to write

validateA :: MonadThrow m => a -> m t
validateA x | acceptable x = pure $ convert x
            | otherwise = throwM $ problemWith x

(where all the functions taking x as an argument are made up, needing to be related to your domain somehow).

amalloy
  • 89,153
  • 8
  • 140
  • 205
  • Thanks for the insight. I take away to things: For one, the MonadThrow style of error handling is not (yet?) as pervasive; second, there is no adapter function, I would need to write it myself. So it is probably best to not adopt it as default. – Ulrich Schuster Jun 13 '20 at 20:29