Skip to content

Commit

Permalink
[#24] Extra interface functions (#45)
Browse files Browse the repository at this point in the history
* [#24] Extra interface functions

Resolves #24

* Rename vars

* Add hs-boot for Validation to break cyclic deps
  • Loading branch information
vrom911 authored May 5, 2020
1 parent 9e45fe6 commit 8dc86be
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ The changelog is available [on GitHub][2].
* [#41](https://github.com/kowainik/relude/issues/41):
Support GHC-8.10.1.
(by [@chshersh](https://github.com/chshersh))
* [#24](https://github.com/kowainik/relude/issues/24):
Add `validationAll`, `when*` and `maybe*` combinators into
`Validation.Combinators`.
(by [@vrom911](https://github.com/vrom911))

## 0.0.0.0

Expand Down
21 changes: 21 additions & 0 deletions src/Validation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,25 @@ module Validation
-- $either
, validationToEither
, eitherToValidation

-- * Combinators
, validateAll

-- ** When* functions
, whenSuccess
, whenFailure
, whenSuccess_
, whenFailure_
, whenSuccessM
, whenFailureM
, whenSuccessM_
, whenFailureM_

-- ** 'Maybe' conversion
, failureToMaybe
, successToMaybe
, maybeToFailure
, maybeToSuccess
) where

import Control.Applicative (Alternative (..), Applicative (..))
Expand All @@ -97,6 +116,8 @@ import Data.List.NonEmpty (NonEmpty (..))
import GHC.Generics (Generic, Generic1)
import GHC.TypeLits (ErrorMessage (..), TypeError)

import Validation.Combinators


-- $setup
-- >>> import Control.Applicative (liftA3)
Expand Down
13 changes: 13 additions & 0 deletions src/Validation.hs-boot
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Validation
( Validation (..)
, validation
) where


data Validation e a
= Failure e
| Success a

instance (Semigroup e) => Applicative (Validation e)

validation :: (e -> x) -> (a -> x) -> Validation e a -> x
216 changes: 216 additions & 0 deletions src/Validation/Combinators.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
{- |
Copyright: (c) 2020 Kowainik
SPDX-License-Identifier: MPL-2.0
Maintainer: Kowainik <[email protected]>
Helpful combinators to work with 'Validation' data type.
-}

module Validation.Combinators
( validateAll

-- * When* functions
, whenSuccess
, whenFailure
, whenSuccess_
, whenFailure_
, whenSuccessM
, whenFailureM
, whenSuccessM_
, whenFailureM_

-- * 'Maybe' conversion
, failureToMaybe
, successToMaybe
, maybeToFailure
, maybeToSuccess

) where

import Data.Foldable (foldl')

import {-# SOURCE #-} Validation (Validation (..), validation)


{- | Validate all given checks in a 'Foldable'. Returns the 'Success' of the
start element when all checks are successful.
A basic example of usage could look like this:
@
> let validatePassword = validateAll
[ validateEmptyPassword
, validateShortPassword
]
> validateAll "VeryStrongPassword"
Success "VeryStrongPassword"
> validateAll ""
Failure (EmptyPassword :| [ShortPassword])
@
-}
validateAll
:: forall e b a f
. (Foldable f, Semigroup e)
=> f (a -> Validation e b)
-> a
-> Validation e a
validateAll fs a = foldl' (\res f -> res <* f a) (Success a) fs
{-# INLINE validateAll #-}

{- | Applies the given action to 'Validation' if it is 'Failure' and returns the
result. In case of 'Success' the default value is returned.
>>> whenFailure "bar" (Failure 42) (\a -> "foo" <$ print a)
42
"foo"
>>> whenFailure "bar" (Success 42) (\a -> "foo" <$ print a)
"bar"
-}
whenFailure :: Applicative f => x -> Validation e a -> (e -> f x) -> f x
whenFailure _ (Failure e) f = f e
whenFailure a (Success _) _ = pure a
{-# INLINE whenFailure #-}

{- | Applies given action to the 'Validation' content if it is 'Failure'.
Similar to 'whenFailure' but the default value is '()'.
>>> whenFailure_ (Success 42) putStrLn
>>> whenFailure_ (Failure "foo") putStrLn
foo
-}
whenFailure_ :: Applicative f => Validation e a -> (e -> f ()) -> f ()
whenFailure_ = whenFailure ()
{-# INLINE whenFailure_ #-}

{- | Monadic version of 'whenFailure'.
Applies monadic action to the given 'Validation' in case of 'Failure'.
Returns the resulting value, or provided default.
>>> whenFailureM "bar" (pure $ Failure 42) (\a -> "foo" <$ print a)
42
"foo"
>>> whenFailureM "bar" (pure $ Success 42) (\a -> "foo" <$ print a)
"bar"
-}
whenFailureM :: Monad m => x -> m (Validation e a) -> (e -> m x) -> m x
whenFailureM x mv f = mv >>= \v -> whenFailure x v f
{-# INLINE whenFailureM #-}

{- | Monadic version of 'whenFailure_'.
Applies monadic action to the given 'Validation' in case of 'Failure'.
Similar to 'whenFailureM' but the default is '()'.
>>> whenFailureM_ (pure $ Success 42) putStrLn
>>> whenFailureM_ (pure $ Failure "foo") putStrLn
foo
-}
whenFailureM_ :: Monad m => m (Validation e a) -> (e -> m ()) -> m ()
whenFailureM_ mv f = mv >>= \v -> whenFailure_ v f
{-# INLINE whenFailureM_ #-}

{- | Applies the given action to 'Validation' if it is 'Success' and returns the
result. In case of 'Failure' the default value is returned.
>>> whenSuccess "bar" (Failure "foo") (\a -> "success!" <$ print a)
"bar"
>>> whenSuccess "bar" (Success 42) (\a -> "success!" <$ print a)
42
"success!"
-}
whenSuccess :: Applicative f => x -> Validation e a -> (a -> f x) -> f x
whenSuccess x (Failure _) _ = pure x
whenSuccess _ (Success a) f = f a
{-# INLINE whenSuccess #-}

{- | Applies given action to the 'Validation' content if it is 'Success'.
Similar to 'whenSuccess' but the default value is '()'.
>>> whenSuccess_ (Failure "foo") print
>>> whenSuccess_ (Success 42) print
42
-}
whenSuccess_ :: Applicative f => Validation e a -> (a -> f ()) -> f ()
whenSuccess_ = whenSuccess ()
{-# INLINE whenSuccess_ #-}

{- | Monadic version of 'whenSuccess'.
Applies monadic action to the given 'Validation' in case of 'Success'.
Returns the resulting value, or provided default.
>>> whenSuccessM "bar" (pure $ Failure "foo") (\a -> "success!" <$ print a)
"bar"
>>> whenSuccessM "bar" (pure $ Success 42) (\a -> "success!" <$ print a)
42
"success!"
-}
whenSuccessM :: Monad m => x -> m (Validation e a) -> (a -> m x) -> m x
whenSuccessM x mv f = mv >>= \v -> whenSuccess x v f
{-# INLINE whenSuccessM #-}

{- | Monadic version of 'whenSuccess_'.
Applies monadic action to the given 'Validation' in case of 'Success'.
Similar to 'whenSuccessM' but the default is '()'.
>>> whenSuccessM_ (pure $ Failure "foo") print
>>> whenSuccessM_ (pure $ Success 42) print
42
-}
whenSuccessM_ :: Monad m => m (Validation e a) -> (a -> m ()) -> m ()
whenSuccessM_ mv f = mv >>= \v -> whenSuccess_ v f
{-# INLINE whenSuccessM_ #-}


{- | Maps 'Failure' of 'Validation' to 'Just'.
>>> failureToMaybe (Failure True)
Just True
>>> failureToMaybe (Success "aba")
Nothing
-}
failureToMaybe :: Validation e a -> Maybe e
failureToMaybe = validation Just (const Nothing)
{-# INLINE failureToMaybe #-}

{- | Maps 'Success' of 'Validation' to 'Just'.
>>> successToMaybe (Failure True)
Nothing
>>> successToMaybe (Success "aba")
Just "aba"
-}
successToMaybe :: Validation e a -> Maybe a
successToMaybe = validation (const Nothing) Just
{-# INLINE successToMaybe #-}

{- | Maps 'Just' to 'Failure' In case of 'Nothing' it wraps the given default
value into 'Success'.
>>> maybeToFailure True (Just "aba")
Failure "aba"
>>> maybeToFailure True Nothing
Success True
-}
maybeToFailure :: a -> Maybe e -> Validation e a
maybeToFailure a = maybe (Success a) Failure
{-# INLINE maybeToFailure #-}

{- | Maps 'Just' to 'Success'. In case of 'Nothing' it wraps the given default
value into 'Failure'
>>> maybeToSuccess True (Just "aba")
Success "aba"
>>> maybeToSuccess True Nothing
Failure True
-}
maybeToSuccess :: e -> Maybe a -> Validation e a
maybeToSuccess e = maybe (Failure e) Success
{-# INLINE maybeToSuccess #-}
4 changes: 3 additions & 1 deletion test/Doctest.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ main = doctest
: "-XRecordWildCards"
: "-XScopedTypeVariables"
: "-XTypeApplications"
: [ "src/Validation.hs" ]
: [ "src/Validation.hs"
, "src/Validation/Combinators.hs"
]
1 change: 1 addition & 0 deletions validation-selective.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ library
import: common-options
hs-source-dirs: src
exposed-modules: Validation
Validation.Combinators
build-depends: deepseq ^>= 1.4.3.0
, selective >= 0.3 && < 0.5

Expand Down

0 comments on commit 8dc86be

Please sign in to comment.