Code Monkey home page Code Monkey logo

validation-selective's Introduction

validation-selective

Logo GitHub CI

Hackage MPL-2.0 license

Lightweight pure data validation based on Applicative and Selective functors.

validation-selective is built around the following data type:

data Validation e a
    = Failure e
    | Success a

This data type is similar to Either but allows accumulating all errors instead of short-circuiting on the first one.

For more examples and library tutorial, refer to Haddock:

Comparison with other packages

validation-selective is not the only package that provides such Validation data type. However, unlike other packages, it has some noticeable advantages:

  • Lightweight. validation-selective depends only on base and selective (which is tiny) Haskell libraries which make this package fast to build. So adding validation capabilities to your library or application doesn't contribute much to your dependency footprint.
  • Selective instance. validation-selective is the only package that provides Selective instance for Validation which allows using Monad-like branching behaviour but without implementing wrong Monad instance.
  • More algebraic instances. validation-selective also provides the Alternative instance and a more general Semigroup instance.
  • Best-in-class documentation. Official Haddock documentation contains mini-tutorial, usage example, per-component comparison with Either, the motivation behind each instance and the interface in general along with examples for each instance and function.

The below section provides per-package comparison with the most popular validation packages in the Haskell ecosystem:

  • either: Validation implementation by Edward Kmett. This package is more heavyweight, since it depends on more Haskell libraries like profunctors, bifunctors, semigroupoids. But it also provides prisms for Validation and some combinators for Either.
  • validation: Validation from Queensland Functional Programming Lab. Depends on lens, which makes it even heavier but also have richer interface compared to the either package.

How to use

validation-selective is compatible with the latest GHC compiler versions starting from 8.6.

In order to start using validation-selective in your project, you will need to set it up with the three easy steps:

  1. Add the dependency on validation-selective in your project's .cabal file. For this, you should modify the build-depends section by adding the name of this library. After the adjustment, this section could look like this:

    build-depends: base ^>= 4.14
                 , validation-selective ^>= 0.0
  2. In the module where you wish to implement pure data validation, you should add the import:

    import Validation (Validation (..))
  3. Now you can use the types and functions from the library:

    main :: IO ()
    main = print [Failure "wrong", Success 42]

Usage with Stack

If validation-selective is not available on your current Stackage resolver yet, fear not! You can still use it from Hackage by adding the following to the extra-deps section of your stack.yaml file:

extra-deps:
  - validation-selective-CURRENT_VERSION

validation-selective's People

Contributors

chshersh avatar dependabot[bot] avatar felixonmars avatar jaspa avatar kleidukos avatar mm1995tk avatar philderbeast avatar tomjaguarpaw avatar vrom911 avatar wbadart avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

validation-selective's Issues

Write readme

  • A few words about this package
  • Example data type and very small usage example
  • Comparison to (some) other packages
  • How to use this package (dependency in the .cabal file, add import, etc.)

Does not compile with ghc-9.6

This package depends on selective < 0.7 and that does not compile with ghc-9.6.

I am currently getting around this in my cabal.project file using:

if impl(ghc >= 9.6)
  allow-newer:
    , validation-selective:selective

to allow the use of selective == 0.7.

Add a guarded combinator for validations that return the value itself

Often, when using validation-selective, you write code like:

validateUser user = user <$ failureIf (someUserCheck user) SomeError

We can probably introduce one more helpful combinator to write domain checks even easier, a function like this:

guardedWith :: (a -> Bool) -> e -> a -> Validation (NonEmpty e) a
guardedWith p e a = a <$ failureIf (p a) e

And the previous code example will be rewritten like this:

validateUser = guardedWith someUserCheck SomeError

An open question is how to name this function ๐Ÿ™‚ So any ideas are welcome!

Improve Haddock documentation

A lot of great suggestions by @vrom911 in this PR: #6

Specifically:

  • Better documentation for the Validation data type
  • Rewrite example with Computer: probably a completely different example where validation makes more sense is required, like a form with user name and list of some non-empty strings or something different. And probably it's better to use newtypes.
  • Add better docs to each instance with doctest examples
  • Make each instance description more high-level

Introduce some combinators to work with 'Validation (NonEmpty e)'

When using Validation, we often work with the NonEmpty list of errors, and those lists will be concatenated later. I propose to introduce two functions to help with writing more concise code. I'm not sure about names, this is the most difficult part here, so any suggestions are appreciated:

failure :: e -> Validation (NonEmpty e) a
failureIf :: Bool -> e -> Validation (NonEmpty e) ()
failureUnless :: Bool -> e -> Validation (NonEmpty e) ()  -- equal to 'failureIf (non p)'

So instead of:

validateName :: String -> Validation (NonEmpty FormValidationError) UserName
validateName name
    | null name = Failure (EmptyName :| [])
    | otherwise = Success (UserName name)

we will write

validateName :: String -> Validation (NonEmpty FormValidationError) UserName
validateName name = UserName name <$ failureIf (null name) EmptyName

or even like this (with slightly different type):

validateName :: String -> Validation (NonEmpty FormValidationError) UserName
validateName name = UserName <$> failureIf null EmptyName name

What to do when I need a Monad

My case is this:

I want to type check a recursive expression and the type checking may depend on the type of the child expressions. I want to collect all the errors from all the children, but if one of my children has an error, there is no need to report another error for this node as well.

Basically I want the Either monad instance for the specific case, but the applicative of Validation for everything else. Or not even that. If this had the Either monad instance (which obviously won't happen), I would still have to write:

tpCheck :: Exp () -> Validation (Exp Tp)
tpCheck (BinaryExp op a b ()) = do
  (ta, tb) <- (,) <$> tpCheck a <*> tpCheck b
  BinaryExp op ta tb <$> tpCheckBinary op ta tb
  
 tpCheckBinary :: Op -> Exp Tp -> Exp Tp -> Validation Tp

which feels kind of indirect and silly, with the (,) <$> part. I wonder if there is some kind of operator that would allow me to do the whole thing more directly:

tpCheck (BinaryExp op a b ()) = do
  tpCheckBinary op <$!?> tpCheck a <*> tpCheck b
  
tpCheckBinary :: Op -> Exp Tp -> Exp Tp -> Validation (Exp Tp)

From my understanding, selective is more restrictive and wouldn't allow expressing this, since it really wants to collect errors from everything?

Maybe the answer is to just use another Validation library with a non-law abiding instance and accept the slight ugliness and the need to be careful? Or to manually convert it back and forth to Either?

Derive NFData instance

This will require to add deepseq to dependencies, but that's okay, it's a boot library. However, having NFData instance will help us and other people to write benchmarks including Validation. This will also require to derive Generic which is nice in general.

Some comments

Hello to both of you,
thanks for your work that helps a lot to understand Haskell concepts.

Not really a bug here, but some comments based on your tutorial, that are hopefully useful.

First, isn't it error prone to do this :

validateName name = UserName name <$ failureIf (null name) EmptyName

I see a risk of validating a value and building another value. I see <$ as a smell, and would rather use <$>.

Here, my intuition would be to have a helper validate like this :

validate :: (a -> Bool) -> e -> a -> Validation (NonEmpty e) a
validate p  e x = if p x then Success x else failure e

allowing me to write

validateName' :: String -> Validation (NonEmpty FormValidationError) UserName
validateName' name = UserName <$> validate (not . null) EmptyName name

Or maybe even

validate :: (a -> Bool) -> (a -> b) -> e -> a -> Validation (NonEmpty e) b
validate p c e x = if p x then Success (c x) else failure e

then

validateName :: String -> Validation (NonEmpty FormValidationError) UserName
validateName = validate (not . null) UserName EmptyName

I think it reads quite well, and also avoids building a wrong result.
One could also imagine some variants of validate, taking id or const () instead of the constructor

What do you think ?


Another thing I would like to comment is the validateAll function.

It is defined as

validateAll
    :: forall e b a f
    .  (Foldable f, Semigroup e)
    => f (a -> Validation e b)
    -> a
    -> Validation e a

We are throwing the b values produced by the validations, and I think the result type is surprising too.

I would rather expect, hoping that the validators all returns the same value :

validateAll
    :: forall e b a f
    .  (Foldable f, Semigroup e)
    => f (a -> Validation e b)
    -> a
    -> Validation e b

But then, what would b be when the foldable is empty ?

So if we really want to provide a validateAll function, I would rather do :

validateAll :: Semigroup e => NonEmpty (a -> Validation e b) -> a -> Validation e b
validateAll fs a = head <$> traverse ($ a) fs

which is only keeping the first produced value, so it might not be so nice.
Maybe validateAll is not needed, and *> would be clearer.

Here again, I would be happy to know your point of you.

Add to stackage?

Would the maintainers here be open to adding this library to stackage?

Support GHC-8.8.3

It should be on each CI (Travis, AppVeyor and GitHub actions), so shouldn't be a problem to support it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.