Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple explanation of <$> and <*> operators

Tags:

haskell

yesod

I have to give a (simple) talk about Yesod. And yes,.. i've never or really really rarely used haskell as well. University lecturer.....huh.

So i read a book about yesod and in some chapters the author is using some operators like <$> and <*>. Can someone explain in easy words, what this operators do? Its pretty hard to google for that chars and if tried to read the documentation of Control.Applicative but to be honest, its hard to get for an haskell beginner.

so i hope anyone have a simple answer for me :)

an example of the book where these operators are used:

......
personForm :: Html -> MForm Handler (FormResult Person, Widget)
personForm = renderDivs $ Person
    <$> areq textField "Name" Nothing
    <*> areq (jqueryDayField def
        { jdsChangeYear = True -- give a year dropdown
        , jdsYearRange = "1900:-5" -- 1900 till five years ago
        }) "Birthday" Nothing
    <*> aopt textField "Favorite color" Nothing
    <*> areq emailField "Email address" Nothing
    <*> aopt urlField "Website" Nothing
data Person = Person
    { personName          :: Text
    , personBirthday      :: Day
    , personFavoriteColor :: Maybe Text
    , personEmail         :: Text
    , personWebsite       :: Maybe Text
    }
  deriving Show
.....

.....................................

Hey,

Thanks a lot and amazingly most of the answers are useful. Sadly a only can hit "solved" on one answer. Thanks a lot, the tutorial (that I really didn't find on Google) is pretty good

like image 675
Smoki Avatar asked Nov 28 '22 15:11

Smoki


2 Answers

I am always very careful when making answers that are made up mostly of links, but this is one amazing tutorial that explains Functors, Applicatives and gives a bit of understaning on Monads.

like image 192
Bartek Banachewicz Avatar answered Dec 01 '22 04:12

Bartek Banachewicz


The simplest answer is the type, of course. These operators come from type typeclasses Functor and its subclass Applicative.

class Functor f where
  fmap :: (a -> b) -> (f a -> f b)

(<$>) = fmap -- synonym

class Functor f => Applicative f where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b

The simplest intuitive answer is that Functors and Applicatives let you annotate simple values with "metadata" and (<$>), (<*>), and friends let you transform your "regular" value-level functions to work on "annotated" values.

go x y                    -- works if x and y are regular values
go <$> pure x <*> pure y  -- uses `pure` to add "default" metadata
                          -- but is otherwise identical to the last one

Like any simple answer, it's kind of a lie, though. "Metadata" is a very oversimplified term. Better ones are "computational context" or "effect context" or "container".

If you're familiar with Monads then you are already very familiar with this concept. All Monads are Applicatives and so you can think of (<$>) and (<*>) as providing an alternative syntax for some do notation

do x_val <- x                     go <$> x
   y_val <- y                        <*> y
   return (go x_val y_val)

It has fewer symbols and emphasizes the idea of "applying" go to two arguments instead of emphasizing the imperative notion of "get the value that x generates, then get the value that y generates, then apply those values to go, then re-wrap the result" like do syntax does.


One final intuition I can throw out there is to think of Applicative in a very different way. Applicative is equivalent to another class called Monoidal.

class Functor f => Monoidal f where
  init :: f ()                             -- similar to pure
  prod :: f a -> f b -> f (a, b)           -- similar to (<*>)

so that Monoidal Functors let you (a) instantiate them with a trivial value

  init :: [()]
  init = []

  init :: Maybe ()
  init = Just ()

and also smash two of them together to produce their product

  prod :: [a] -> [b] -> [(a, b)]
  prod as bs = [(a, b) | a <- as, b <- bs]

  prod :: Maybe a -> Maybe b -> Maybe (a, b)
  prod (Just a) (Just b) = (Just (a, b))
  prod _        _        = Nothing

This means that with a Monoidal functor you can smash a whole lot of values together and then fmap a value-level function over the whole bunch

go <$> maybeInt `prod` (maybeChar `prod` maybeBool) where
  go :: (Int, (Char, Bool)) -> Double                       -- it's pure!
  go (i, (c, b)) = ...

which is essentially what you're doing with (<$>) and (<*>), just with fewer tuples

go <$> maybeInt <*> maybeChar <*> maybeBool where
  go :: Int -> Char -> Bool -> Double
  go i c b = ...

Finally, here's how you convert between the two notions

-- forward
init     = pure ()
prod x y = (,) <$> x <*> y

-- back
pure a   = const a <$> init
f <*> x  = ($) <$> prod f x

which shows how you can think of (<*>) as taking a normal value-level application ($) and injecting it up into the product inside of the Functor.

like image 27
J. Abrahamson Avatar answered Dec 01 '22 05:12

J. Abrahamson