| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  | <!doctype html> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <title>CodeMirror: Haskell-literate mode</title> | 
					
						
							|  |  |  | <meta charset="utf-8"/> | 
					
						
							|  |  |  | <link rel=stylesheet href="../../doc/docs.css"> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <link rel="stylesheet" href="../../lib/codemirror.css"> | 
					
						
							|  |  |  | <script src="../../lib/codemirror.js"></script> | 
					
						
							|  |  |  | <script src="haskell-literate.js"></script> | 
					
						
							|  |  |  | <script src="../haskell/haskell.js"></script> | 
					
						
							|  |  |  | <style>.CodeMirror { | 
					
						
							|  |  |  |   border-top    : 1px solid #DDDDDD; | 
					
						
							|  |  |  |   border-bottom : 1px solid #DDDDDD; | 
					
						
							|  |  |  | }</style> | 
					
						
							|  |  |  | <div id=nav> | 
					
						
							| 
									
										
										
										
											2018-10-07 12:02:07 +02:00
										 |  |  |   <a href="https://codemirror.net"><h1>CodeMirror</h1><img id=logo | 
					
						
							| 
									
										
										
										
											2018-01-21 10:33:32 -05:00
										 |  |  |                                                           src="../../doc/logo.png"></a> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   <ul> | 
					
						
							|  |  |  |     <li><a href="../../index.html">Home</a> | 
					
						
							|  |  |  |     <li><a href="../../doc/manual.html">Manual</a> | 
					
						
							|  |  |  |     <li><a href="https://github.com/codemirror/codemirror">Code</a> | 
					
						
							|  |  |  |   </ul> | 
					
						
							|  |  |  |   <ul> | 
					
						
							|  |  |  |     <li><a href="../index.html">Language modes</a> | 
					
						
							|  |  |  |     <li><a class=active href="#">Haskell-literate</a> | 
					
						
							|  |  |  |   </ul> | 
					
						
							|  |  |  | </div> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <article> | 
					
						
							|  |  |  |   <h2>Haskell literate mode</h2> | 
					
						
							|  |  |  |   <form> | 
					
						
							|  |  |  |     <textarea id="code" name="code"> | 
					
						
							|  |  |  | > {-# LANGUAGE OverloadedStrings #-} | 
					
						
							|  |  |  | > {-# OPTIONS_GHC -fno-warn-unused-do-bind #-} | 
					
						
							|  |  |  | > import Control.Applicative ((<$>), (<*>)) | 
					
						
							|  |  |  | > import Data.Maybe (isJust) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | > import Data.Text (Text) | 
					
						
							|  |  |  | > import Text.Blaze ((!)) | 
					
						
							|  |  |  | > import qualified Data.Text as T | 
					
						
							|  |  |  | > import qualified Happstack.Server as Happstack | 
					
						
							|  |  |  | > import qualified Text.Blaze.Html5 as H | 
					
						
							|  |  |  | > import qualified Text.Blaze.Html5.Attributes as A | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | > import Text.Digestive | 
					
						
							|  |  |  | > import Text.Digestive.Blaze.Html5 | 
					
						
							|  |  |  | > import Text.Digestive.Happstack | 
					
						
							|  |  |  | > import Text.Digestive.Util | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Simple forms and validation | 
					
						
							|  |  |  | --------------------------- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Let's start by creating a very simple datatype to represent a user: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | > data User = User | 
					
						
							|  |  |  | >     { userName :: Text | 
					
						
							|  |  |  | >     , userMail :: Text | 
					
						
							|  |  |  | >     } deriving (Show) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | And dive in immediately to create a `Form` for a user. The `Form v m a` type | 
					
						
							|  |  |  | has three parameters: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | - `v`: the type for messages and errors (usually a `String`-like type, `Text` in | 
					
						
							|  |  |  |   this case); | 
					
						
							|  |  |  | - `m`: the monad we are operating in, not specified here; | 
					
						
							|  |  |  | - `a`: the return type of the `Form`, in this case, this is obviously `User`. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | > userForm :: Monad m => Form Text m User | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | We create forms by using the `Applicative` interface. A few form types are | 
					
						
							|  |  |  | provided in the `Text.Digestive.Form` module, such as `text`, `string`, | 
					
						
							|  |  |  | `bool`... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | In the `digestive-functors` library, the developer is required to label each | 
					
						
							|  |  |  | field using the `.:` operator. This might look like a bit of a burden, but it | 
					
						
							|  |  |  | allows you to do some really useful stuff, like separating the `Form` from the | 
					
						
							|  |  |  | actual HTML layout. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | > userForm = User | 
					
						
							|  |  |  | >     <$> "name" .: text Nothing | 
					
						
							|  |  |  | >     <*> "mail" .: check "Not a valid email address" checkEmail (text Nothing) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The `check` function enables you to validate the result of a form. For example, | 
					
						
							|  |  |  | we can validate the email address with a really naive `checkEmail` function. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | > checkEmail :: Text -> Bool | 
					
						
							|  |  |  | > checkEmail = isJust . T.find (== '@') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | More validation | 
					
						
							|  |  |  | --------------- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | For our example, we also want descriptions of Haskell libraries, and in order to | 
					
						
							|  |  |  | do that, we need package versions... | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | > type Version = [Int] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | We want to let the user input a version number such as `0.1.0.0`. This means we | 
					
						
							|  |  |  | need to validate if the input `Text` is of this form, and then we need to parse | 
					
						
							|  |  |  | it to a `Version` type. Fortunately, we can do this in a single function: | 
					
						
							|  |  |  | `validate` allows conversion between values, which can optionally fail. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | `readMaybe :: Read a => String -> Maybe a` is a utility function imported from | 
					
						
							|  |  |  | `Text.Digestive.Util`. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | > validateVersion :: Text -> Result Text Version | 
					
						
							|  |  |  | > validateVersion = maybe (Error "Cannot parse version") Success . | 
					
						
							|  |  |  | >     mapM (readMaybe . T.unpack) . T.split (== '.') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | A quick test in GHCi: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ghci> validateVersion (T.pack "0.3.2.1") | 
					
						
							|  |  |  |     Success [0,3,2,1] | 
					
						
							|  |  |  |     ghci> validateVersion (T.pack "0.oops") | 
					
						
							|  |  |  |     Error "Cannot parse version" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | It works! This means we can now easily add a `Package` type and a `Form` for it: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | > data Category = Web | Text | Math | 
					
						
							|  |  |  | >     deriving (Bounded, Enum, Eq, Show) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | > data Package = Package Text Version Category | 
					
						
							|  |  |  | >     deriving (Show) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | > packageForm :: Monad m => Form Text m Package | 
					
						
							|  |  |  | > packageForm = Package | 
					
						
							|  |  |  | >     <$> "name"     .: text Nothing | 
					
						
							|  |  |  | >     <*> "version"  .: validate validateVersion (text (Just "0.0.0.1")) | 
					
						
							|  |  |  | >     <*> "category" .: choice categories Nothing | 
					
						
							|  |  |  | >   where | 
					
						
							|  |  |  | >     categories = [(x, T.pack (show x)) | x <- [minBound .. maxBound]] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Composing forms | 
					
						
							|  |  |  | --------------- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | A release has an author and a package. Let's use this to illustrate the | 
					
						
							|  |  |  | composability of the digestive-functors library: we can reuse the forms we have | 
					
						
							|  |  |  | written earlier on. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | > data Release = Release User Package | 
					
						
							|  |  |  | >     deriving (Show) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | > releaseForm :: Monad m => Form Text m Release | 
					
						
							|  |  |  | > releaseForm = Release | 
					
						
							|  |  |  | >     <$> "author"  .: userForm | 
					
						
							|  |  |  | >     <*> "package" .: packageForm | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Views | 
					
						
							|  |  |  | ----- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | As mentioned before, one of the advantages of using digestive-functors is | 
					
						
							|  |  |  | separation of forms and their actual HTML layout. In order to do this, we have | 
					
						
							|  |  |  | another type, `View`. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | We can get a `View` from a `Form` by supplying input. A `View` contains more | 
					
						
							|  |  |  | information than a `Form`, it has: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | - the original form; | 
					
						
							|  |  |  | - the input given by the user; | 
					
						
							|  |  |  | - any errors that have occurred. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | It is this view that we convert to HTML. For this tutorial, we use the | 
					
						
							|  |  |  | [blaze-html] library, and some helpers from the `digestive-functors-blaze` | 
					
						
							|  |  |  | library. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | [blaze-html]: http://jaspervdj.be/blaze/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Let's write a view for the `User` form. As you can see, we here refer to the | 
					
						
							|  |  |  | different fields in the `userForm`. The `errorList` will generate a list of | 
					
						
							|  |  |  | errors for the `"mail"` field. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | > userView :: View H.Html -> H.Html | 
					
						
							|  |  |  | > userView view = do | 
					
						
							|  |  |  | >     label     "name" view "Name: " | 
					
						
							|  |  |  | >     inputText "name" view | 
					
						
							|  |  |  | >     H.br | 
					
						
							|  |  |  | > | 
					
						
							|  |  |  | >     errorList "mail" view | 
					
						
							|  |  |  | >     label     "mail" view "Email address: " | 
					
						
							|  |  |  | >     inputText "mail" view | 
					
						
							|  |  |  | >     H.br | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Like forms, views are also composable: let's illustrate that by adding a view | 
					
						
							|  |  |  | for the `releaseForm`, in which we reuse `userView`. In order to do this, we | 
					
						
							|  |  |  | take only the parts relevant to the author from the view by using `subView`. We | 
					
						
							|  |  |  | can then pass the resulting view to our own `userView`. | 
					
						
							|  |  |  | We have no special view code for `Package`, so we can just add that to | 
					
						
							|  |  |  | `releaseView` as well. `childErrorList` will generate a list of errors for each | 
					
						
							|  |  |  | child of the specified form. In this case, this means a list of errors from | 
					
						
							|  |  |  | `"package.name"` and `"package.version"`. Note how we use `foo.bar` to refer to | 
					
						
							|  |  |  | nested forms. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | > releaseView :: View H.Html -> H.Html | 
					
						
							|  |  |  | > releaseView view = do | 
					
						
							|  |  |  | >     H.h2 "Author" | 
					
						
							|  |  |  | >     userView $ subView "author" view | 
					
						
							|  |  |  | > | 
					
						
							|  |  |  | >     H.h2 "Package" | 
					
						
							|  |  |  | >     childErrorList "package" view | 
					
						
							|  |  |  | > | 
					
						
							|  |  |  | >     label     "package.name" view "Name: " | 
					
						
							|  |  |  | >     inputText "package.name" view | 
					
						
							|  |  |  | >     H.br | 
					
						
							|  |  |  | > | 
					
						
							|  |  |  | >     label     "package.version" view "Version: " | 
					
						
							|  |  |  | >     inputText "package.version" view | 
					
						
							|  |  |  | >     H.br | 
					
						
							|  |  |  | > | 
					
						
							|  |  |  | >     label       "package.category" view "Category: " | 
					
						
							|  |  |  | >     inputSelect "package.category" view | 
					
						
							|  |  |  | >     H.br | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The attentive reader might have wondered what the type parameter for `View` is: | 
					
						
							|  |  |  | it is the `String`-like type used for e.g. error messages. | 
					
						
							|  |  |  | But wait! We have | 
					
						
							|  |  |  |     releaseForm :: Monad m => Form Text m Release | 
					
						
							|  |  |  |     releaseView :: View H.Html -> H.Html | 
					
						
							|  |  |  | ... doesn't this mean that we need a `View Text` rather than a `View Html`?  The | 
					
						
							|  |  |  | answer is yes -- but having `View Html` allows us to write these views more | 
					
						
							|  |  |  | easily with the `digestive-functors-blaze` library. Fortunately, we will be able | 
					
						
							|  |  |  | to fix this using the `Functor` instance of `View`. | 
					
						
							|  |  |  |     fmap :: Monad m => (v -> w) -> View v -> View w | 
					
						
							|  |  |  | A backend | 
					
						
							|  |  |  | --------- | 
					
						
							|  |  |  | To finish this tutorial, we need to be able to actually run this code. We need | 
					
						
							|  |  |  | an HTTP server for that, and we use [Happstack] for this tutorial. The | 
					
						
							|  |  |  | `digestive-functors-happstack` library gives about everything we need for this. | 
					
						
							|  |  |  | [Happstack]: http://happstack.com/ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | > site :: Happstack.ServerPart Happstack.Response | 
					
						
							|  |  |  | > site = do | 
					
						
							|  |  |  | >     Happstack.decodeBody $ Happstack.defaultBodyPolicy "/tmp" 4096 4096 4096 | 
					
						
							|  |  |  | >     r <- runForm "test" releaseForm | 
					
						
							|  |  |  | >     case r of | 
					
						
							|  |  |  | >         (view, Nothing) -> do | 
					
						
							|  |  |  | >             let view' = fmap H.toHtml view | 
					
						
							|  |  |  | >             Happstack.ok $ Happstack.toResponse $ | 
					
						
							|  |  |  | >                 template $ | 
					
						
							|  |  |  | >                     form view' "/" $ do | 
					
						
							|  |  |  | >                         releaseView view' | 
					
						
							|  |  |  | >                         H.br | 
					
						
							|  |  |  | >                         inputSubmit "Submit" | 
					
						
							|  |  |  | >         (_, Just release) -> Happstack.ok $ Happstack.toResponse $ | 
					
						
							|  |  |  | >             template $ do | 
					
						
							|  |  |  | >                 css | 
					
						
							|  |  |  | >                 H.h1 "Release received" | 
					
						
							|  |  |  | >                 H.p $ H.toHtml $ show release | 
					
						
							|  |  |  | > | 
					
						
							|  |  |  | > main :: IO () | 
					
						
							|  |  |  | > main = Happstack.simpleHTTP Happstack.nullConf site | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Utilities | 
					
						
							|  |  |  | --------- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | > template :: H.Html -> H.Html | 
					
						
							|  |  |  | > template body = H.docTypeHtml $ do | 
					
						
							|  |  |  | >     H.head $ do | 
					
						
							|  |  |  | >         H.title "digestive-functors tutorial" | 
					
						
							|  |  |  | >         css | 
					
						
							|  |  |  | >     H.body body | 
					
						
							|  |  |  | > css :: H.Html | 
					
						
							|  |  |  | > css = H.style ! A.type_ "text/css" $ do | 
					
						
							|  |  |  | >     "label {width: 130px; float: left; clear: both}" | 
					
						
							|  |  |  | >     "ul.digestive-functors-error-list {" | 
					
						
							|  |  |  | >     "    color: red;" | 
					
						
							|  |  |  | >     "    list-style-type: none;" | 
					
						
							|  |  |  | >     "    padding-left: 0px;" | 
					
						
							|  |  |  | >     "}" | 
					
						
							|  |  |  |     </textarea> | 
					
						
							|  |  |  |   </form> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   <p><strong>MIME types | 
					
						
							|  |  |  |   defined:</strong> <code>text/x-literate-haskell</code>.</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   <p>Parser configuration parameters recognized: <code>base</code> to | 
					
						
							|  |  |  |   set the base mode (defaults to <code>"haskell"</code>).</p> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   <script> | 
					
						
							|  |  |  |     var editor = CodeMirror.fromTextArea(document.getElementById("code"), {mode: "haskell-literate"}); | 
					
						
							|  |  |  |   </script> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | </article> |