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
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.
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 Applicative
s 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 Monad
s then you are already very familiar with this concept. All Monad
s are Applicative
s 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
Functor
s 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 prod
uct inside of the Functor
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With