I've been playing around with Yesod a bit, and a question arose: how are forms used as Applicatives?
Take:
personForm :: Html -> MForm Synopsis Synopsis (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
I don't understand how the <*>
operator, which as I understood it takes something of type f (a -> b)
(i.e. a functor containing a binary function) as its first argument, is being applied to AForm
values:
(AForm f) <*> (AForm g) = AForm $ \mr env ints -> do ...
What's going on here?
https://github.com/yesodweb/yesod/blob/bf293e6a1f6e691281520d254f72b9441cc64704/yesod-form/Yesod/Form/Types.hs#L95
It helps here if you try to ignore the detailed types a bit and look at the big picture.
Think of <$>
and <*>
as special versions of $
that work on a different level.
Let's look in general first - suppose I had an applicative functor AF
, and objects
x :: AF a
y :: AF b
z :: AF c
sort of meaning they "do something" then return values of types a
, b
and c
, and also a pure function
f :: a -> b -> c -> d
that I wanted to use to combine those values together to get a d
. Then
f <$> x <*> y <*> z :: AF d
works by "doing" x
, then y
then z
, and applying f
to the result.
Notice that that's similar to f $ a $ b $ c
.
If your applicative functor agrees with a monad instance, f <$> x <*> y <*> z
is a nice way of writing
do
a <- x
b <- y
c <- z
return (f a b c)
Think of a form as something that produces data (from the user). areq
and aopt
both return an AForm sub master ??
. You can ignore the sub
and master
- they're for keeping track of site/subsite using the type system. The ??
is the type of data that's returned.
Soareq textField "Name" Nothing
is a small form that produces Text
,areq (jqueryDayField def) "Birthday" Nothing
is a small form that produces a Day
,
and the other three also produce Text
.
Now we have
data Person = Person Text Day Text Text Text
so that Person
is a function :: Text -> Day -> Text -> Text -> Text -> Person
, so if you have
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
you've combined all those forms into one form that produces a single Person
value, by getting all the individual values of the fields and applying the pure Person
function to them.
To write that in monadic style you'd write
do
name <- areq textField "Name" Nothing
day <- areq (jqueryDayField def
{ jdsChangeYear = True -- give a year dropdown
, jdsYearRange = "1900:-5" -- 1900 till five years ago
}) "Birthday" Nothing
color <- aopt textField "Favorite color" Nothing
email <- areq emailField "Email address" Nothing
website <- aopt urlField "Website" Nothing
return $ Person name day color email website
I very much prefer the applicative functor style, because it feels like applying a function to some data instead of performing a sequence of instructions.
<$>
versus <*>
?You may have noticed I always do
pureFunction <$> af1 <*> af2 <*> af3 <*> af4 ....
with <$>
first, and <*>
for the rest. That's because the first thing, pureFunction
is pure and doesn't work on an applicative functor value. It lifts it. (All applicative functors are functors.) Let's compare the types:
<$> :: Functor f => (a -> b) -> f a -> f b
<*> :: Applicative f => f (a -> b) -> f a -> f b
which means that <$>
is for lifting a pure function, but when you're using <*>
the left hand side has to already be producing functions instead of just data, which initially seems weird, but not if you use <$>
first. For example, if you partially apply (++)
to "Hello"
you get a function :: String -> String
, so
getLine :: IO String -- produces a String
(++) <$> getLine :: IO (String -> String) -- produces an appender
(++) <$> getLine <*> getLine :: IO String -- produces a combined String
Compare that with
areq textField "Name" Nothing
:: AForm sub master Text
Since Person :: Text -> Day -> Text -> Text -> Text -> Person
, if we give it a name::Text
value, we get a partially applied function Person name
of type Day -> Text -> Text -> Text -> Person
Person <$> areq textField "Name" Nothing
:: AForm sub master (Day -> Text -> Text -> Text -> Person)
which we can than combine using <*>
with something that produces a Day
to give something that produces a (Text -> Text -> Text -> Person)
, and so on until we get something that produces a Person
. (It all works because ->
associates to the right and <$>
and <*>
associate to the left, like $
does.)
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