> required :: String -> Either AppError String
> required [] = Left Required
> required str = Right str
>
In this case we are simply checking that the `String` is not null. If it is null we return an error, otherwise we return the `String` unmodified. Some validators will actually transform the value -- such as converting the `String` to an `Integer`.
To apply this validation function we can use `transformEither`:
] transformEither :: Monad m =>
] Form m input error view anyProof a
] -> (a -> Either error b)
] -> Form m input error view () b
We can update our `Form` to:
> validPostForm :: SimpleForm Message
> validPostForm =
> Message <$> name <*> title <*> msg <* inputSubmit "post"
> where
> name = errorList ++> label "name:" ++>
> (inputText "" `transformEither` required) <++ br
>
> title = errorList ++> label "title:" ++>
> (inputText "" `transformEither` required) <++ br
>
> msg = errorList ++> (label "message:" <++ br) ++>
> (textarea 80 40 "" `transformEither` required) <++ br
>
The `errorList` will add a list of error messages to a `Form`
element. This gives greater control over where error messages appear
in the form. The list of errors is literally a list of errors inside
a `` tag:
You can use CSS to control the theming.
For even greater control we could use the `Text.Reform.Generalized.errors` function:
] errors :: Monad m =>
] ([error] -> view) -- ^ function to convert the error messages into a view
] -> Form m input error view () ()
This allows you to provide your own custom view code for rendering the errors.
We can wrap up the `validForm` the exact same way we did `postForm`:
> validPage :: AppT IO Response
> validPage =
> dir "valid" $
> appTemplate "valid post" () $
> <% reform (form "/valid") "valid" displayMessage Nothing validPostForm %>
> where
> displayMessage msg = appTemplate "Your Message" () $ renderMessage msg
>
A few names have been changed, but everything else is exactly the same.
One of the primary motivations behind the changes in
`digestive-functors 0.3` is allowing developers to separate the
validation code from the code which generates the view. We can do this
using `reform` as well -- in a manner that is both more flexible and
which provides greater type safety. The key is the `proof` parameter
-- which we have so far set to `()` and otherwise ignored.
In `reform` we divide the work into two pieces:
1. `Proofs`
2. a `Form` that returns a `Proved` value
This allows the library authors to create `Proofs` and demand that a `Form` created by another developer satisfies the `Proof`. At the same time, it gives the developer unrestricted control over the layout of the `Form` -- including choice of templating library.
Let's create a new type alias for `Form` that allows us to actually set the `proof` parameter:
> type ProofForm proof = Form IO [Input] AppError [AppT IO (XMLType (ServerPartT IO))] proof
>
First we will explore the `Proof` related code that would go into a library.
The `proof` parameter for a `Form` is used to indicate that something has been proven about the form's return value.
Two create a `proof` we need two things:
1. a type which names the proof
2. a function which performs the proof
We wrap those two pieces up into a `Proof`:
] data Proof m error proof a b
] = Proof { proofName :: proof -- ^ name of the thing to prove
] , proofFunction :: a -> m (Either error b) -- ^ function which provides the proof
] }
In `validPostForm`, we checked that the input fields were not empty
`Strings`. We could turn that check into a proof by first creating a
type to name that proof:
> data NotNull = NotNull
>
and then creating a proof function like this:
> assertNotNull :: (Monad m) => error -> [a] -> m (Either error [a])
> assertNotNull errorMsg [] = return (Left errorMsg)
> assertNotNull _ xs = return (Right xs)
>
We can then wrap the two pieces up into a proof:
> notNullProof :: (Monad m) =>
> error -- ^ error to return if list is empty
> -> Proof m error NotNull [a] [a]
> notNullProof errorMsg =
> Proof { proofName = NotNull
> , proofFunction = assertNotNull errorMsg
> }
>
We can also create proofs that combine existing proofs. For example, a `Message` is only valid if all its fields are not null. So, first thing we want to do is create a proof name for valid messages:
> data ValidMessage = ValidMessage
>
The `Message` constructor has the type:
] Message :: String -> String -> String -> Message
For `SimpleForm` we would use `pure` to turn `Message` into a `SimpleForm`:
] mkSimpleMessage :: SimpleForm (String -> String -> String -> Message)
] mkSimpleMessage = pure Message
For `ProofForm`, we can do the same thing use `ipure`:
> mkMessage :: ProofForm (NotNull -> NotNull -> NotNull -> ValidMessage)
> (String -> String -> String -> Message)
> mkMessage = ipure (\NotNull NotNull NotNull -> ValidMessage) Message
>
This creates a chain of validation since `mkMessage` can only be applied to `String` values that have been proven `NotNull`.
The library author can now specify that the user supplied `Form` has the type:
] someFunc :: ProofForm ValidMessage Message -> ...
You will notice that what we have constructed so far has imposes no restrictions on what types of form elements can be used, what template library must be used, or what web server must be used. At the same time, in order for the library user to create a `ProofForm` with the required type, they must apply the supplied validators. Now, clearly a devious library user could use evil tricks to circumvent the system -- and they will get what they deserve.
To construct the `Form`, we use a pattern very similar to what we did when using `SimpleForm`. They only real differences are:
1. we use `prove` instead of `transformEither`
2. we use `<<*>>` instead of `<*>`
To apply a `Proof` we use the `prove` function:
] prove :: (Monad m) =>
] Form m input error view q a
] -> Proof m error proof a b
] -> Form m input error view proof b
So, we can make a `ProofForms` for non-empty `Strings` like this:
> inputText' :: String -> ProofForm NotNull String
> inputText' initialValue = inputText initialValue `prove` (notNullProof Required)
>
> textarea' :: Int -> Int -> String -> ProofForm NotNull String
> textarea' cols rows initialValue =
> textarea cols rows initialValue `prove` (notNullProof Required)
>
to create the `ValidMessage` form we can then combine the pieces like:
> provenPostForm :: ProofForm ValidMessage Message
> provenPostForm =
> mkMessage <<*>> errorList ++> label "name: " ++> inputText' ""
> <<*>> errorList ++> label "title: " ++> inputText' ""
> <<*>> errorList ++> label "message: " ++> textarea' 80 40 ""
>
This code looks quite similar to our `validPostForm` code. The primary
difference is that we use `<<*>>` instead of `<*>`. That brings is to the topic of type-indexed applicative functors.
Lets look at the type for `Form` again:
] newtype Form m input error view proof a = Form { ... }
In order to make an `Applicative` instance of `Form`, all the proof type variables must be the same type and must form a `Monoid`:
] instance (Functor m, Monad m, Monoid view, Monoid proof) => (Form m input error view proof)
for `SimpleForm` we used the following instance, which is defined for us already in `reform`:
] instance (Functor m, Monoid view, Monad m) => Applicative (Form m input error view ())
With this instance, `reform` feels and works almost exactly like `digestive-functors <= 0.2`.
But, for the `provePostForm`, that `Applicative` instance won't work for us. `mkMessage` has the type:
] mkMessage :: ProofForm (NotNull -> NotNull -> NotNull -> ValidMessage)
] (String -> String -> String -> Message)
and we want to apply it to `ProofForms` created by:
] inputText' :: String -> ProofForm NotNull String
Here the proof types don't match up. Instead we need a `Applicative
Functor` that allows us to transform the return value *and* the proof
value. We need, what I believe is called, a `Type-Indexed Applicative
Functor` or a `Parameterized Applicative Functor`. Most literature on
this subject is actually dealing with type-indexed or parameterized
`Monads`, but the idea is the same.
The `reform` library defines two new classes, `IndexedFunctor` and `IndexedApplicative`:
] class IndexedFunctor f where
] -- | imap is similar to fmap
] imap :: (x -> y) -- ^ function to apply to first parameter
] -> (a -> b) -- ^ function to apply to second parameter
] -> f x a -- ^ indexed functor
] -> f y b
] class (IndexedFunctor f) => IndexedApplicative f where
] -- | similar to 'pure'
] ipure :: x -> a -> f x a
] -- | similar to '<*>'
] (<<*>>) :: f (x -> y) (a -> b) -> f x a -> f y b
These classes look just like their non-indexed counterparts, except that they transform an extra parameter. Now we can create instances like:
] instance (Monad m) => IndexedFunctor (Form m input view error) where
] instance (Monad m, Monoid view) => IndexedApplicative (Form m input error view) where
We use these classes the same way we would use the normal `Functor` and `Applicative` classes. The only difference is that the type-checker can now enforce the proofs.
The `Proof` module provides a handful of useful `Proofs` that perform
transformations, such as converting a `String` to a `Int`:
] decimal :: (Monad m, Eq i, Num i) =>
] (String -> error) -- ^ create an error message ('String' is the value
] -- that did not parse)
] -> Proof m error Decimal String i
We can use this `Proof` with our `SimpleForm` by using the `transform` function:
] transform :: (Monad m) =>
] Form m input error view anyProof a
] -> Proof m error proof a b
] -> Form m input error view () b
`transform` is similar to the `prove` function, except it ignores the proof name and sets the proof to `()`. Technically `()` is still a proof -- but we consider it to be the proof that proves nothing.
Here is an example of using `transform` with `decimal` to create a
simple form that parses a positive `Integer` value:
> inputInteger :: SimpleForm Integer
> inputInteger = inputText "" `transform` (decimal NotANatural)
>
And, that is the essence of `reform`. The Haddock documentation should cover the remainder -- such as other types of input controls (radio buttons, checkboxes, etc).
Here is a main function that ties all the examples together:
> main :: IO ()
> main =
> simpleHTTP nullConf $ unXMLGenT $
> do decodeBody (defaultBodyPolicy "/tmp/" 0 10000 10000)
> msum [ postPage
> , postPage2
> , validPage
> , do nullDir
> appTemplate "forms" () $
>
> ]
>
There is nothing `reform` specific about.
[Source code for the app is here.]
Next: Web Routes