Thanks for all the suggestions .. Here's an example of a validation function with Reader Env ..

validateOpenDate :: ZonedTime -> Reader Env (Validation [String] ZonedTime)
validateOpenDate openDate = do
  now <- asks curTime
  if zonedTimeToUTC openDate > zonedTimeToUTC now
  then return $ Failure ["Account open date cannot be in the future"]
  else return $ Success openDate

I have a few such functions and I wire them together to form a smart constructor that builds an ADT ..

makeAccount :: String -> String -> ZonedTime -> IO (Validation [String] Account)
makeAccount no name openDate = do
  env <- Env <$> getZonedTime
  flip runReader env $ do
    validAccountNo       <- validateAccountNo no
    validAccountName     <- validateAccountName name
    validOpeningDate     <- validateOpenDate openDate
    return $ return $ Account <$> validAccountNo <*> validAccountName <*> validOpeningDate 

Does this look like an idiomatic implementation ? Is there any scope to make things better ? I compose applicatively as I need to accumulate all validation errors. And also Validation does not have a Monad ..

regards.

On Sat, Jan 4, 2020 at 9:20 PM Viktor Dukhovni <ietf-dane@dukhovni.org> wrote:
On Sat, Jan 04, 2020 at 08:28:50PM +0530, Debasish Ghosh wrote:

> I am trying to validate a bunch of inputs and found the package
> Data.Validation. I am also using Data.Time for date and time. I have a
> function like this.
>
>   validateOpenDate :: ZonedTime -> Validation [String] ZonedTime
>
> It's supposed to validate a date for which I need to fetch the current
> date and time. I can do that with getZonedTime :: IO ZonedTime. I want
> to check if the current time is less than the date time I pass to the
> function and return an appropriate Validation.

This function is not pure.  It needs an external input that affects its
output.  Therefore, the honest type of this function is either:

    -- | Validate date relative to current time
    validateOpenDate :: ZonedTime -> IO (Validation [String] ZonedTime)

or (with the caller doing the time lookup):

    -- | Validate date relative to supplied time
    validateOpenDate :: ZonedTime -> ZonedTime -> Validation [String] ZonedTime

The "supplied time could be generalized to an "Environment" that carries
whatever context information might be needed:

    type Env = Env { curTime :: !ZonedTime }

With all the validation functions now:

    validateOpenDate :: ZonedTime -> Reader Env (Validation [String] ZonedTime)
    validateOpenData inputDate = do
        now <- asks curTime
        if inputDate > now
        then ...
        else ...

and then

    import Control.Monad.Reader

    main :: IO ()
    main = do
        env <- Env <$> getZonedTime
        flip runReader env $ do
            -- computations doing validation relative to the environment

> The question is how do I lift the result from IO into the Validation?

The only way to do that without changing the output type is to lie, and
use "unsafePerformIO" to extract the date inside a pure function.  This
is not recommended.

> Or is there any alternative strategy that I should consider for this
> validation ? I have a number of other validations which follow the
> same strategy but don't have the IO. My final goal is to be able to
> compose all these validations applicatively to prepare a final ADT in
> a smart constructor.

You should be able "compose" the validations in the context of an
environment.  Either Monadically, or Applicatively.

--
    Viktor.
_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.


--
Debasish Ghosh