Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Yesod Applicative Forms

Tags:

haskell

yesod

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

like image 309
Matthew H Avatar asked Apr 08 '13 00:04

Matthew H


1 Answers

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.

Applicative Functors in general

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)

Forms as applicative functors

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.

So
areq 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.

So what's the deal with <$> 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.)

like image 187
AndrewC Avatar answered Oct 20 '22 17:10

AndrewC