Haskell applicator-style validator pooling

I have a good understanding of imperative programming, but now I am learning myself as Haskell for the great good.

I think I have a good theoretical understanding of Monads, Functors and Applications, but I need practice. And for practice, I sometimes bring some bits from my current work tasks.

And I get a little fixated on combining material with an applicative way.

First question

I have two functions to check:

import Prelude hiding (even) even :: Integer -> Maybe Integer even x = if rem x 2 == 0 then Just x else Nothing isSmall :: Integer -> Maybe Integer isSmall x = if x < 10 then Just x else Nothing 

Now I want validate :: Integer -> Maybe Integer built from even and isSmall

My best solution is

 validate a = isSmall a *> even a *> Just a 

And it does not indicate free

I can use a monad

 validate x = do even x isSmall x return x 

But why use Monad if (I suppose) all I need is applicative? (And he is still not accurate)

Is this a better (and better) way to do this?

Second question

Now I have two validators with different signatures:

 even = ... greater :: (Integer, Integer) -> Maybe (Integer, Integer) -- tuple second element should be greater than the first greater (a, b) = if a >= b then Nothing else Just (a, b) 

I need validate :: (Integer, Integer) -> Maybe (Integer, Integer) , which tries greater on the input tuple, and then even on the second element of the set.

And validate' :: (Integer, Integer) -> Maybe Integer with the same logic, but returning the second code element.

 validate (a, b) = greater (a, b) *> even b *> Just (a, b) validate' (a, b) = greater (a, b) *> even b *> Just b 

But I believe that the input tuple "flows" to greater , and then "flows" to some kind of snd and even , and then only the final element ends in the final Just .

What would a haskeller do?

+5
source share
2 answers

When you write form validators a -> Maybe b , you are more interested in this whole type than in the Maybe application. Type a -> Maybe b are the Claysley arrows of the Maybe monad. You can make some tools to help work with this type.

For the first question you can define

 (>*>) :: Applicative f => (t -> fa) -> (t -> fb) -> t -> fb (f >*> g) x = fx *> gx infixr 3 >*> 

and write

 validate = isSmall >*> even 

Your other examples:

 validate = even . snd >*> greater validate' = even . snd >*> fmap snd . greater 

They check the conditions in a different order. If you need an evaluation order, you can define another function <*< .

ReaderT

If you use type a -> Maybe b lot, it might be worth creating a newtype for it so that you can add your own instances for what you want. newtype already exists; ReaderT and its instances are already doing what you want to do.

 newtype ReaderT rma = ReaderT { runReaderT :: r -> ma } 

When you use type r -> Maybe a as a validator to validate and convert a single input r , it is the same as ReaderT r Maybe . The Applicative instance for ReaderT combines the two of them together, applying both of their functions to the same input, and then combining them together with <*> :

 instance (Applicative m) => Applicative (ReaderT rm) where f <*> v = ReaderT $ \ r -> runReaderT fr <*> runReaderT vr ... 

ReaderT <*> almost exactly matches >*> from the first section, but it does not discard the first result. ReaderT *> exactly matches the >*> from the first section.

In ReaderT terms ReaderT your examples become

 import Control.Monad.Trans.ReaderT checkEven :: ReaderT Integer Maybe Integer checkEven = ReaderT $ \x -> if rem x 2 == 0 then Just x else Nothing checkSmall = ReaderT Integer Maybe Integer checkSmall = ReaderT $ \x -> if x < 10 then Just x else Nothing validate = checkSmall *> checkEven 

and

 checkGreater = ReaderT (Integer, Integer) Maybe (Integer, Integer) checkGreater = ReaderT $ \(a, b) = if a >= b then Nothing else Just (a, b) validate = checkGreater <* withReaderT snd checkEven validate' = snd <$> validate 

You use one of these ReaderT validators on the x value on runReaderT validate x

+6
source

You ask, why use Monad if all you need is applicative? I may ask - why use applicative if all you need is Monoid?

Everything you do is basically trying to use monoidal behavior / monoid, but trying to do it through the applicative interface. It seems like working with Int through their string representation (implementing + for strings "1" and "12" and working with strings instead of 1 and 12 and working with ints)

Note that you can get the Applicative instance from any Monoid instance, so finding a Monoid that can solve your problem is the same as finding an applicator that can.

 even :: Integer -> All even x = All (rem x 2 == 0) isSmall :: Integer -> All isSmall x = All (x < 10) greater :: (Integer, Integer) -> All greater (a, b) = All (b > a) 

To prove that they are the same, we can write the conversion functions back and forth:

 convertToMaybeFunc :: (a -> All) -> (a -> Maybe a) convertToMaybeFunc fx = guard (getAll (fx)) $> x -- assuming the resulting Just contains no new information convertFromMaybeFunc :: (a -> Maybe b) -> (a -> All) convertFromMaybeFunc fx = maybe (All False) (\_ -> All True) (fx) 

You can directly write your validate :

 validate :: Int -> All validate a = isSmall a <> even a 

But you can also write it in the style you need:

 validate :: Int -> All validate = isSmall <> even 

Want to label?

 validate :: Int -> All validate = execWriter $ do tell isSmall tell even tell (other validator) validate' :: (Int, Int) -> All validate' = execWriter $ do tell (isSmall . fst) tell (isSmall . snd) tell greater 

As you can see, each Monoid instance launches the Applicative / Monad instance (via Writer and tell ), which makes this a little convenient. You can come up with Writer / tell here how to "raise" a Monoid instance to a free Applicative / Monad instance.

In the end, you notice a pattern / abstraction useful, but it is really a monoid that you notice. You are fixated on working on this monoid through the applicative interface, somehow ... but it was easier to just work with the monoid directly.

Besides,

 validate :: Int -> All validate = mconcat [ isSmall , even , other validator ] 

perhaps comparable in clarity to the noteworthy version of w / Writer :)

+4
source

Source: https://habr.com/ru/post/1261555/


All Articles