I have a good grasp on imperative programming, but now I learn myself a Haskell for great good.
I think, I have a good theoretical understanding of Monads, Functors and Applicatives, but I need some practice. And for practice I sometimes bring some bits from my current work tasks.
And I'm stuck a bit with combining stuff in applicative way
I have two functions for validation:
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's not point 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 an Applicative? (And it still not point free)
Is it a better (and more buitiful way) to do that?
Now I have two validators with different signatures:
even = ...
greater :: (Integer, Integer) -> Maybe (Integer, Integer)
-- tuple's 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 tuple's second element.
And validate' :: (Integer, Integer) -> Maybe Integer
with same logic, but returning tuple's second element.
validate (a, b) = greater (a, b) *> even b *> Just (a, b)
validate' (a, b) = greater (a, b) *> even b *> Just b
But I imagine that the input tuple "flows" into greater
, then "flows" into some kind of composition of snd
and even
and then only single element ends up in the final Just
.
What would a haskeller do?
When you are writing validators of the form a -> Maybe b
you are more interested in that whole type than in the Maybe
applicative. The type a -> Maybe b
are the Kleisli 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 -> f a) -> (t -> f b) -> t -> f b
(f >*> g) x = f x *> g x
infixr 3 >*>
and write
validate = isSmall >*> even
Your second examples are
validate = even . snd >*> greater
validate' = even . snd >*> fmap snd . greater
These check the conditions in a different order. If you care about evaluation order you can define another function <*<
.
If you use the type a -> Maybe b
a lot it might be worth creating a newtype
for it so that you can add your own instances for what you want it to do. The newtype
already exists; it's ReaderT
, and its instances already do what you want to do.
newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }
When you use the type r -> Maybe a
as a validator to validate and transform a single input r
it's the same as ReaderT r Maybe
. The Applicative
instance for ReaderT
combines two of them together by applying both of their functions to the same input and then combining them together with <*>
:
instance (Applicative m) => Applicative (ReaderT r m) where
f <*> v = ReaderT $ \ r -> runReaderT f r <*> runReaderT v r
...
ReaderT
's <*>
is almost exactly the same as >*>
from the first section, but it doesn't discard the first result. ReaderT
's *>
is exactly the same as >*>
from the first section.
In terms of 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 a value x
by runReaderT validate x