marco
marco

Reputation: 826

Generic data constructor from Maybe arguments

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

Answers (1)

bheklilr
bheklilr

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 Applicatives? 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 Functors, 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

Related Questions