Reputation: 826
I have a bunch of data structures like data Foo = Foo {a :: Type1, b :: Type2} deriving (Something)
with Type1
and Type2
being always different (generally primitive types but it's irrelevant) and in different numbers.
I came to have a bunch of functions like
justFooify :: Maybe Type1 -> Maybe Type2 -> Maybe Foo
justFooify f b =
| isNothing f = Nothing
| isNothing b = Nothing
| otherwise = Just $ Foo (fromJust f) (fromJust b)
Is there something I'm missing? After the third such function I wrote I came to think that maybe it could be too much.
Upvotes: 3
Views: 218
Reputation: 54058
You need applicatives!
import Control.Applicative
justFooify :: Maybe Type1 -> Maybe Type2 -> Maybe Foo
justFooify f b = Foo <$> f <*> b
Or you can use liftA2
in this example:
justFooify = liftA2 Foo
It acts like liftM2
, but for Applicative
. If you have more parameters, just use more <*>
s:
data Test = Test String Int Double String deriving (Eq, Show)
buildTest :: Maybe String -> Maybe Int -> Maybe Double -> Maybe String -> Maybe Test
buildTest s1 i d s2 = Test <$> s1 <*> i <*> d <*> s2
What are Applicative
s? They're basically a more powerful Functor
and a less powerful Monad
, they fall right in between. The definition of the typeclass is
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
-- plus a few more things that aren't important right now
If your Applicative
is also a Monad
, then pure
is the same as return
(in fact, some people feel that having return
is incorrect, we should only have pure
). The <*>
operator is what makes them more powerful than Functor
s, though. It gives you a way to put a function in your data structure, then apply that function to values also wrapped in your data structure. So when you have something like
> :t Test -- Our construct
Test :: String -> Int -> Double -> String -> Test
> :t fmap Test -- also (Test <$>), since (<$>) = fmap
fmap Test :: Functor f => f String -> f (Int -> Double -> String -> Test)
We see that it constructs a function inside of a Functor
, since Test
takes multiple arguments. So Test <$> Just "a"
has the type Maybe (Int -> Double -> String -> Test)
. With just Functor
and fmap
, we can't apply anything to the inside of that Maybe
, but with <*>
we can. Each application of <*>
applies one argument to that inner Functor
, which should now be considered an Applicative
.
Another handy thing about it is that it works with all Monads (that currently define their Applicative
instance). This means lists, IO
, functions of one argument, Either e
, parsers, and more. For example, if you were getting input from the user to build a Test
:
askString :: IO String
askInt :: IO Int
askDouble :: IO Double
-- whatever you might put here to prompt for it, or maybe it's read from disk, etc
askForTest :: IO Test
askForTest = Test <$> askString <*> askInt <*> askDouble <*> askString
And it'd still work. This is the power of Applicatives.
FYI, in GHC 7.10 there will be implemented the Functor-Applicative-Monad Proposal. This will change the definition of Monad
from
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
to
class Applicative m => Monad m where
return :: a -> m a
return = pure
(>>=) :: m a -> (a -> m b) -> m b
join :: m (m a) -> m a
(more or less). This will break some old code, but many people are excited for it as it will mean that all Monads are Applicatives and all Applicatives are Functors, and we'll have the full power of these algebraic objects at our disposal.
Upvotes: 9