I’m writing a blog system using Yesod. I want to allow comments from users that are not logged in, but I don’t want millions of spam messages, so I thought I’d add ReCAPTCHA verification to the comment form. Here is the basic form:

To get a ReCAPTCHA field, we just append <* recaptchaAForm right at the end:

So how does the <* thing work? How are the two forms combined so that the ReCAPTCHA functionality works, and the result of the first form is the actual output?

For the sake of this blog post I’ll use a simpler form and datatype. So let’s define a Person type that just stores a name of type Text:

The form in Yesod just has a single required field (areq = Applicative REQuired) for the name:

(If you aren’t familiar with Yesod’s applicative forms, have a look at this very informative Stack Overflow question.)

Using ghci, let’s check the type of everything after renderDivs $: > :t Person areq textField "Name" Nothing Person areq textField "Name" Nothing :: RenderMessage master FormMessage => AForm sub master Person  All we are interested in is the final bit, namely AForm sub master Person. The sub and master refer to Yesod subsites, which we aren’t using here. So basically we can say that is of type AForm sub master Person, a form that returns a Person. If we tack a ReCAPTCHA form onto the end, the data type stays the same: > :t (Person areq textField "Name" Nothing) <* recaptchaAForm (Person areq textField "Name" Nothing) AForm sub master Person  So how does this work? First, we look up <* using :info on the ghci prompt: > :info ( Applicative f where ... ( f b -> f a -- Defined in Control.Applicative' infixl 4 <*  Looking at Control.Applicative we see that: Since Haskell respects equational reasoning, we can make substitutions as follows: (Person areq textField "Name" Nothing) liftA2 const (Person areq textField "Name" Nothing) recaptchaAForm --> const (Person areq textField "Name" Nothing) recaptchaAForm  and we can do a basic check on our working by verifying that the type of the final expression is still an AForm sub master Person: > :t const (Person areq textField "Name" Nothing) recaptchaAForm const (Person areq textField "Name" Nothing) recaptchaAForm :: YesodReCAPTCHA master => AForm sub master Person  All good. Now, is just short for fmap, so we can make another substitution: --> const (Person areq textField "Name" Nothing) recaptchaAForm --> fmap const (Person areq textField "Name" Nothing) recaptchaAForm --> (fmap const (Person areq textField "Name" Nothing)) recaptchaAForm  where the last line makes it clear that fmap takes two parameters, and this blob is the first argument to FIXME. Next we have to find the Applicative instance definition for an AForm, which is in Yesod.Form.Types. It’s a bit gnarly: The original question was how the result of the form composition is just a Person. Looking at the definition of AForm, we see the FormResult data type, and in the definition for (AForm f) (AForm g), the FormResult component is just a x where a and x are the FormResult components of the forms f and g, respectively. So what happens to the FormResult component when we use fmap const? We can get an idea by writing a small function that rips out the FormResult of an applicative form. By poking around in Yesod.Form.Functions we find aFormToForm which does the trick: So let’s try it out: *Handler.Home> :t getFormResult (Person areq textField "Name" Nothing) getFormResult (Person areq textField "Name" Nothing) :: RenderMessage master FormMessage => Control.Monad.Trans.RWS.Lazy.RWST (Maybe (Env, FileEnv), master, [Yesod.Form.Types.Lang]) Enctype Ints (GHandler sub master) (FormResult Person)  and with fmap const: *Handler.Home> :t getFormResult (fmap const ((Person areq textField "Name" Nothing))) getFormResult (fmap const ((Person areq textField "Name" Nothing))) :: RenderMessage master FormMessage => Control.Monad.Trans.RWS.Lazy.RWST (Maybe (Env, FileEnv), master, [Yesod.Form.Types.Lang]) Enctype Ints (GHandler sub master) (FormResult (b -> Person))  There’s a lot there, but the only change was that (FormResult Person) became (FormResult (b -> Person)). This is what we expect from the definition of fmap and const. The FormResult part becomes a function that just returns itself. So we can finally move on to the applicative definition for a FormResult which is in Yesod.Form.Types: 1. instance Applicative FormResult where 2. pure = FormSuccess 3. (FormSuccess f) (FormSuccess g) = FormSuccess$ f g
4.     (FormFailure x)  (FormFailure y) = FormFailure $x ++ y 5. (FormFailure x) _ = FormFailure x 6. _ (FormFailure y) = FormFailure y 7. _ _ = FormMissing  Line 3 is the case where the Person form was successful and the ReCAPTCHA form was also successful. In our case the left hand side might be fmap const (FormSuccess (Person "bob"))  and the right hand side, representing the ReCAPTCHA form, will be fmap const (FormSuccess ())  since a Yesod.Recaptcha form returns an empty result. So at the end, this is what happens to the result of the form: > (fmap const (FormSuccess (Person "bob"))) (FormSuccess ()) FormSuccess (Person {personName = "bob"})  We can reason our way to this result: > :t fmap const (FormSuccess (Person "bob")) fmap const (FormSuccess (Person "bob")) :: FormResult (b -> Person) *Handler.Home> :t FormSuccess () FormSuccess () :: FormResult () (fmap const (FormSuccess (Person "bob"))) (FormSuccess ()) --> FormSuccess$ (fmap const (Person "bob")) () -- using line 3.
--> FormSuccess \$ (Person "bob")                 -- defn of Functor on FormResult
--> FormSuccess (Person "bob")


Note: I have been a bit hand-wavey with the use of getFormResult but the behaviour does follow from the Functor instance definition for AForm. Similar for the Functor instance definition for FormResult`.