0
I Use This!
Inactive

News

Analyzed 16 days ago. based on code collected 17 days ago.
Posted almost 14 years ago by [email protected] (Mauricio Scheffer)
I've blogged before about formlets, a nice abstraction of HTML forms. I started with a basic implementation, then showed validation. Now I'll present a non-toy implementation of formlets I called FsFormlets (really original name, I know). By ... [More] "non-toy" I mean this implementation is not a proof of concept or just for didactic purposes, but is meant to be eventually production-quality. I don't like to explain things in a vacuum, so I'll use a typical registration form to show how it works: Even though FsFormlets can be used to generate HTML on its own, there's a much better tool for this in F#: Wing Beats. Wing Beats is an EDSL in F# to generate HTML, much like SharpDOM in C# or Blaze in Haskell, or Eliom's XHTML.M in OCaml (except that XHTML.M actually validates HTML statically). So I've added a module to integrate FsFormlets to Wing Beats. This integration is a separate assembly; FsFormlets is stand-alone (it only requires .NET 3.5 SP1 and the F# runtime). We'll use FsFormlets to express forms, and Wing Beats to express the rest of HTML. Also, we'll handle web requests with Figment, with the help of some more glue code to integrate it with FsFormlets and Wing Beats. Layout Let's start by defining a layout in Wing Beats: let e = XhtmlElement() let layout title body = [ e.DocTypeHTML5 e.Html [ e.Head [ e.Title [ &title ] e.Style [ &".error {color:red;}" &"body {font-family:Verdana,Geneva,sans-serif; line-height: 160%;}" ] ] e.Body [ yield e.H1 [ &title ] yield! body ] ] ] No formlets so far, this is all pure HTML, expressed as a regular function. Now we'll build the form bottom-up. ReCaptcha FsFormlets already includes a reCaptcha formlet (I'll show its innards in a future post). We just have to configure it with a pair of public and private key (get it here) before using it: let reCaptcha = reCaptcha {PublicKey = "your_public_key"; PrivateKey = "your_private_key"; MockedResult = None} MockedResult lets you skip the actual validation web call and force a result, for testing purposes. Date input Now the date formlet, which is built from three inputs, plus labels and validation: let f = e.Formlets let dateFormlet : DateTime Formlet = let baseFormlet = yields t3 <*> (f.Text(maxlength = 2, attributes = ["type","number"; "min","1"; "max","12"; "required","required"; "size","3"]) |> f.WithLabel "Month: ") <*> (f.Text(maxlength = 2, attributes = ["type","number"; "min","1"; "max","31"; "required","required"; "size","3"]) |> f.WithLabel "Day: ") <*> (f.Text(maxlength = 4, attributes = ["type","number"; "min","1900"; "required","required"; "size","5"]) |> f.WithLabel "Year: ") let isDate (month,day,year) = let pad n (v: string) = v.PadLeft(n,'0') let ymd = sprintf "%s%s%s" (pad 4 year) (pad 2 month) (pad 2 day) DateTime.TryParseExact(ymd, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None) |> fst let dateValidator = err isDate (fun _ -> "Invalid date") baseFormlet |> satisfies dateValidator |> map (fun (month,day,year) -> DateTime(int year,int month,int day)) Here, baseFormlet is of type (string * string * string) Formlet, that is, it collects values in their raw form. This baseFormlet is then validated to make sure it's a date, and finally mapped to a DateTime. Note the use of the 'number' input type (a HTML5 input) and also HTML5 validation attributes (required, min, max) Password input The password formlet is next: let doublePassword = let compressedLength (s: string) = use buffer = new MemoryStream() use comp = new DeflateStream(buffer, CompressionMode.Compress) use w = new StreamWriter(comp) w.Write(s) w.Flush() buffer.Length let isStrong s = compressedLength s >= 106L let f = yields t2 <*> (f.Password(required = true) |> f.WithLabel "Password: ") < e.Br() <*> (f.Password(required = true) |> f.WithLabel "Repeat password: ") let areEqual (a,b) = a = b f |> satisfies (err areEqual (fun _ -> "Passwords don't match")) |> map fst |> satisfies (err isStrong (fun _ -> "Password too weak")) There are two things we validate here for passwords: first, the two entered passwords must match; and second, it must be strong enough. To measure password strength I use the compression technique I described in my last post. The < operator is used to add pure markup to the formlet, using Wing Beats. Also note the 'required = true' parameter. In the date formlet, we just used required=required as an HTML attribute. Here we used the optional argument required=true, which validates the formlet both at the client (with a required=required HTML attribute) and the server (it has a satisfies clause). In the date formlet we don't really care to validate server-side if the user filled out each input, we just want to know if it's a valid date or not. The optional parameter 'maxlength' does a similar thing: it outputs a maxlength attribute, and also checks the maximum length of he POSTed value. Sure all browsers implement maxlength properly, but anyone can easily craft a request (e.g. with cUrl, no need to program at all) to circumvent it. I see malicious bots doing things like this every day. This makes sure the data is really valid before you start processing it. Putting it all together Moving on, let's write the final formlet and store the posted information in a record: type PersonalInfo = { Name: string Email: string Password: string DateOfBirth: DateTime } let registrationFormlet ip = yields (fun n e p d -> { Name = n; Email = e; Password = p; DateOfBirth = d }) <*> (f.Text(required = true) |> f.WithLabel "Name: ") < e.Br() <*> (f.Email(required = true) |> f.WithLabel "Email: ") < e.Br() <*> doublePassword < e.Br() < &"Date of birth: " <*> dateFormlet < e.Br() < &"Please read very carefully these terms and conditions before registering for this online program, blah blah blah" < e.Br() <* (f.Checkbox(false) |> satisfies (err id (fun _ -> "Please accept the terms and conditions")) |> f.WithLabel "I agree to the terms and conditions above") <* reCaptcha ip Some notes about this last snippet: f.Email(required = true) generates an <input type="email" required="required"/> (again, HTML5 !), all server-validated. Unlike previous formlets, this one is a function, because reCaptcha needs the client's IP address to validate. If you've read my previous posts about formlets, you may be wondering why I'm using things like f.Text() and f.Checkbox() (which are members of an object) instead of the regular functions input and checkbox. Those functions are also present in FsFormlets and you may use them interchangeably with the object-style formlets, e.g. instead of f.CheckBox(false) you can write checkbox false []. The object-style formlets build on top of functional formlets, adding optional parameters for validation. They also integrate more seamlessly with Wing Beats. And we're done with the form! Now let's build the actual page that contains it. Using the layout we wrote earlier: let s = e.Shortcut let registrationPage form = layout "Registration" [ s.FormPost "" [ e.Fieldset [ yield e.Legend [ &"Please fill the fields below" ] yield!! form yield e.Br() yield s.Submit "Register!" ] ] ] This is also pure Wing Beats markup, I think it doesn't need much explanation except for the "yield!! form" which indicates where to render the formlet (yes, the operator doesn't look very friendly, I'll probably change it). Note how easy it is to compose pieces of potentially reusable HTML with Wing Beats as they're just functions. Handling requests with Figment Now all we need to do is bind the formlet to a URL to render the page: get "register" (fun _ -> registrationFormlet "" |> renderToXml |> registrationPage |> Result.wbview) and handle the form POST: post "register" (fun ctx -> let env = EnvDict.fromFormAndFiles ctx.Request match run (registrationFormlet ctx.IP) env with | Success v -> Result.redirectf "thankyou?n=%s" v.Name | Failure(errorForm,_) -> errorForm |> registrationPage |> Result.wbview) Oh, I almost forgot the little "thank you" after-registration page: get "thankyou" (fun ctx -> Result.contentf "Thank you for registering, %s" ctx.QueryString.["n"]) Now, this workflow we've just modeled is pretty common: Show form. User submits form. Validate form. If errors, show form again to user. Process form data. Redirect. So it's worthy of abstraction. The only moving parts are the formlet, the page and what to do on successful validation, so instead of mapping get and post individually we can say: formAction "register" { Formlet = fun ctx -> registrationFormlet ctx.IP Page = fun _ -> registrationPage Success = fun _ v -> Result.redirectf "thankyou?n=%s" v.Name } HTML5 ready Implementation of HTML5 in browsers has exploded in 2010 (see the beautiful html5readiness.com for reference). In particular, HTML5 forms are already implemented in Chrome 10, Opera and Firefox 4 (with caveats). Safari and IE haven't implemented it yet (at least not in versions 5.0.4 and 9 respectively), but WebKit already supports it so I guess Safari and other WebKit-based browsers will implement this in the short-term. So there's little reason not to use the new elements and attributes right now, as long as you also have server-side validation. For example, here's how Chrome validates required fields when trying to submit: Whereas in Safari 5.0.4/Windows the server-side validation kicks in: If you want browser-side validation in Safari, IE, etc, you can easily use an unobtrusive polyfill. A thorough list of cross-browser HTML5 polyfills is available here. For example, applying jQuery.tools is as easy as: let jsValidation = e.Div [ s.JavascriptFile "http://cdn.jquerytools.org/1.2.5/full/jquery.tools.min.js" e.Script [ &"$('form').validator();" ] ] and putting it at the bottom of the Wing Beats layout. All code posted here is part of the sample Figment app. FsFormlets source code is here. [Less]
Posted almost 14 years ago by [email protected] (Mauricio Scheffer)
I've blogged before about formlets, a nice abstraction of HTML forms. I started with a basic implementation, then showed validation. Now I'll present a non-toy implementation of formlets I called FsFormlets (really original name, I know). By ... [More] "non-toy" I mean this implementation is not a proof of concept or just for didactic purposes, but is meant to be eventually production-quality. I don't like to explain things in a vacuum, so I'll use a typical registration form to show how it works: Even though FsFormlets can be used to generate HTML on its own, there's a much better tool for this in F#: Wing Beats. Wing Beats is an EDSL in F# to generate HTML, much like SharpDOM in C# or Blaze in Haskell, or Eliom's XHTML.M in OCaml (except that XHTML.M actually validates HTML statically). So I've added a module to integrate FsFormlets to Wing Beats. This integration is a separate assembly; FsFormlets is stand-alone (it only requires .NET 3.5 SP1 and the F# runtime). We'll use FsFormlets to express forms, and Wing Beats to express the rest of HTML. Also, we'll handle web requests with Figment, with the help of some more glue code to integrate it with FsFormlets and Wing Beats. Layout Let's start by defining a layout in Wing Beats: let e = XhtmlElement() let layout title body = [ e.DocTypeHTML5 e.Html [ e.Head [ e.Title [ &title ] e.Style [ &".error {color:red;}" &"body {font-family:Verdana,Geneva,sans-serif; line-height: 160%;}" ] ] e.Body [ yield e.H1 [ &title ] yield! body ] ] ] No formlets so far, this is all pure HTML, expressed as a regular function. Now we'll build the form bottom-up. ReCaptcha FsFormlets already includes a reCaptcha formlet (I'll show its innards in a future post). We just have to configure it with a pair of public and private key (get it here) before using it: let reCaptcha = reCaptcha {PublicKey = "your_public_key"; PrivateKey = "your_private_key"; MockedResult = None} MockedResult lets you skip the actual validation web call and force a result, for testing purposes. Date input Now the date formlet, which is built from three inputs, plus labels and validation: let f = e.Formlets let dateFormlet : DateTime Formlet = let baseFormlet = yields t3 <*> (f.Text(maxlength = 2, attributes = ["type","number"; "min","1"; "max","12"; "required","required"; "size","3"]) |> f.WithLabel "Month: ") <*> (f.Text(maxlength = 2, attributes = ["type","number"; "min","1"; "max","31"; "required","required"; "size","3"]) |> f.WithLabel "Day: ") <*> (f.Text(maxlength = 4, attributes = ["type","number"; "min","1900"; "required","required"; "size","5"]) |> f.WithLabel "Year: ") let isDate (month,day,year) = let pad n (v: string) = v.PadLeft(n,'0') let ymd = sprintf "%s%s%s" (pad 4 year) (pad 2 month) (pad 2 day) DateTime.TryParseExact(ymd, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None) |> fst let dateValidator = err isDate (fun _ -> "Invalid date") baseFormlet |> satisfies dateValidator |> map (fun (month,day,year) -> DateTime(int year,int month,int day)) Here, baseFormlet is of type (string * string * string) Formlet, that is, it collects values in their raw form. This baseFormlet is then validated to make sure it's a date, and finally mapped to a DateTime. Note the use of the 'number' input type (a HTML5 input) and also HTML5 validation attributes (required, min, max) Password input The password formlet is next: let doublePassword = let compressedLength (s: string) = use buffer = new MemoryStream() use comp = new DeflateStream(buffer, CompressionMode.Compress) use w = new StreamWriter(comp) w.Write(s) w.Flush() buffer.Length let isStrong s = compressedLength s >= 106L let f = yields t2 <*> (f.Password(required = true) |> f.WithLabel "Password: ") <+ e.Br() <*> (f.Password(required = true) |> f.WithLabel "Repeat password: ") let areEqual (a,b) = a = b f |> satisfies (err areEqual (fun _ -> "Passwords don't match")) |> map fst |> satisfies (err isStrong (fun _ -> "Password too weak")) There are two things we validate here for passwords: first, the two entered passwords must match; and second, it must be strong enough. To measure password strength I use the compression technique I described in my last post. The <+ operator is used to add pure markup to the formlet, using Wing Beats. Also note the 'required = true' parameter. In the date formlet, we just used required=required as an HTML attribute. Here we used the optional argument required=true, which validates the formlet both at the client (with a required=required HTML attribute) and the server (it has a satisfies clause). In the date formlet we don't really care to validate server-side if the user filled out each input, we just want to know if it's a valid date or not. The optional parameter 'maxlength' does a similar thing: it outputs a maxlength attribute, and also checks the maximum length of he POSTed value. Sure all browsers implement maxlength properly, but anyone can easily craft a request (e.g. with cUrl, no need to program at all) to circumvent it. I see malicious bots doing things like this every day. This makes sure the data is really valid before you start processing it. Putting it all together Moving on, let's write the final formlet and store the posted information in a record: type PersonalInfo = { Name: string Email: string Password: string DateOfBirth: DateTime } let registrationFormlet ip = yields (fun n e p d -> { Name = n; Email = e; Password = p; DateOfBirth = d }) <*> (f.Text(required = true) |> f.WithLabel "Name: ") <+ e.Br() <*> (f.Email(required = true) |> f.WithLabel "Email: ") <+ e.Br() <*> doublePassword <+ e.Br() <+ &"Date of birth: " <*> dateFormlet <+ e.Br() <+ &"Please read very carefully these terms and conditions before registering for this online program, blah blah blah" <+ e.Br() <* (f.Checkbox(false) |> satisfies (err id (fun _ -> "Please accept the terms and conditions")) |> f.WithLabel "I agree to the terms and conditions above") <* reCaptcha ip Some notes about this last snippet: f.Email(required = true) generates an (again, HTML5 !), all server-validated. Unlike previous formlets, this one is a function, because reCaptcha needs the client's IP address to validate. If you've read my previous posts about formlets, you may be wondering why I'm using things like f.Text() and f.Checkbox() (which are members of an object) instead of the regular functions input and checkbox. Those functions are also present in FsFormlets and you may use them interchangeably with the object-style formlets, e.g. instead of f.CheckBox(false) you can write checkbox false []. The object-style formlets build on top of functional formlets, adding optional parameters for validation. They also integrate more seamlessly with Wing Beats. And we're done with the form! Now let's build the actual page that contains it. Using the layout we wrote earlier: let s = e.Shortcut let registrationPage form = layout "Registration" [ s.FormPost "" [ e.Fieldset [ yield e.Legend [ &"Please fill the fields below" ] yield!!+form yield e.Br() yield s.Submit "Register!" ] ] ] This is also pure Wing Beats markup, I think it doesn't need much explanation except for the "yield!!+form" which indicates where to render the formlet (yes, the operator doesn't look very friendly, I'll probably change it). Note how easy it is to compose pieces of potentially reusable HTML with Wing Beats as they're just functions. Handling requests with Figment Now all we need to do is bind the formlet to a URL to render the page: get "register" (fun _ -> registrationFormlet "" |> renderToXml |> registrationPage |> Result.wbview) and handle the form POST: post "register" (fun ctx -> let env = EnvDict.fromFormAndFiles ctx.Request match run (registrationFormlet ctx.IP) env with | Success v -> Result.redirectf "thankyou?n=%s" v.Name | Failure(errorForm,_) -> errorForm |> registrationPage |> Result.wbview) Oh, I almost forgot the little "thank you" after-registration page: get "thankyou" (fun ctx -> Result.contentf "Thank you for registering, %s" ctx.QueryString.["n"]) Now, this workflow we've just modeled is pretty common: Show form. User submits form. Validate form. If errors, show form again to user. Process form data. Redirect. So it's worthy of abstraction. The only moving parts are the formlet, the page and what to do on successful validation, so instead of mapping get and post individually we can say: formAction "register" { Formlet = fun ctx -> registrationFormlet ctx.IP Page = fun _ -> registrationPage Success = fun _ v -> Result.redirectf "thankyou?n=%s" v.Name } HTML5 ready Implementation of HTML5 in browsers has exploded in 2010 (see the beautiful html5readiness.com for reference). In particular, HTML5 forms are already implemented in Chrome 10, Opera and Firefox 4 (with caveats). Safari and IE haven't implemented it yet (at least not in versions 5.0.4 and 9 respectively), but WebKit already supports it so I guess Safari and other WebKit-based browsers will implement this in the short-term. So there's little reason not to use the new elements and attributes right now, as long as you also have server-side validation. For example, here's how Chrome validates required fields when trying to submit: Whereas in Safari 5.0.4/Windows the server-side validation kicks in: If you want browser-side validation in Safari, IE, etc, you can easily use an unobtrusive polyfill. A thorough list of cross-browser HTML5 polyfills is available here. For example, applying jQuery.tools is as easy as: let jsValidation = e.Div [ s.JavascriptFile "http://cdn.jquerytools.org/1.2.5/full/jquery.tools.min.js" e.Script [ &"$('form').validator();" ] ] and putting it at the bottom of the Wing Beats layout. All code posted here is part of the sample Figment app. FsFormlets source code is here. [Less]
Posted about 14 years ago by [email protected] (mausch)
Last time, we broke up formlets into primitive applicative functors, then composed these primitives to produce formlets. So far, if someone entered an unexpected value in our little implementation of formlets (e.g. "abc" in an int Formlet), we'd get ... [More] a nasty exception and there wasn't much we could do about it because we couldn't trace the exception to the formlet that was producing it. What we want, instead of an exception, is something that accumulates errors without interrupting the computation flow. Applicative functors, being oblivious, are perfect for that. More concretely, we want to: Know if the formlet was able to collect the value or not, and  if it couldn't, get the formlet rendered with the values the user entered and error messages, so we can re-display this second form, giving the user a chance to correct his mistakes. The first item is a job for the option type. We'll return Some v if the formlet was successful and None if it wasn't. For the second, we already have a XmlWriter applicative, so we could just reuse it to build this "error form". Recall the type of formlets as we defined it last time: type 'a Formlet = 'a Environ XmlWriter NameGen Adding the two concerns we just mentioned, the signature becomes: type 'a Formlet = 'a Error XmlWriter Environ XmlWriter NameGen where Error is the Maybe monad in applicative form (remember that every monad is also an applicative functor). This is what I was talking about when I mentioned formlets being extensible: you can extend formlet features by composing additional applicatives. By the way, here's the same formlet type expressed using C# bracket style (which F# also supports): type Formlet<'a> = NameGen<XmlWriter<Environ<XmlWriter<Error<'a>>>>> See now why I prefer ML syntax? Bracket style looks quite messy once you start nesting many types. Anyway, it's not enough to compose these applicatives, we also need some way to define how to validate things and how to show error messages. Let's define a validator type: /// Validator type. /// Fst determines if value is valid /// Snd builds an error message type 'a Validator = ('a -> bool) * ('a -> xml_item list -> xml_item list) and a function that attaches a validator to a formlet: satisfies : 'a Validator -> 'a Formlet -> 'a Formlet (I'm not going to bore you with implementation details in this post) So we can now write: let isInt = Int32.TryParse >> fst let intErrorMsg a xml = xml @ [ Tag("span", ["class","error"], [Text(sprintf "'%s' is not a valid number" a)]) ] let inputInt = input |> satisfies (isInt, intErrorMsg) |> lift int When rendered the first time, this formlet doesn't show anything different: printfn "%s" (render inputInt) <input name="input_0" value=""/> If we enter an invalid value, validation kicks in: let env = EnvDict.fromValueSeq ["input_0","abc"] match run inputInt env with | _, Some v -> printfn "Formlet successful, value %d" v | errorForm, None -> printfn "Formlet unsuccessful, error form: \n%A" (XmlWriter.render errorForm) This will print: Formlet unsuccessful, error form: <div> <input name="input_0" value="abc" /> <span class="error">'abc' is not a valid number</span> </div> We might want to wrap all that XML/HTML manipulation in order to make defining validators easier, for example: /// <summary> /// Constructs a validator /// </summary> /// <param name="isValid">Determines if value is valid</param> /// <param name="errorMsg">Builds the error message</param> let err (isValid: 'a -> bool) (errorMsg: 'a -> string) : 'a Validator = let addError value xml = [ Tag("span", ["class","errorinput"], xml) Tag("span", ["class","error"], [Text(errorMsg value)]) ] isValid, addError Now we can code inputInt as: let inputInt = input |> satisfies (err isInt (sprintf "'%s' is not a valid number")) |> lift int We can chain as many validators as we want for any formlet. For example, here's a formlet that checks that the submitted value falls within a specified range: let isInRange min max n = n >= min && n <= max let inputRange min max = inputInt |> satisfies (err (isInRange min max) (fun _ -> sprintf "Value must be between %d and %d" min max)) let input10to40 = inputRange 10 40 Note how I used inputInt as the starting point for inputRange. inputInt is already validated for integers, so any non-integers values fed to inputRange will fail with the same error message as before. inputRange adds further validation on top of inputInt's validation. This is another example of the composability of formlets. I put up a fully-fledged implementation of formlets, including validation, on github. There are some minor differences with what I've written so far about formlets. I'll blog about these differences soon. As with all my previous articles on formlets, I have to give credit to the Links team; these blog posts and code are mostly just a rehash of their papers, which I can't recommend enough. [Less]
Posted about 14 years ago by [email protected] (Mauricio Scheffer)
Last time, we broke up formlets into primitive applicative functors, then composed these primitives to produce formlets. So far, if someone entered an unexpected value in our little implementation of formlets (e.g. "abc" in an int Formlet), we'd get ... [More] a nasty exception and there wasn't much we could do about it because we couldn't trace the exception to the formlet that was producing it. What we want, instead of an exception, is something that accumulates errors without interrupting the computation flow. Applicative functors, being oblivious, are perfect for that. More concretely, we want to: Know if the formlet was able to collect the value or not, and  if it couldn't, get the formlet rendered with the values the user entered and error messages, so we can re-display this second form, giving the user a chance to correct his mistakes. The first item is a job for the option type. We'll return Some v if the formlet was successful and None if it wasn't. For the second, we already have a XmlWriter applicative, so we could just reuse it to build this "error form". Recall the type of formlets as we defined it last time: type 'a Formlet = 'a Environ XmlWriter NameGen Adding the two concerns we just mentioned, the signature becomes: type 'a Formlet = 'a Error XmlWriter Environ XmlWriter NameGen where Error is the Maybe monad in applicative form (remember that every monad is also an applicative functor). This is what I was talking about when I mentioned formlets being extensible: you can extend formlet features by composing additional applicatives. By the way, here's the same formlet type expressed using C# bracket style (which F# also supports): type Formlet<'a> = NameGen<XmlWriter<Environ<XmlWriter<Error<'a>>>>> See now why I prefer ML syntax? Bracket style looks quite messy once you start nesting many types. Anyway, it's not enough to compose these applicatives, we also need some way to define how to validate things and how to show error messages. Let's define a validator type: /// Validator type. /// Fst determines if value is valid /// Snd builds an error message type 'a Validator = ('a -> bool) * ('a -> xml_item list -> xml_item list) and a function that attaches a validator to a formlet: satisfies : 'a Validator -> 'a Formlet -> 'a Formlet (I'm not going to bore you with implementation details in this post) So we can now write: let isInt = Int32.TryParse >> fst let intErrorMsg a xml = xml @ [ Tag("span", ["class","error"], [Text(sprintf "'%s' is not a valid number" a)]) ] let inputInt = input |> satisfies (isInt, intErrorMsg) |> lift int When rendered the first time, this formlet doesn't show anything different: printfn "%s" (render inputInt) <input name="input_0" value=""/> If we enter an invalid value, validation kicks in: let env = EnvDict.fromValueSeq ["input_0","abc"] match run inputInt env with | _, Some v -> printfn "Formlet successful, value %d" v | errorForm, None -> printfn "Formlet unsuccessful, error form: \n%A" (XmlWriter.render errorForm) This will print: Formlet unsuccessful, error form: <div> <input name="input_0" value="abc" /> <span class="error">'abc' is not a valid number</span> </div> We might want to wrap all that XML/HTML manipulation in order to make defining validators easier, for example: /// <summary> /// Constructs a validator /// </summary> /// <param name="isValid">Determines if value is valid</param> /// <param name="errorMsg">Builds the error message</param> let err (isValid: 'a -> bool) (errorMsg: 'a -> string) : 'a Validator = let addError value xml = [ Tag("span", ["class","errorinput"], xml) Tag("span", ["class","error"], [Text(errorMsg value)]) ] isValid, addError Now we can code inputInt as: let inputInt = input |> satisfies (err isInt (sprintf "'%s' is not a valid number")) |> lift int We can chain as many validators as we want for any formlet. For example, here's a formlet that checks that the submitted value falls within a specified range: let isInRange min max n = n >= min && n <= max let inputRange min max = inputInt |> satisfies (err (isInRange min max) (fun _ -> sprintf "Value must be between %d and %d" min max)) let input10to40 = inputRange 10 40 Note how I used inputInt as the starting point for inputRange. inputInt is already validated for integers, so any non-integers values fed to inputRange will fail with the same error message as before. inputRange adds further validation on top of inputInt's validation. This is another example of the composability of formlets. I put up a fully-fledged implementation of formlets, including validation, on github. There are some minor differences with what I've written so far about formlets. I'll blog about these differences soon. As with all my previous articles on formlets, I have to give credit to the Links team; these blog posts and code are mostly just a rehash of their papers, which I can't recommend enough. [Less]
Posted about 14 years ago by [email protected] (Mauricio Scheffer)
Last time, we broke up formlets into primitive applicative functors, then composed these primitives to produce formlets. So far, if someone entered an unexpected value in our little implementation of formlets (e.g. "abc" in an int Formlet), we'd get ... [More] a nasty exception and there wasn't much we could do about it because we couldn't trace the exception to the formlet that was producing it. What we want, instead of an exception, is something that accumulates errors without interrupting the computation flow. Applicative functors, being oblivious, are perfect for that. More concretely, we want to: Know if the formlet was able to collect the value or not, and  if it couldn't, get the formlet rendered with the values the user entered and error messages, so we can re-display this second form, giving the user a chance to correct his mistakes. The first item is a job for the option type. We'll return Some v if the formlet was successful and None if it wasn't. For the second, we already have a XmlWriter applicative, so we could just reuse it to build this "error form". Recall the type of formlets as we defined it last time: type 'a Formlet = 'a Environ XmlWriter NameGen Adding the two concerns we just mentioned, the signature becomes: type 'a Formlet = 'a Error XmlWriter Environ XmlWriter NameGen where Error is the Maybe monad in applicative form (remember that every monad is also an applicative functor). This is what I was talking about when I mentioned formlets being extensible: you can extend formlet features by composing additional applicatives. By the way, here's the same formlet type expressed using C# bracket style (which F# also supports): type Formlet<'a> = NameGen>>>> See now why I prefer ML syntax? Bracket style looks quite messy once you start nesting many types. Anyway, it's not enough to compose these applicatives, we also need some way to define how to validate things and how to show error messages. Let's define a validator type: /// Validator type. /// Fst determines if value is valid /// Snd builds an error message type 'a Validator = ('a -> bool) * ('a -> xml_item list -> xml_item list) and a function that attaches a validator to a formlet: satisfies : 'a Validator -> 'a Formlet -> 'a Formlet (I'm not going to bore you with implementation details in this post) So we can now write: let isInt = Int32.TryParse >> fst let intErrorMsg a xml = xml @ [ Tag("span", ["class","error"], [Text(sprintf "'%s' is not a valid number" a)]) ] let inputInt = input |> satisfies (isInt, intErrorMsg) |> lift int When rendered the first time, this formlet doesn't show anything different: printfn "%s" (render inputInt) "input_0" value=""/> If we enter an invalid value, validation kicks in: let env = EnvDict.fromValueSeq ["input_0","abc"] match run inputInt env with | _, Some v -> printfn "Formlet successful, value %d" v | errorForm, None -> printfn "Formlet unsuccessful, error form: \n%A" (XmlWriter.render errorForm) This will print: Formlet unsuccessful, error form: "input_0" value="abc" /> class="error">'abc' is not a valid number We might want to wrap all that XML/HTML manipulation in order to make defining validators easier, for example: /// /// Constructs a validator /// /// Determines if value is valid /// Builds the error message let err (isValid: 'a -> bool) (errorMsg: 'a -> string) : 'a Validator = let addError value xml = [ Tag("span", ["class","errorinput"], xml) Tag("span", ["class","error"], [Text(errorMsg value)]) ] isValid, addError Now we can code inputInt as: let inputInt = input |> satisfies (err isInt (sprintf "'%s' is not a valid number")) |> lift int We can chain as many validators as we want for any formlet. For example, here's a formlet that checks that the submitted value falls within a specified range: let isInRange min max n = n >= min && n <= max let inputRange min max = inputInt |> satisfies (err (isInRange min max) (fun _ -> sprintf "Value must be between %d and %d" min max)) let input10to40 = inputRange 10 40 Note how I used inputInt as the starting point for inputRange. inputInt is already validated for integers, so any non-integers values fed to inputRange will fail with the same error message as before. inputRange adds further validation on top of inputInt's validation. This is another example of the composability of formlets. I put up a fully-fledged implementation of formlets, including validation, on github. There are some minor differences with what I've written so far about formlets. I'll blog about these differences soon. As with all my previous articles on formlets, I have to give credit to the Links team; these blog posts and code are mostly just a rehash of their papers, which I can't recommend enough. [Less]
Posted about 14 years ago by [email protected] (mausch)
In my last post I showed a bare bones implementation of formlets. You may have noticed that formlets do a lot of stuff: Generating form element names Handling submitted values Building and rendering HTML That's a lot of ... [More] responsibilities for a single module. Even the type of a formlet reflects this, it's pretty complex: type 'a Formlet = int -> (xml_item list * ((string * string) list -> 'a) * int) And we haven't even looked at adding validation, which would complicate things further. Bottom line: there is no clear separation of concerns. The good news are: we already have identified those concerns (the bulleted list above), and applicative functors are easily composable, so we can model each of those concerns as independent applicative functors, then compose them to yield formlets. HTML building As usual, we start by defining types: the same definition for XML trees: type xml_item = | Text of string | Tag of string * (string*string) list * xml_item list And the type of the applicative functor itself: type 'a XmlWriter = xml_item list * 'a A XML forest and something else. That 'something else' is what will enable the composition of this applicative with others. Finally, the implementation. If you compare this with the implementation of formlets described in the previous post, function by function, you'll see that this is effectively a stripped down applicative that only deals with XML. module XmlWriter = let puree (v: 'a) : 'a XmlWriter = [],v let (<*>) (f: ('a -> 'b) XmlWriter) (a: 'a XmlWriter) : 'b XmlWriter = fst f @ fst a, (snd f) (snd a) let lift f a = puree f <*> a let lift2 f a b = puree f <*> a <*> b let plug (f: xml_item list -> xml_item list) (a: 'a XmlWriter) : 'a XmlWriter = f (fst a), snd a let xml (e: xml_item list): unit XmlWriter = e,() let text (s: string) : unit XmlWriter = xml [Text s] let tag (t: string) (attr: (string*string) list) (v: 'a XmlWriter) : 'a XmlWriter = plug (fun x -> [Tag(t, attr, x)]) v open System.Xml.Linq let render (xml: xml_item list) : XDocument = let (!!) t = XName.op_Implicit t let xtext (s: string) = XText s :> XObject let xattr (name, value) = XAttribute(!!name, value) :> XObject let xattrs attr = List.map xattr attr let xelem name attr children = XElement(!!name, attr @ children) :> XObject let rec renderForest x = let render' = function | Text t -> xtext t | Tag(name, attr, children) -> xelem name (xattrs attr) (renderForest children) List.map render' x let root = xelem "div" [] (renderForest xml) XDocument root The only additional function here is plug, which maps the XML forest in a XmlWriter applicative. It's not of much use here and could be inlined, but it will be useful later to implement validation (in a future post). Environment handling The environment (or collector) applicative is one that fetches (collects) a value from the environment (usually Request.Form in ASP.NET). As such, its type is type 'a Environ = (string*string) list -> 'a The implementation: module Environ = let puree v = fun env -> v let (<*>) (f: ('a -> 'b) Environ) (a: 'a Environ) : 'b Environ = fun env -> let g = f env g(a(env)) let lift f a = puree f <*> a let lookup (n: string) : string Environ = fun env -> match List.tryFind (fun (k,_) -> k = n) env with | Some (_,v) -> v | _ -> failwithf "Key %s not found in environment" n Note the lookup function, it's an Environ constructor that implements the core functionality of "given this key and this environment, give me the corresponding value". It's pretty much a dictionary in applicative form. We'll also add here a helper function to convert a NameValueCollection (like Request.Form) to an environment: open System.Collections.Specialized let fromNV (a: NameValueCollection) = a.AllKeys |> Seq.collect (fun k -> a.GetValues k |> Seq.map (fun v -> k,v)) |> Seq.toList Name generation We've seen how formlets automatically generate form element names. While the actual generation is provided by nextName, the counter used is carried over from application to application, since this is purely functional code, without state: type 'a NameGen = int -> 'a * int module NameGen = let puree (v : 'a) : 'a NameGen = fun gen -> v,gen let (<*>) (f: ('a -> 'b) NameGen) (a: 'a NameGen) : 'b NameGen = fun gen -> let v,gen = f gen let w,gen = a gen v w, gen let lift f a = puree f <*> a let lift2 f a b = puree f <*> a <*> b let nextName : string NameGen = fun gen -> "input_" gen.ToString(), gen 1 let run (c: 'a NameGen) : 'a = fst (c 0) Here's a simple example of name generation: > open NameGen;; > let names = puree (printfn "%s %s") <*> nextName <*> nextName;; > run names;; input_0 input_1 Composing applicatives Applicative functors are easily composable, and the composition of any two applicatives is an applicative (math people say they're closed under composition). Interestingly, monads are not closed under composition, which as far as I understand is the reason for having monad transformers, but this doesn't seem to be a problem in F# since monads aren't as ubiquitous as in Haskell anyway. Composing two applicatives is as simple as applying one applicative's pure to the other applicative's pure, and lifting <*>. Again, since we can't abstract type constructors, we can't perform composition generically, but we can always do it ad-hoc. type 'a EnvironXmlWriter = 'a Environ XmlWriter Expanding the types, this means type 'a EnvironXmlWriter = xml_item list * ((string * string) list -> 'a) The implementation: module EnvironXmlWriter = let puree (v: 'a) : 'a Environ XmlWriter = v |> Environ.puree |> XmlWriter.puree let (<*>) (f: ('a -> 'b) Environ XmlWriter) (a: 'a Environ XmlWriter) : 'b Environ XmlWriter = XmlWriter.lift2 Environ.(<*>) f a let lift f a = puree f <*> a We'll also add a refine function that lets us build the composition from the "wrapping" applicative (in our case XmlWriter). Building it from the nested applicative is as easy as applying XmlWriter.pure. let refine (x: 'a XmlWriter) : 'a Environ XmlWriter = XmlWriter.lift Environ.puree x Example: let input : string Environ XmlWriter = let xml = [Tag("input",["name","firstname"],[])] let lookup = Environ.lookup "firstname" xml,lookup This is almost a formlet. Just like a fully-fledged formlet, it has a XML part that can be rendered, and an associated collector. The only thing missing here is the automatic name generation applied to both the XML element and the collector. Finally Formlets All we have to do now is compose EnvironXmlWriter with NameGen to yield Formlets. Its type is: type 'a Formlet = 'a EnvironXmlWriter NameGen or type 'a Formlet = 'a Environ XmlWriter NameGen or if we expand each type: type 'a Formlet = int -> (xml_item list * ((string * string) list -> 'a) * int) which is exactly what we had in the last post. As you can see, factoring it to primitive applicatives has given us a clearer definition. The implementation of formlets is almost trivial now: module Formlet = let puree v : _ Formlet = v |> EnvironXmlWriter.puree |> NameGen.puree let (<*>) (f: ('a -> 'b) Formlet) (a: 'a Formlet) : 'b Formlet = NameGen.lift2 EnvironXmlWriter.(<*>) f a let lift f a : _ Formlet = puree f <*> a let lift2 f a b : _ Formlet = puree f <*> a <*> b let ( *>) f a : _ Formlet = lift2 (fun _ z -> z) f a let ( <*) f a : _ Formlet = lift2 (fun z _ -> z) f a let xml (x: xml_item list) : unit Formlet = NameGen.puree (EnvironXmlWriter.refine (XmlWriter.xml x)) let text (s: string) : unit Formlet = xml [Text s] let tag (t: string) (attr: (string*string) list) (f: 'a Formlet) : 'a Formlet = NameGen.lift (XmlWriter.tag t attr) f let input : string Formlet = let xml name = XmlWriter.tag "input" ["name",name] let lookup name = XmlWriter.puree (Environ.lookup name) let tag name = xml name (lookup name) NameGen.lift tag NameGen.nextName let br: unit Formlet = xml [Tag("br",[],[])] let run (f: 'a Formlet) : 'a Environ = NameGen.run f |> snd open System.Xml.Linq let render (f: _ Formlet) = NameGen.run f |> fst |> XmlWriter.render F# Web Snippets Factoring formlets like this not only has the advantage of producing cleaner code, but also enables the reusability of each applicative functor and extending formlets more easily, as we'll see in a future post. Full source code is here. This implementation is interchangeable with the one in my last post, the project includes both implementations so you can compare them side by side. Most of this code was taken almost verbatim from the original papers on formlets. namespace Formlets Multiple items val int : 'T -> int (requires member op_Explicit) Full name: Microsoft.FSharp.Core.Operators.int -------------------- type int<'Measure> = int Full name: Microsoft.FSharp.Core.int<_>   type: int<'Measure>   implements: System.IComparable   implements: System.IConvertible   implements: System.IFormattable   implements: System.IComparable<int<'Measure>>   implements: System.IEquatable<int<'Measure>>   inherits: System.ValueType -------------------- type int = int32 Full name: Microsoft.FSharp.Core.int   type: int   implements: System.IComparable   implements: System.IFormattable   implements: System.IConvertible   implements: System.IComparable<int>   implements: System.IEquatable<int>   inherits: System.ValueType val puree : 'a -> int -> 'a * int Full name: Formlets.NameGen.puree val v : 'a type 'a NameGen = int -> 'a * int Full name: Formlets.NameGen<_> val gen : int   type: int   implements: System.IComparable   implements: System.IFormattable   implements: System.IConvertible   implements: System.IComparable<int>   implements: System.IEquatable<int>   inherits: System.ValueType val f : ('a -> 'b) NameGen val a : 'a NameGen val v : ('a -> 'b) val w : 'a val lift : ('a -> 'b) -> 'a NameGen -> 'b NameGen Full name: Formlets.NameGen.lift val f : ('a -> 'b) val lift2 : ('a -> 'b -> 'c) -> 'a NameGen -> 'b NameGen -> 'c NameGen Full name: Formlets.NameGen.lift2 val f : ('a -> 'b -> 'c) val b : 'b NameGen val nextName : int -> string * int Full name: Formlets.NameGen.nextName Multiple items val string : 'T -> string Full name: Microsoft.FSharp.Core.Operators.string -------------------- type string = System.String Full name: Microsoft.FSharp.Core.string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> Multiple overloads System.Object.ToString() : string System.Int32.ToString(provider: System.IFormatProvider) : string System.Int32.ToString(format: string) : string System.Int32.ToString(format: string, provider: System.IFormatProvider) : string val run : 'a NameGen -> 'a Full name: Formlets.NameGen.run val c : 'a NameGen val fst : ('T1 * 'T2) -> 'T1 Full name: Microsoft.FSharp.Core.Operators.fst type 'a Environ = (string * string) list -> 'a Full name: Formlets.Environ<_> type 'T list = List<'T> Full name: Microsoft.FSharp.Collections.list<_>   type: 'T list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<'T>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'T>   implements: System.Collections.IEnumerable val puree : 'a -> 'b -> 'a Full name: Formlets.Environ.puree val env : 'b val f : ('a -> 'b) Environ val a : 'a Environ val env : (string * string) list   type: (string * string) list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<string * string>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<string * string>   implements: System.Collections.IEnumerable val g : ('a -> 'b) val lift : ('a -> 'b) -> 'a Environ -> 'b Environ Full name: Formlets.Environ.lift val lookup : string -> (string * string) list -> string Full name: Formlets.Environ.lookup val n : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> Multiple items module List from Microsoft.FSharp.Collections -------------------- type List<'T> =   | ( [] )   | ( :: ) of 'T * 'T list   with     interface System.Collections.IEnumerable     interface System.Collections.Generic.IEnumerable<'T>     member Head : 'T     member IsEmpty : bool     member Item : index:int -> 'T with get     member Length : int     member Tail : 'T list     static member Cons : head:'T * tail:'T list -> 'T list     static member Empty : 'T list   end Full name: Microsoft.FSharp.Collections.List<_>   type: List<'T>   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<'T>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'T>   implements: System.Collections.IEnumerable val tryFind : ('T -> bool) -> 'T list -> 'T option Full name: Microsoft.FSharp.Collections.List.tryFind val k : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> union case Option.Some: 'T -> Option<'T> val v : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> val failwithf : Printf.StringFormat<'T,'Result> -> 'T Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.failwithf namespace System namespace System.Collections namespace System.Collections.Specialized val fromNV : NameValueCollection -> (string * string) list Full name: Formlets.Environ.fromNV val a : NameValueCollection   type: NameValueCollection   implements: System.Collections.ICollection   implements: System.Collections.IEnumerable   implements: System.Runtime.Serialization.ISerializable   implements: System.Runtime.Serialization.IDeserializationCallback   inherits: NameObjectCollectionBase type NameValueCollection =   class     inherit System.Collections.Specialized.NameObjectCollectionBase     new : unit -> System.Collections.Specialized.NameValueCollection     new : System.Collections.Specialized.NameValueCollection -> System.Collections.Specialized.NameValueCollection     new : System.Collections.IHashCodeProvider * System.Collections.IComparer -> System.Collections.Specialized.NameValueCollection     new : int -> System.Collections.Specialized.NameValueCollection     new : System.Collections.IEqualityComparer -> System.Collections.Specialized.NameValueCollection     new : int * System.Collections.IEqualityComparer -> System.Collections.Specialized.NameValueCollection     new : int * System.Collections.Specialized.NameValueCollection -> System.Collections.Specialized.NameValueCollection     new : int * System.Collections.IHashCodeProvider * System.Collections.IComparer -> System.Collections.Specialized.NameValueCollection     member Add : System.Collections.Specialized.NameValueCollection -> unit     member Add : string * string -> unit     member AllKeys : string []     member Clear : unit -> unit     member CopyTo : System.Array * int -> unit     member Get : string -> string     member Get : int -> string     member GetKey : int -> string     member GetValues : string -> string []     member GetValues : int -> string []     member HasKeys : unit -> bool     member Item : string -> string with get, set     member Item : int -> string     member Remove : string -> unit     member Set : string * string -> unit   end Full name: System.Collections.Specialized.NameValueCollection   type: NameValueCollection   implements: System.Collections.ICollection   implements: System.Collections.IEnumerable   implements: System.Runtime.Serialization.ISerializable   implements: System.Runtime.Serialization.IDeserializationCallback   inherits: NameObjectCollectionBase property NameValueCollection.AllKeys: string [] module Seq from Microsoft.FSharp.Collections val collect : ('T -> #seq<'U>) -> seq<'T> -> seq<'U> Full name: Microsoft.FSharp.Collections.Seq.collect Multiple overloads NameValueCollection.GetValues(index: int) : string [] NameValueCollection.GetValues(name: string) : string [] val map : ('T -> 'U) -> seq<'T> -> seq<'U> Full name: Microsoft.FSharp.Collections.Seq.map val toList : seq<'T> -> 'T list Full name: Microsoft.FSharp.Collections.Seq.toList type xml_item =   | Text of string   | Tag of string * (string * string) list * xml_item list Full name: Formlets.xml_item   type: xml_item   implements: System.IEquatable<xml_item>   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<xml_item>   implements: System.IComparable   implements: System.Collections.IStructuralComparable union case xml_item.Text: string -> xml_item union case xml_item.Tag: string * (string * string) list * xml_item list -> xml_item type 'a XmlWriter = xml_item list * 'a Full name: Formlets.XmlWriter<_> val puree : 'a -> 'a XmlWriter Full name: Formlets.XmlWriter.puree val f : ('a -> 'b) XmlWriter val a : 'a XmlWriter val snd : ('T1 * 'T2) -> 'T2 Full name: Microsoft.FSharp.Core.Operators.snd val lift : ('a -> 'b) -> xml_item list * 'a -> 'b XmlWriter Full name: Formlets.XmlWriter.lift val lift2 : ('a -> 'b -> 'c) -> xml_item list * 'a -> xml_item list * 'b -> 'c XmlWriter Full name: Formlets.XmlWriter.lift2 val b : 'b XmlWriter val plug : (xml_item list -> xml_item list) -> xml_item list * 'a -> 'a XmlWriter Full name: Formlets.XmlWriter.plug val f : (xml_item list -> xml_item list) val xml : xml_item list -> unit XmlWriter Full name: Formlets.XmlWriter.xml val e : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<xml_item>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<xml_item>   implements: System.Collections.IEnumerable type unit = Unit Full name: Microsoft.FSharp.Core.unit   type: unit   implements: System.IComparable val text : string -> unit XmlWriter Full name: Formlets.XmlWriter.text val s : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> val tag : string -> (string * string) list -> xml_item list * 'a -> 'a XmlWriter Full name: Formlets.XmlWriter.tag val t : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> val attr : (string * string) list   type: (string * string) list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<string * string>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<string * string>   implements: System.Collections.IEnumerable val v : 'a XmlWriter val x : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<xml_item>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<xml_item>   implements: System.Collections.IEnumerable namespace System.Xml namespace System.Xml.Linq val render : xml_item list -> XDocument Full name: Formlets.XmlWriter.render val xml : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<xml_item>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<xml_item>   implements: System.Collections.IEnumerable type XDocument =   class     inherit System.Xml.Linq.XContainer     new : unit -> System.Xml.Linq.XDocument     new : obj [] -> System.Xml.Linq.XDocument     new : System.Xml.Linq.XDeclaration * obj [] -> System.Xml.Linq.XDocument     new : System.Xml.Linq.XDocument -> System.Xml.Linq.XDocument     member Declaration : System.Xml.Linq.XDeclaration with get, set     member DocumentType : System.Xml.Linq.XDocumentType     member NodeType : System.Xml.XmlNodeType     member Root : System.Xml.Linq.XElement     member Save : string -> unit     member Save : System.IO.TextWriter -> unit     member Save : System.Xml.XmlWriter -> unit     member Save : string * System.Xml.Linq.SaveOptions -> unit     member Save : System.IO.TextWriter * System.Xml.Linq.SaveOptions -> unit     member WriteTo : System.Xml.XmlWriter -> unit     static member Load : string -> System.Xml.Linq.XDocument     static member Load : System.IO.TextReader -> System.Xml.Linq.XDocument     static member Load : System.Xml.XmlReader -> System.Xml.Linq.XDocument     static member Load : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument     static member Load : System.IO.TextReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument     static member Load : System.Xml.XmlReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument     static member Parse : string -> System.Xml.Linq.XDocument     static member Parse : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument   end Full name: System.Xml.Linq.XDocument   type: XDocument   implements: System.Xml.IXmlLineInfo   inherits: XContainer   inherits: XNode   inherits: XObject type XName =   class     member Equals : obj -> bool     member GetHashCode : unit -> int     member LocalName : string     member Namespace : System.Xml.Linq.XNamespace     member NamespaceName : string     member ToString : unit -> string     static member Get : string -> System.Xml.Linq.XName     static member Get : string * string -> System.Xml.Linq.XName   end Full name: System.Xml.Linq.XName   type: XName   implements: System.IEquatable<XName>   implements: System.Runtime.Serialization.ISerializable XName.op_Implicit(expandedName: string) : XName val xtext : (string -> XObject) type XText =   class     inherit System.Xml.Linq.XNode     new : string -> System.Xml.Linq.XText     new : System.Xml.Linq.XText -> System.Xml.Linq.XText     member NodeType : System.Xml.XmlNodeType     member Value : string with get, set     member WriteTo : System.Xml.XmlWriter -> unit   end Full name: System.Xml.Linq.XText   type: XText   implements: System.Xml.IXmlLineInfo   inherits: XNode   inherits: XObject type XObject =   class     member AddAnnotation : obj -> unit     member Annotation<'T> : unit -> 'T     member Annotation : System.Type -> obj     member Annotations<'T> : unit -> System.Collections.Generic.IEnumerable<'T>     member Annotations : System.Type -> System.Collections.Generic.IEnumerable<obj>     member BaseUri : string     member Document : System.Xml.Linq.XDocument     member NodeType : System.Xml.XmlNodeType     member Parent : System.Xml.Linq.XElement     member RemoveAnnotations<'T> : unit -> unit     member RemoveAnnotations : System.Type -> unit   end Full name: System.Xml.Linq.XObject   type: XObject   implements: System.Xml.IXmlLineInfo val xattr : (string * 'a -> XObject) val name : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> val value : 'a type XAttribute =   class     inherit System.Xml.Linq.XObject     new : System.Xml.Linq.XName * obj -> System.Xml.Linq.XAttribute     new : System.Xml.Linq.XAttribute -> System.Xml.Linq.XAttribute     member IsNamespaceDeclaration : bool     member Name : System.Xml.Linq.XName     member NextAttribute : System.Xml.Linq.XAttribute     member NodeType : System.Xml.XmlNodeType     member PreviousAttribute : System.Xml.Linq.XAttribute     member Remove : unit -> unit     member SetValue : obj -> unit     member ToString : unit -> string     member Value : string with get, set     static member EmptySequence : System.Collections.Generic.IEnumerable<System.Xml.Linq.XAttribute>   end Full name: System.Xml.Linq.XAttribute   type: XAttribute   implements: System.Xml.IXmlLineInfo   inherits: XObject val xattrs : ((string * 'a) list -> XObject list) val attr : (string * 'a) list   type: (string * 'a) list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<string * 'a>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<string * 'a>   implements: System.Collections.IEnumerable val map : ('T -> 'U) -> 'T list -> 'U list Full name: Microsoft.FSharp.Collections.List.map val xelem : (string -> 'a list -> 'a list -> XObject) val attr : 'a list   type: 'a list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<'a>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'a>   implements: System.Collections.IEnumerable val children : 'a list   type: 'a list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<'a>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'a>   implements: System.Collections.IEnumerable type XElement =   class     inherit System.Xml.Linq.XContainer     new : System.Xml.Linq.XName -> System.Xml.Linq.XElement     new : System.Xml.Linq.XName * obj -> System.Xml.Linq.XElement     new : System.Xml.Linq.XName * obj [] -> System.Xml.Linq.XElement     new : System.Xml.Linq.XElement -> System.Xml.Linq.XElement     new : System.Xml.Linq.XStreamingElement -> System.Xml.Linq.XElement     member AncestorsAndSelf : unit -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>     member AncestorsAndSelf : System.Xml.Linq.XName -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>     member Attribute : System.Xml.Linq.XName -> System.Xml.Linq.XAttribute     member Attributes : unit -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XAttribute>     member Attributes : System.Xml.Linq.XName -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XAttribute>     member DescendantNodesAndSelf : unit -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XNode>     member DescendantsAndSelf : unit -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>     member DescendantsAndSelf : System.Xml.Linq.XName -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>     member FirstAttribute : System.Xml.Linq.XAttribute     member GetDefaultNamespace : unit -> System.Xml.Linq.XNamespace     member GetNamespaceOfPrefix : string -> System.Xml.Linq.XNamespace     member GetPrefixOfNamespace : System.Xml.Linq.XNamespace -> string     member HasAttributes : bool     member HasElements : bool     member IsEmpty : bool     member LastAttribute : System.Xml.Linq.XAttribute     member Name : System.Xml.Linq.XName with get, set     member NodeType : System.Xml.XmlNodeType     member RemoveAll : unit -> unit     member RemoveAttributes : unit -> unit     member ReplaceAll : obj -> unit     member ReplaceAll : obj [] -> unit     member ReplaceAttributes : obj -> unit     member ReplaceAttributes : obj [] -> unit     member Save : string -> unit     member Save : System.IO.TextWriter -> unit     member Save : System.Xml.XmlWriter -> unit     member Save : string * System.Xml.Linq.SaveOptions -> unit     member Save : System.IO.TextWriter * System.Xml.Linq.SaveOptions -> unit     member SetAttributeValue : System.Xml.Linq.XName * obj -> unit     member SetElementValue : System.Xml.Linq.XName * obj -> unit     member SetValue : obj -> unit     member Value : string with get, set     member WriteTo : System.Xml.XmlWriter -> unit     static member EmptySequence : System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>     static member Load : string -> System.Xml.Linq.XElement     static member Load : System.IO.TextReader -> System.Xml.Linq.XElement     static member Load : System.Xml.XmlReader -> System.Xml.Linq.XElement     static member Load : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement     static member Load : System.IO.TextReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement     static member Load : System.Xml.XmlReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement     static member Parse : string -> System.Xml.Linq.XElement     static member Parse : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement   end Full name: System.Xml.Linq.XElement   type: XElement   implements: System.Xml.IXmlLineInfo   implements: System.Xml.Serialization.IXmlSerializable   inherits: XContainer   inherits: XNode   inherits: XObject val renderForest : (xml_item list -> XObject list) val render' : (xml_item -> XObject) val children : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<xml_item>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<xml_item>   implements: System.Collections.IEnumerable val root : XObject   type: XObject   implements: System.Xml.IXmlLineInfo val puree : 'a -> 'a Environ XmlWriter Full name: Formlets.EnvironXmlWriter.puree Multiple items module Environ from Formlets -------------------- type 'a Environ = (string * string) list -> 'a Full name: Formlets.Environ<_> Multiple items module XmlWriter from Formlets -------------------- type 'a XmlWriter = xml_item list * 'a Full name: Formlets.XmlWriter<_> val f : ('a -> 'b) Environ XmlWriter val a : 'a Environ XmlWriter val lift : ('a -> 'b) -> xml_item list * 'a Environ -> 'b Environ XmlWriter Full name: Formlets.EnvironXmlWriter.lift val refine : xml_item list * 'a -> 'a Environ XmlWriter Full name: Formlets.EnvironXmlWriter.refine val x : 'a XmlWriter type 'a Formlet = 'a Environ XmlWriter NameGen Full name: Formlets.Formlet<_> Multiple items module NameGen from Formlets -------------------- type 'a NameGen = int -> 'a * int Full name: Formlets.NameGen<_> type AutoOpenAttribute =   class     inherit System.Attribute     new : unit -> AutoOpenAttribute     new : path:string -> AutoOpenAttribute     member Path : string   end Full name: Microsoft.FSharp.Core.AutoOpenAttribute   type: AutoOpenAttribute   implements: System.Runtime.InteropServices._Attribute   inherits: System.Attribute Multiple items module Formlet from Formlets -------------------- type 'a Formlet = 'a Environ XmlWriter NameGen Full name: Formlets.Formlet<_> val puree : 'a -> 'a Formlet Full name: Formlets.Formlet.puree module EnvironXmlWriter from Formlets val f : ('a -> 'b) Formlet val a : 'a Formlet val lift : ('a -> 'b) -> 'a Formlet -> 'b Formlet Full name: Formlets.Formlet.lift val lift2 : ('a -> 'b -> 'c) -> 'a Formlet -> 'b Formlet -> 'c Formlet Full name: Formlets.Formlet.lift2 val b : 'b Formlet val f : 'a Formlet val a : 'b Formlet val z : 'b val z : 'a val xml : xml_item list -> unit Formlet Full name: Formlets.Formlet.xml val text : string -> unit Formlet Full name: Formlets.Formlet.text val tag : string -> (string * string) list -> 'a Formlet -> 'a Formlet Full name: Formlets.Formlet.tag val input : string Formlet Full name: Formlets.Formlet.input val xml : (string -> 'a XmlWriter -> 'a XmlWriter) val lookup : (string -> string Environ XmlWriter) val tag : (string -> string Environ XmlWriter) val br : unit Formlet Full name: Formlets.Formlet.br val run : 'a Formlet -> 'a Environ Full name: Formlets.Formlet.run val render : 'a Formlet -> XDocument Full name: Formlets.Formlet.render [Less]
Posted about 14 years ago by [email protected] (Mauricio Scheffer)
In my last post I showed a bare bones implementation of formlets. You may have noticed that formlets do a lot of stuff: Generating form element names Handling submitted values Building and rendering HTML That's a lot of ... [More] responsibilities for a single module. Even the type of a formlet reflects this, it's pretty complex: type 'a Formlet = int -> (xml_item list * ((string * string) list -> 'a) * int) And we haven't even looked at adding validation, which would complicate things further. Bottom line: there is no clear separation of concerns. The good news are: we already have identified those concerns (the bulleted list above), and applicative functors are easily composable, so we can model each of those concerns as independent applicative functors, then compose them to yield formlets. HTML building As usual, we start by defining types: the same definition for XML trees: type xml_item = | Text of string | Tag of string * (string*string) list * xml_item list And the type of the applicative functor itself: type 'a XmlWriter = xml_item list * 'a A XML forest and something else. That 'something else' is what will enable the composition of this applicative with others. Finally, the implementation. If you compare this with the implementation of formlets described in the previous post, function by function, you'll see that this is effectively a stripped down applicative that only deals with XML. module XmlWriter = let puree (v: 'a) : 'a XmlWriter = [],v let (<*>) (f: ('a -> 'b) XmlWriter) (a: 'a XmlWriter) : 'b XmlWriter = fst f @ fst a, (snd f) (snd a) let lift f a = puree f <*> a let lift2 f a b = puree f <*> a <*> b let plug (f: xml_item list -> xml_item list) (a: 'a XmlWriter) : 'a XmlWriter = f (fst a), snd a let xml (e: xml_item list): unit XmlWriter = e,() let text (s: string) : unit XmlWriter = xml [Text s] let tag (t: string) (attr: (string*string) list) (v: 'a XmlWriter) : 'a XmlWriter = plug (fun x -> [Tag(t, attr, x)]) v open System.Xml.Linq let render (xml: xml_item list) : XDocument = let (!!) t = XName.op_Implicit t let xtext (s: string) = XText s :> XObject let xattr (name, value) = XAttribute(!!name, value) :> XObject let xattrs attr = List.map xattr attr let xelem name attr children = XElement(!!name, attr @ children) :> XObject let rec renderForest x = let render' = function | Text t -> xtext t | Tag(name, attr, children) -> xelem name (xattrs attr) (renderForest children) List.map render' x let root = xelem "div" [] (renderForest xml) XDocument root The only additional function here is plug, which maps the XML forest in a XmlWriter applicative. It's not of much use here and could be inlined, but it will be useful later to implement validation (in a future post). Environment handling The environment (or collector) applicative is one that fetches (collects) a value from the environment (usually Request.Form in ASP.NET). As such, its type is type 'a Environ = (string*string) list -> 'a The implementation: module Environ = let puree v = fun env -> v let (<*>) (f: ('a -> 'b) Environ) (a: 'a Environ) : 'b Environ = fun env -> let g = f env g(a(env)) let lift f a = puree f <*> a let lookup (n: string) : string Environ = fun env -> match List.tryFind (fun (k,_) -> k = n) env with | Some (_,v) -> v | _ -> failwithf "Key %s not found in environment" n Note the lookup function, it's an Environ constructor that implements the core functionality of "given this key and this environment, give me the corresponding value". It's pretty much a dictionary in applicative form. We'll also add here a helper function to convert a NameValueCollection (like Request.Form) to an environment: open System.Collections.Specialized let fromNV (a: NameValueCollection) = a.AllKeys |> Seq.collect (fun k -> a.GetValues k |> Seq.map (fun v -> k,v)) |> Seq.toList Name generation We've seen how formlets automatically generate form element names. While the actual generation is provided by nextName, the counter used is carried over from application to application, since this is purely functional code, without state: type 'a NameGen = int -> 'a * int module NameGen = let puree (v : 'a) : 'a NameGen = fun gen -> v,gen let (<*>) (f: ('a -> 'b) NameGen) (a: 'a NameGen) : 'b NameGen = fun gen -> let v,gen = f gen let w,gen = a gen v w, gen let lift f a = puree f <*> a let lift2 f a b = puree f <*> a <*> b let nextName : string NameGen = fun gen -> "input_" + gen.ToString(), gen+1 let run (c: 'a NameGen) : 'a = fst (c 0) Here's a simple example of name generation: > open NameGen;; > let names = puree (printfn "%s %s") <*> nextName <*> nextName;; > run names;; input_0 input_1 Composing applicatives Applicative functors are easily composable, and the composition of any two applicatives is an applicative (math people say they're closed under composition). Interestingly, monads are not closed under composition, which as far as I understand is the reason for having monad transformers, but this doesn't seem to be a problem in F# since monads aren't as ubiquitous as in Haskell anyway. Composing two applicatives is as simple as applying one applicative's pure to the other applicative's pure, and lifting <*>. Again, since we can't abstract type constructors, we can't perform composition generically, but we can always do it ad-hoc. type 'a EnvironXmlWriter = 'a Environ XmlWriter Expanding the types, this means type 'a EnvironXmlWriter = xml_item list * ((string * string) list -> 'a) The implementation: module EnvironXmlWriter = let puree (v: 'a) : 'a Environ XmlWriter = v |> Environ.puree |> XmlWriter.puree let (<*>) (f: ('a -> 'b) Environ XmlWriter) (a: 'a Environ XmlWriter) : 'b Environ XmlWriter = XmlWriter.lift2 Environ.(<*>) f a let lift f a = puree f <*> a We'll also add a refine function that lets us build the composition from the "wrapping" applicative (in our case XmlWriter). Building it from the nested applicative is as easy as applying XmlWriter.pure. let refine (x: 'a XmlWriter) : 'a Environ XmlWriter = XmlWriter.lift Environ.puree x Example: let input : string Environ XmlWriter = let xml = [Tag("input",["name","firstname"],[])] let lookup = Environ.lookup "firstname" xml,lookup This is almost a formlet. Just like a fully-fledged formlet, it has a XML part that can be rendered, and an associated collector. The only thing missing here is the automatic name generation applied to both the XML element and the collector. Finally Formlets All we have to do now is compose EnvironXmlWriter with NameGen to yield Formlets. Its type is: type 'a Formlet = 'a EnvironXmlWriter NameGen or type 'a Formlet = 'a Environ XmlWriter NameGen or if we expand each type: type 'a Formlet = int -> (xml_item list * ((string * string) list -> 'a) * int) which is exactly what we had in the last post. As you can see, factoring it to primitive applicatives has given us a clearer definition. The implementation of formlets is almost trivial now: module Formlet = let puree v : _ Formlet = v |> EnvironXmlWriter.puree |> NameGen.puree let (<*>) (f: ('a -> 'b) Formlet) (a: 'a Formlet) : 'b Formlet = NameGen.lift2 EnvironXmlWriter.(<*>) f a let lift f a : _ Formlet = puree f <*> a let lift2 f a b : _ Formlet = puree f <*> a <*> b let ( *>) f a : _ Formlet = lift2 (fun _ z -> z) f a let ( <*) f a : _ Formlet = lift2 (fun z _ -> z) f a let xml (x: xml_item list) : unit Formlet = NameGen.puree (EnvironXmlWriter.refine (XmlWriter.xml x)) let text (s: string) : unit Formlet = xml [Text s] let tag (t: string) (attr: (string*string) list) (f: 'a Formlet) : 'a Formlet = NameGen.lift (XmlWriter.tag t attr) f let input : string Formlet = let xml name = XmlWriter.tag "input" ["name",name] let lookup name = XmlWriter.puree (Environ.lookup name) let tag name = xml name (lookup name) NameGen.lift tag NameGen.nextName let br: unit Formlet = xml [Tag("br",[],[])] let run (f: 'a Formlet) : 'a Environ = NameGen.run f |> snd open System.Xml.Linq let render (f: _ Formlet) = NameGen.run f |> fst |> XmlWriter.render F# Web Snippets Factoring formlets like this not only has the advantage of producing cleaner code, but also enables the reusability of each applicative functor and extending formlets more easily, as we'll see in a future post. Full source code is here. This implementation is interchangeable with the one in my last post, the project includes both implementations so you can compare them side by side. Most of this code was taken almost verbatim from the original papers on formlets. namespace Formlets Multiple items val int : 'T -> int (requires member op_Explicit) Full name: Microsoft.FSharp.Core.Operators.int -------------------- type int<'Measure> = int Full name: Microsoft.FSharp.Core.int<_>   type: int<'Measure>   implements: System.IComparable   implements: System.IConvertible   implements: System.IFormattable   implements: System.IComparable>   implements: System.IEquatable>   inherits: System.ValueType -------------------- type int = int32 Full name: Microsoft.FSharp.Core.int   type: int   implements: System.IComparable   implements: System.IFormattable   implements: System.IConvertible   implements: System.IComparable   implements: System.IEquatable   inherits: System.ValueType val puree : 'a -> int -> 'a * int Full name: Formlets.NameGen.puree val v : 'a type 'a NameGen = int -> 'a * int Full name: Formlets.NameGen<_> val gen : int   type: int   implements: System.IComparable   implements: System.IFormattable   implements: System.IConvertible   implements: System.IComparable   implements: System.IEquatable   inherits: System.ValueType val f : ('a -> 'b) NameGen val a : 'a NameGen val v : ('a -> 'b) val w : 'a val lift : ('a -> 'b) -> 'a NameGen -> 'b NameGen Full name: Formlets.NameGen.lift val f : ('a -> 'b) val lift2 : ('a -> 'b -> 'c) -> 'a NameGen -> 'b NameGen -> 'c NameGen Full name: Formlets.NameGen.lift2 val f : ('a -> 'b -> 'c) val b : 'b NameGen val nextName : int -> string * int Full name: Formlets.NameGen.nextName Multiple items val string : 'T -> string Full name: Microsoft.FSharp.Core.Operators.string -------------------- type string = System.String Full name: Microsoft.FSharp.Core.string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable   implements: seq   implements: System.Collections.IEnumerable   implements: System.IEquatable Multiple overloads System.Object.ToString() : string System.Int32.ToString(provider: System.IFormatProvider) : string System.Int32.ToString(format: string) : string System.Int32.ToString(format: string, provider: System.IFormatProvider) : string val run : 'a NameGen -> 'a Full name: Formlets.NameGen.run val c : 'a NameGen val fst : ('T1 * 'T2) -> 'T1 Full name: Microsoft.FSharp.Core.Operators.fst type 'a Environ = (string * string) list -> 'a Full name: Formlets.Environ<_> type 'T list = List<'T> Full name: Microsoft.FSharp.Collections.list<_>   type: 'T list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'T>   implements: System.Collections.IEnumerable val puree : 'a -> 'b -> 'a Full name: Formlets.Environ.puree val env : 'b val f : ('a -> 'b) Environ val a : 'a Environ val env : (string * string) list   type: (string * string) list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable   implements: System.Collections.IEnumerable val g : ('a -> 'b) val lift : ('a -> 'b) -> 'a Environ -> 'b Environ Full name: Formlets.Environ.lift val lookup : string -> (string * string) list -> string Full name: Formlets.Environ.lookup val n : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable   implements: seq   implements: System.Collections.IEnumerable   implements: System.IEquatable Multiple items module List from Microsoft.FSharp.Collections -------------------- type List<'T> =   | ( [] )   | ( :: ) of 'T * 'T list   with     interface System.Collections.IEnumerable     interface System.Collections.Generic.IEnumerable<'T>     member Head : 'T     member IsEmpty : bool     member Item : index:int -> 'T with get     member Length : int     member Tail : 'T list     static member Cons : head:'T * tail:'T list -> 'T list     static member Empty : 'T list   end Full name: Microsoft.FSharp.Collections.List<_>   type: List<'T>   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'T>   implements: System.Collections.IEnumerable val tryFind : ('T -> bool) -> 'T list -> 'T option Full name: Microsoft.FSharp.Collections.List.tryFind val k : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable   implements: seq   implements: System.Collections.IEnumerable   implements: System.IEquatable union case Option.Some: 'T -> Option<'T> val v : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable   implements: seq   implements: System.Collections.IEnumerable   implements: System.IEquatable val failwithf : Printf.StringFormat<'T,'Result> -> 'T Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.failwithf namespace System namespace System.Collections namespace System.Collections.Specialized val fromNV : NameValueCollection -> (string * string) list Full name: Formlets.Environ.fromNV val a : NameValueCollection   type: NameValueCollection   implements: System.Collections.ICollection   implements: System.Collections.IEnumerable   implements: System.Runtime.Serialization.ISerializable   implements: System.Runtime.Serialization.IDeserializationCallback   inherits: NameObjectCollectionBase type NameValueCollection =   class     inherit System.Collections.Specialized.NameObjectCollectionBase     new : unit -> System.Collections.Specialized.NameValueCollection     new : System.Collections.Specialized.NameValueCollection -> System.Collections.Specialized.NameValueCollection     new : System.Collections.IHashCodeProvider * System.Collections.IComparer -> System.Collections.Specialized.NameValueCollection     new : int -> System.Collections.Specialized.NameValueCollection     new : System.Collections.IEqualityComparer -> System.Collections.Specialized.NameValueCollection     new : int * System.Collections.IEqualityComparer -> System.Collections.Specialized.NameValueCollection     new : int * System.Collections.Specialized.NameValueCollection -> System.Collections.Specialized.NameValueCollection     new : int * System.Collections.IHashCodeProvider * System.Collections.IComparer -> System.Collections.Specialized.NameValueCollection     member Add : System.Collections.Specialized.NameValueCollection -> unit     member Add : string * string -> unit     member AllKeys : string []     member Clear : unit -> unit     member CopyTo : System.Array * int -> unit     member Get : string -> string     member Get : int -> string     member GetKey : int -> string     member GetValues : string -> string []     member GetValues : int -> string []     member HasKeys : unit -> bool     member Item : string -> string with get, set     member Item : int -> string     member Remove : string -> unit     member Set : string * string -> unit   end Full name: System.Collections.Specialized.NameValueCollection   type: NameValueCollection   implements: System.Collections.ICollection   implements: System.Collections.IEnumerable   implements: System.Runtime.Serialization.ISerializable   implements: System.Runtime.Serialization.IDeserializationCallback   inherits: NameObjectCollectionBase property NameValueCollection.AllKeys: string [] module Seq from Microsoft.FSharp.Collections val collect : ('T -> #seq<'U>) -> seq<'T> -> seq<'U> Full name: Microsoft.FSharp.Collections.Seq.collect Multiple overloads NameValueCollection.GetValues(index: int) : string [] NameValueCollection.GetValues(name: string) : string [] val map : ('T -> 'U) -> seq<'T> -> seq<'U> Full name: Microsoft.FSharp.Collections.Seq.map val toList : seq<'T> -> 'T list Full name: Microsoft.FSharp.Collections.Seq.toList type xml_item =   | Text of string   | Tag of string * (string * string) list * xml_item list Full name: Formlets.xml_item   type: xml_item   implements: System.IEquatable   implements: System.Collections.IStructuralEquatable   implements: System.IComparable   implements: System.IComparable   implements: System.Collections.IStructuralComparable union case xml_item.Text: string -> xml_item union case xml_item.Tag: string * (string * string) list * xml_item list -> xml_item type 'a XmlWriter = xml_item list * 'a Full name: Formlets.XmlWriter<_> val puree : 'a -> 'a XmlWriter Full name: Formlets.XmlWriter.puree val f : ('a -> 'b) XmlWriter val a : 'a XmlWriter val snd : ('T1 * 'T2) -> 'T2 Full name: Microsoft.FSharp.Core.Operators.snd val lift : ('a -> 'b) -> xml_item list * 'a -> 'b XmlWriter Full name: Formlets.XmlWriter.lift val lift2 : ('a -> 'b -> 'c) -> xml_item list * 'a -> xml_item list * 'b -> 'c XmlWriter Full name: Formlets.XmlWriter.lift2 val b : 'b XmlWriter val plug : (xml_item list -> xml_item list) -> xml_item list * 'a -> 'a XmlWriter Full name: Formlets.XmlWriter.plug val f : (xml_item list -> xml_item list) val xml : xml_item list -> unit XmlWriter Full name: Formlets.XmlWriter.xml val e : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable   implements: System.Collections.IEnumerable type unit = Unit Full name: Microsoft.FSharp.Core.unit   type: unit   implements: System.IComparable val text : string -> unit XmlWriter Full name: Formlets.XmlWriter.text val s : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable   implements: seq   implements: System.Collections.IEnumerable   implements: System.IEquatable val tag : string -> (string * string) list -> xml_item list * 'a -> 'a XmlWriter Full name: Formlets.XmlWriter.tag val t : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable   implements: seq   implements: System.Collections.IEnumerable   implements: System.IEquatable val attr : (string * string) list   type: (string * string) list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable   implements: System.Collections.IEnumerable val v : 'a XmlWriter val x : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable   implements: System.Collections.IEnumerable namespace System.Xml namespace System.Xml.Linq val render : xml_item list -> XDocument Full name: Formlets.XmlWriter.render val xml : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable   implements: System.Collections.IEnumerable type XDocument =   class     inherit System.Xml.Linq.XContainer     new : unit -> System.Xml.Linq.XDocument     new : obj [] -> System.Xml.Linq.XDocument     new : System.Xml.Linq.XDeclaration * obj [] -> System.Xml.Linq.XDocument     new : System.Xml.Linq.XDocument -> System.Xml.Linq.XDocument     member Declaration : System.Xml.Linq.XDeclaration with get, set     member DocumentType : System.Xml.Linq.XDocumentType     member NodeType : System.Xml.XmlNodeType     member Root : System.Xml.Linq.XElement     member Save : string -> unit     member Save : System.IO.TextWriter -> unit     member Save : System.Xml.XmlWriter -> unit     member Save : string * System.Xml.Linq.SaveOptions -> unit     member Save : System.IO.TextWriter * System.Xml.Linq.SaveOptions -> unit     member WriteTo : System.Xml.XmlWriter -> unit     static member Load : string -> System.Xml.Linq.XDocument     static member Load : System.IO.TextReader -> System.Xml.Linq.XDocument     static member Load : System.Xml.XmlReader -> System.Xml.Linq.XDocument     static member Load : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument     static member Load : System.IO.TextReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument     static member Load : System.Xml.XmlReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument     static member Parse : string -> System.Xml.Linq.XDocument     static member Parse : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument   end Full name: System.Xml.Linq.XDocument   type: XDocument   implements: System.Xml.IXmlLineInfo   inherits: XContainer   inherits: XNode   inherits: XObject type XName =   class     member Equals : obj -> bool     member GetHashCode : unit -> int     member LocalName : string     member Namespace : System.Xml.Linq.XNamespace     member NamespaceName : string     member ToString : unit -> string     static member Get : string -> System.Xml.Linq.XName     static member Get : string * string -> System.Xml.Linq.XName   end Full name: System.Xml.Linq.XName   type: XName   implements: System.IEquatable   implements: System.Runtime.Serialization.ISerializable XName.op_Implicit(expandedName: string) : XName val xtext : (string -> XObject) type XText =   class     inherit System.Xml.Linq.XNode     new : string -> System.Xml.Linq.XText     new : System.Xml.Linq.XText -> System.Xml.Linq.XText     member NodeType : System.Xml.XmlNodeType     member Value : string with get, set     member WriteTo : System.Xml.XmlWriter -> unit   end Full name: System.Xml.Linq.XText   type: XText   implements: System.Xml.IXmlLineInfo   inherits: XNode   inherits: XObject type XObject =   class     member AddAnnotation : obj -> unit     member Annotation<'T> : unit -> 'T     member Annotation : System.Type -> obj     member Annotations<'T> : unit -> System.Collections.Generic.IEnumerable<'T>     member Annotations : System.Type -> System.Collections.Generic.IEnumerable     member BaseUri : string     member Document : System.Xml.Linq.XDocument     member NodeType : System.Xml.XmlNodeType     member Parent : System.Xml.Linq.XElement     member RemoveAnnotations<'T> : unit -> unit     member RemoveAnnotations : System.Type -> unit   end Full name: System.Xml.Linq.XObject   type: XObject   implements: System.Xml.IXmlLineInfo val xattr : (string * 'a -> XObject) val name : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable   implements: seq   implements: System.Collections.IEnumerable   implements: System.IEquatable val value : 'a type XAttribute =   class     inherit System.Xml.Linq.XObject     new : System.Xml.Linq.XName * obj -> System.Xml.Linq.XAttribute     new : System.Xml.Linq.XAttribute -> System.Xml.Linq.XAttribute     member IsNamespaceDeclaration : bool     member Name : System.Xml.Linq.XName     member NextAttribute : System.Xml.Linq.XAttribute     member NodeType : System.Xml.XmlNodeType     member PreviousAttribute : System.Xml.Linq.XAttribute     member Remove : unit -> unit     member SetValue : obj -> unit     member ToString : unit -> string     member Value : string with get, set     static member EmptySequence : System.Collections.Generic.IEnumerable   end Full name: System.Xml.Linq.XAttribute   type: XAttribute   implements: System.Xml.IXmlLineInfo   inherits: XObject val xattrs : ((string * 'a) list -> XObject list) val attr : (string * 'a) list   type: (string * 'a) list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable   implements: System.Collections.IEnumerable val map : ('T -> 'U) -> 'T list -> 'U list Full name: Microsoft.FSharp.Collections.List.map val xelem : (string -> 'a list -> 'a list -> XObject) val attr : 'a list   type: 'a list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'a>   implements: System.Collections.IEnumerable val children : 'a list   type: 'a list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'a>   implements: System.Collections.IEnumerable type XElement =   class     inherit System.Xml.Linq.XContainer     new : System.Xml.Linq.XName -> System.Xml.Linq.XElement     new : System.Xml.Linq.XName * obj -> System.Xml.Linq.XElement     new : System.Xml.Linq.XName * obj [] -> System.Xml.Linq.XElement     new : System.Xml.Linq.XElement -> System.Xml.Linq.XElement     new : System.Xml.Linq.XStreamingElement -> System.Xml.Linq.XElement     member AncestorsAndSelf : unit -> System.Collections.Generic.IEnumerable     member AncestorsAndSelf : System.Xml.Linq.XName -> System.Collections.Generic.IEnumerable     member Attribute : System.Xml.Linq.XName -> System.Xml.Linq.XAttribute     member Attributes : unit -> System.Collections.Generic.IEnumerable     member Attributes : System.Xml.Linq.XName -> System.Collections.Generic.IEnumerable     member DescendantNodesAndSelf : unit -> System.Collections.Generic.IEnumerable     member DescendantsAndSelf : unit -> System.Collections.Generic.IEnumerable     member DescendantsAndSelf : System.Xml.Linq.XName -> System.Collections.Generic.IEnumerable     member FirstAttribute : System.Xml.Linq.XAttribute     member GetDefaultNamespace : unit -> System.Xml.Linq.XNamespace     member GetNamespaceOfPrefix : string -> System.Xml.Linq.XNamespace     member GetPrefixOfNamespace : System.Xml.Linq.XNamespace -> string     member HasAttributes : bool     member HasElements : bool     member IsEmpty : bool     member LastAttribute : System.Xml.Linq.XAttribute     member Name : System.Xml.Linq.XName with get, set     member NodeType : System.Xml.XmlNodeType     member RemoveAll : unit -> unit     member RemoveAttributes : unit -> unit     member ReplaceAll : obj -> unit     member ReplaceAll : obj [] -> unit     member ReplaceAttributes : obj -> unit     member ReplaceAttributes : obj [] -> unit     member Save : string -> unit     member Save : System.IO.TextWriter -> unit     member Save : System.Xml.XmlWriter -> unit     member Save : string * System.Xml.Linq.SaveOptions -> unit     member Save : System.IO.TextWriter * System.Xml.Linq.SaveOptions -> unit     member SetAttributeValue : System.Xml.Linq.XName * obj -> unit     member SetElementValue : System.Xml.Linq.XName * obj -> unit     member SetValue : obj -> unit     member Value : string with get, set     member WriteTo : System.Xml.XmlWriter -> unit     static member EmptySequence : System.Collections.Generic.IEnumerable     static member Load : string -> System.Xml.Linq.XElement     static member Load : System.IO.TextReader -> System.Xml.Linq.XElement     static member Load : System.Xml.XmlReader -> System.Xml.Linq.XElement     static member Load : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement     static member Load : System.IO.TextReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement     static member Load : System.Xml.XmlReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement     static member Parse : string -> System.Xml.Linq.XElement     static member Parse : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement   end Full name: System.Xml.Linq.XElement   type: XElement   implements: System.Xml.IXmlLineInfo   implements: System.Xml.Serialization.IXmlSerializable   inherits: XContainer   inherits: XNode   inherits: XObject val renderForest : (xml_item list -> XObject list) val render' : (xml_item -> XObject) val children : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable   implements: System.Collections.IEnumerable val root : XObject   type: XObject   implements: System.Xml.IXmlLineInfo val puree : 'a -> 'a Environ XmlWriter Full name: Formlets.EnvironXmlWriter.puree Multiple items module Environ from Formlets -------------------- type 'a Environ = (string * string) list -> 'a Full name: Formlets.Environ<_> Multiple items module XmlWriter from Formlets -------------------- type 'a XmlWriter = xml_item list * 'a Full name: Formlets.XmlWriter<_> val f : ('a -> 'b) Environ XmlWriter val a : 'a Environ XmlWriter val lift : ('a -> 'b) -> xml_item list * 'a Environ -> 'b Environ XmlWriter Full name: Formlets.EnvironXmlWriter.lift val refine : xml_item list * 'a -> 'a Environ XmlWriter Full name: Formlets.EnvironXmlWriter.refine val x : 'a XmlWriter type 'a Formlet = 'a Environ XmlWriter NameGen Full name: Formlets.Formlet<_> Multiple items module NameGen from Formlets -------------------- type 'a NameGen = int -> 'a * int Full name: Formlets.NameGen<_> type AutoOpenAttribute =   class     inherit System.Attribute     new : unit -> AutoOpenAttribute     new : path:string -> AutoOpenAttribute     member Path : string   end Full name: Microsoft.FSharp.Core.AutoOpenAttribute   type: AutoOpenAttribute   implements: System.Runtime.InteropServices._Attribute   inherits: System.Attribute Multiple items module Formlet from Formlets -------------------- type 'a Formlet = 'a Environ XmlWriter NameGen Full name: Formlets.Formlet<_> val puree : 'a -> 'a Formlet Full name: Formlets.Formlet.puree module EnvironXmlWriter from Formlets val f : ('a -> 'b) Formlet val a : 'a Formlet val lift : ('a -> 'b) -> 'a Formlet -> 'b Formlet Full name: Formlets.Formlet.lift val lift2 : ('a -> 'b -> 'c) -> 'a Formlet -> 'b Formlet -> 'c Formlet Full name: Formlets.Formlet.lift2 val b : 'b Formlet val f : 'a Formlet val a : 'b Formlet val z : 'b val z : 'a val xml : xml_item list -> unit Formlet Full name: Formlets.Formlet.xml val text : string -> unit Formlet Full name: Formlets.Formlet.text val tag : string -> (string * string) list -> 'a Formlet -> 'a Formlet Full name: Formlets.Formlet.tag val input : string Formlet Full name: Formlets.Formlet.input val xml : (string -> 'a XmlWriter -> 'a XmlWriter) val lookup : (string -> string Environ XmlWriter) val tag : (string -> string Environ XmlWriter) val br : unit Formlet Full name: Formlets.Formlet.br val run : 'a Formlet -> 'a Environ Full name: Formlets.Formlet.run val render : 'a Formlet -> XDocument Full name: Formlets.Formlet.render [Less]
Posted about 14 years ago by [email protected] (Mauricio Scheffer)
In my last post I showed a bare bones implementation of formlets. You may have noticed that formlets do a lot of stuff: Generating form element names Handling submitted values Building and rendering HTML That's a lot of ... [More] responsibilities for a single module. Even the type of a formlet reflects this, it's pretty complex: type 'a Formlet = int -> (xml_item list * ((string * string) list -> 'a) * int) And we haven't even looked at adding validation, which would complicate things further. Bottom line: there is no clear separation of concerns. The good news are: we already have identified those concerns (the bulleted list above), and applicative functors are easily composable, so we can model each of those concerns as independent applicative functors, then compose them to yield formlets. HTML building As usual, we start by defining types: the same definition for XML trees: type xml_item = | Text of string | Tag of string * (string*string) list * xml_item list And the type of the applicative functor itself: type 'a XmlWriter = xml_item list * 'a A XML forest and something else. That 'something else' is what will enable the composition of this applicative with others. Finally, the implementation. If you compare this with the implementation of formlets described in the previous post, function by function, you'll see that this is effectively a stripped down applicative that only deals with XML. module XmlWriter = let puree (v: 'a) : 'a XmlWriter = [],v let (<*>) (f: ('a -> 'b) XmlWriter) (a: 'a XmlWriter) : 'b XmlWriter = fst f @ fst a, (snd f) (snd a) let lift f a = puree f <*> a let lift2 f a b = puree f <*> a <*> b let plug (f: xml_item list -> xml_item list) (a: 'a XmlWriter) : 'a XmlWriter = f (fst a), snd a let xml (e: xml_item list): unit XmlWriter = e,() let text (s: string) : unit XmlWriter = xml [Text s] let tag (t: string) (attr: (string*string) list) (v: 'a XmlWriter) : 'a XmlWriter = plug (fun x -> [Tag(t, attr, x)]) v open System.Xml.Linq let render (xml: xml_item list) : XDocument = let (!!) t = XName.op_Implicit t let xtext (s: string) = XText s :> XObject let xattr (name, value) = XAttribute(!!name, value) :> XObject let xattrs attr = List.map xattr attr let xelem name attr children = XElement(!!name, attr @ children) :> XObject let rec renderForest x = let render' = function | Text t -> xtext t | Tag(name, attr, children) -> xelem name (xattrs attr) (renderForest children) List.map render' x let root = xelem "div" [] (renderForest xml) XDocument root The only additional function here is plug, which maps the XML forest in a XmlWriter applicative. It's not of much use here and could be inlined, but it will be useful later to implement validation (in a future post). Environment handling The environment (or collector) applicative is one that fetches (collects) a value from the environment (usually Request.Form in ASP.NET). As such, its type is type 'a Environ = (string*string) list -> 'a The implementation: module Environ = let puree v = fun env -> v let (<*>) (f: ('a -> 'b) Environ) (a: 'a Environ) : 'b Environ = fun env -> let g = f env g(a(env)) let lift f a = puree f <*> a let lookup (n: string) : string Environ = fun env -> match List.tryFind (fun (k,_) -> k = n) env with | Some (_,v) -> v | _ -> failwithf "Key %s not found in environment" n Note the lookup function, it's an Environ constructor that implements the core functionality of "given this key and this environment, give me the corresponding value". It's pretty much a dictionary in applicative form. We'll also add here a helper function to convert a NameValueCollection (like Request.Form) to an environment: open System.Collections.Specialized let fromNV (a: NameValueCollection) = a.AllKeys |> Seq.collect (fun k -> a.GetValues k |> Seq.map (fun v -> k,v)) |> Seq.toList Name generation We've seen how formlets automatically generate form element names. While the actual generation is provided by nextName, the counter used is carried over from application to application, since this is purely functional code, without state: type 'a NameGen = int -> 'a * int module NameGen = let puree (v : 'a) : 'a NameGen = fun gen -> v,gen let (<*>) (f: ('a -> 'b) NameGen) (a: 'a NameGen) : 'b NameGen = fun gen -> let v,gen = f gen let w,gen = a gen v w, gen let lift f a = puree f <*> a let lift2 f a b = puree f <*> a <*> b let nextName : string NameGen = fun gen -> "input_" gen.ToString(), gen 1 let run (c: 'a NameGen) : 'a = fst (c 0) Here's a simple example of name generation: > open NameGen;; > let names = puree (printfn "%s %s") <*> nextName <*> nextName;; > run names;; input_0 input_1 Composing applicatives Applicative functors are easily composable, and the composition of any two applicatives is an applicative (math people say they're closed under composition). Interestingly, monads are not closed under composition, which as far as I understand is the reason for having monad transformers, but this doesn't seem to be a problem in F# since monads aren't as ubiquitous as in Haskell anyway. Composing two applicatives is as simple as applying one applicative's pure to the other applicative's pure, and lifting <*>. Again, since we can't abstract type constructors, we can't perform composition generically, but we can always do it ad-hoc. type 'a EnvironXmlWriter = 'a Environ XmlWriter Expanding the types, this means type 'a EnvironXmlWriter = xml_item list * ((string * string) list -> 'a) The implementation: module EnvironXmlWriter = let puree (v: 'a) : 'a Environ XmlWriter = v |> Environ.puree |> XmlWriter.puree let (<*>) (f: ('a -> 'b) Environ XmlWriter) (a: 'a Environ XmlWriter) : 'b Environ XmlWriter = XmlWriter.lift2 Environ.(<*>) f a let lift f a = puree f <*> a We'll also add a refine function that lets us build the composition from the "wrapping" applicative (in our case XmlWriter). Building it from the nested applicative is as easy as applying XmlWriter.pure. let refine (x: 'a XmlWriter) : 'a Environ XmlWriter = XmlWriter.lift Environ.puree x Example: let input : string Environ XmlWriter = let xml = [Tag("input",["name","firstname"],[])] let lookup = Environ.lookup "firstname" xml,lookup This is almost a formlet. Just like a fully-fledged formlet, it has a XML part that can be rendered, and an associated collector. The only thing missing here is the automatic name generation applied to both the XML element and the collector. Finally Formlets All we have to do now is compose EnvironXmlWriter with NameGen to yield Formlets. Its type is: type 'a Formlet = 'a EnvironXmlWriter NameGen or type 'a Formlet = 'a Environ XmlWriter NameGen or if we expand each type: type 'a Formlet = int -> (xml_item list * ((string * string) list -> 'a) * int) which is exactly what we had in the last post. As you can see, factoring it to primitive applicatives has given us a clearer definition. The implementation of formlets is almost trivial now: module Formlet = let puree v : _ Formlet = v |> EnvironXmlWriter.puree |> NameGen.puree let (<*>) (f: ('a -> 'b) Formlet) (a: 'a Formlet) : 'b Formlet = NameGen.lift2 EnvironXmlWriter.(<*>) f a let lift f a : _ Formlet = puree f <*> a let lift2 f a b : _ Formlet = puree f <*> a <*> b let ( *>) f a : _ Formlet = lift2 (fun _ z -> z) f a let ( <*) f a : _ Formlet = lift2 (fun z _ -> z) f a let xml (x: xml_item list) : unit Formlet = NameGen.puree (EnvironXmlWriter.refine (XmlWriter.xml x)) let text (s: string) : unit Formlet = xml [Text s] let tag (t: string) (attr: (string*string) list) (f: 'a Formlet) : 'a Formlet = NameGen.lift (XmlWriter.tag t attr) f let input : string Formlet = let xml name = XmlWriter.tag "input" ["name",name] let lookup name = XmlWriter.puree (Environ.lookup name) let tag name = xml name (lookup name) NameGen.lift tag NameGen.nextName let br: unit Formlet = xml [Tag("br",[],[])] let run (f: 'a Formlet) : 'a Environ = NameGen.run f |> snd open System.Xml.Linq let render (f: _ Formlet) = NameGen.run f |> fst |> XmlWriter.render F# Web Snippets Factoring formlets like this not only has the advantage of producing cleaner code, but also enables the reusability of each applicative functor and extending formlets more easily, as we'll see in a future post. Full source code is here. This implementation is interchangeable with the one in my last post, the project includes both implementations so you can compare them side by side. Most of this code was taken almost verbatim from the original papers on formlets. namespace Formlets Multiple items val int : 'T -> int (requires member op_Explicit) Full name: Microsoft.FSharp.Core.Operators.int -------------------- type int<'Measure> = int Full name: Microsoft.FSharp.Core.int<_>   type: int<'Measure>   implements: System.IComparable   implements: System.IConvertible   implements: System.IFormattable   implements: System.IComparable<int<'Measure>>   implements: System.IEquatable<int<'Measure>>   inherits: System.ValueType -------------------- type int = int32 Full name: Microsoft.FSharp.Core.int   type: int   implements: System.IComparable   implements: System.IFormattable   implements: System.IConvertible   implements: System.IComparable<int>   implements: System.IEquatable<int>   inherits: System.ValueType val puree : 'a -> int -> 'a * int Full name: Formlets.NameGen.puree val v : 'a type 'a NameGen = int -> 'a * int Full name: Formlets.NameGen<_> val gen : int   type: int   implements: System.IComparable   implements: System.IFormattable   implements: System.IConvertible   implements: System.IComparable<int>   implements: System.IEquatable<int>   inherits: System.ValueType val f : ('a -> 'b) NameGen val a : 'a NameGen val v : ('a -> 'b) val w : 'a val lift : ('a -> 'b) -> 'a NameGen -> 'b NameGen Full name: Formlets.NameGen.lift val f : ('a -> 'b) val lift2 : ('a -> 'b -> 'c) -> 'a NameGen -> 'b NameGen -> 'c NameGen Full name: Formlets.NameGen.lift2 val f : ('a -> 'b -> 'c) val b : 'b NameGen val nextName : int -> string * int Full name: Formlets.NameGen.nextName Multiple items val string : 'T -> string Full name: Microsoft.FSharp.Core.Operators.string -------------------- type string = System.String Full name: Microsoft.FSharp.Core.string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> Multiple overloads System.Object.ToString() : string System.Int32.ToString(provider: System.IFormatProvider) : string System.Int32.ToString(format: string) : string System.Int32.ToString(format: string, provider: System.IFormatProvider) : string val run : 'a NameGen -> 'a Full name: Formlets.NameGen.run val c : 'a NameGen val fst : ('T1 * 'T2) -> 'T1 Full name: Microsoft.FSharp.Core.Operators.fst type 'a Environ = (string * string) list -> 'a Full name: Formlets.Environ<_> type 'T list = List<'T> Full name: Microsoft.FSharp.Collections.list<_>   type: 'T list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<'T>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'T>   implements: System.Collections.IEnumerable val puree : 'a -> 'b -> 'a Full name: Formlets.Environ.puree val env : 'b val f : ('a -> 'b) Environ val a : 'a Environ val env : (string * string) list   type: (string * string) list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<string * string>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<string * string>   implements: System.Collections.IEnumerable val g : ('a -> 'b) val lift : ('a -> 'b) -> 'a Environ -> 'b Environ Full name: Formlets.Environ.lift val lookup : string -> (string * string) list -> string Full name: Formlets.Environ.lookup val n : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> Multiple items module List from Microsoft.FSharp.Collections -------------------- type List<'T> =   | ( [] )   | ( :: ) of 'T * 'T list   with     interface System.Collections.IEnumerable     interface System.Collections.Generic.IEnumerable<'T>     member Head : 'T     member IsEmpty : bool     member Item : index:int -> 'T with get     member Length : int     member Tail : 'T list     static member Cons : head:'T * tail:'T list -> 'T list     static member Empty : 'T list   end Full name: Microsoft.FSharp.Collections.List<_>   type: List<'T>   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<'T>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'T>   implements: System.Collections.IEnumerable val tryFind : ('T -> bool) -> 'T list -> 'T option Full name: Microsoft.FSharp.Collections.List.tryFind val k : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> union case Option.Some: 'T -> Option<'T> val v : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> val failwithf : Printf.StringFormat<'T,'Result> -> 'T Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.failwithf namespace System namespace System.Collections namespace System.Collections.Specialized val fromNV : NameValueCollection -> (string * string) list Full name: Formlets.Environ.fromNV val a : NameValueCollection   type: NameValueCollection   implements: System.Collections.ICollection   implements: System.Collections.IEnumerable   implements: System.Runtime.Serialization.ISerializable   implements: System.Runtime.Serialization.IDeserializationCallback   inherits: NameObjectCollectionBase type NameValueCollection =   class     inherit System.Collections.Specialized.NameObjectCollectionBase     new : unit -> System.Collections.Specialized.NameValueCollection     new : System.Collections.Specialized.NameValueCollection -> System.Collections.Specialized.NameValueCollection     new : System.Collections.IHashCodeProvider * System.Collections.IComparer -> System.Collections.Specialized.NameValueCollection     new : int -> System.Collections.Specialized.NameValueCollection     new : System.Collections.IEqualityComparer -> System.Collections.Specialized.NameValueCollection     new : int * System.Collections.IEqualityComparer -> System.Collections.Specialized.NameValueCollection     new : int * System.Collections.Specialized.NameValueCollection -> System.Collections.Specialized.NameValueCollection     new : int * System.Collections.IHashCodeProvider * System.Collections.IComparer -> System.Collections.Specialized.NameValueCollection     member Add : System.Collections.Specialized.NameValueCollection -> unit     member Add : string * string -> unit     member AllKeys : string []     member Clear : unit -> unit     member CopyTo : System.Array * int -> unit     member Get : string -> string     member Get : int -> string     member GetKey : int -> string     member GetValues : string -> string []     member GetValues : int -> string []     member HasKeys : unit -> bool     member Item : string -> string with get, set     member Item : int -> string     member Remove : string -> unit     member Set : string * string -> unit   end Full name: System.Collections.Specialized.NameValueCollection   type: NameValueCollection   implements: System.Collections.ICollection   implements: System.Collections.IEnumerable   implements: System.Runtime.Serialization.ISerializable   implements: System.Runtime.Serialization.IDeserializationCallback   inherits: NameObjectCollectionBase property NameValueCollection.AllKeys: string [] module Seq from Microsoft.FSharp.Collections val collect : ('T -> #seq<'U>) -> seq<'T> -> seq<'U> Full name: Microsoft.FSharp.Collections.Seq.collect Multiple overloads NameValueCollection.GetValues(index: int) : string [] NameValueCollection.GetValues(name: string) : string [] val map : ('T -> 'U) -> seq<'T> -> seq<'U> Full name: Microsoft.FSharp.Collections.Seq.map val toList : seq<'T> -> 'T list Full name: Microsoft.FSharp.Collections.Seq.toList type xml_item =   | Text of string   | Tag of string * (string * string) list * xml_item list Full name: Formlets.xml_item   type: xml_item   implements: System.IEquatable<xml_item>   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<xml_item>   implements: System.IComparable   implements: System.Collections.IStructuralComparable union case xml_item.Text: string -> xml_item union case xml_item.Tag: string * (string * string) list * xml_item list -> xml_item type 'a XmlWriter = xml_item list * 'a Full name: Formlets.XmlWriter<_> val puree : 'a -> 'a XmlWriter Full name: Formlets.XmlWriter.puree val f : ('a -> 'b) XmlWriter val a : 'a XmlWriter val snd : ('T1 * 'T2) -> 'T2 Full name: Microsoft.FSharp.Core.Operators.snd val lift : ('a -> 'b) -> xml_item list * 'a -> 'b XmlWriter Full name: Formlets.XmlWriter.lift val lift2 : ('a -> 'b -> 'c) -> xml_item list * 'a -> xml_item list * 'b -> 'c XmlWriter Full name: Formlets.XmlWriter.lift2 val b : 'b XmlWriter val plug : (xml_item list -> xml_item list) -> xml_item list * 'a -> 'a XmlWriter Full name: Formlets.XmlWriter.plug val f : (xml_item list -> xml_item list) val xml : xml_item list -> unit XmlWriter Full name: Formlets.XmlWriter.xml val e : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<xml_item>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<xml_item>   implements: System.Collections.IEnumerable type unit = Unit Full name: Microsoft.FSharp.Core.unit   type: unit   implements: System.IComparable val text : string -> unit XmlWriter Full name: Formlets.XmlWriter.text val s : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> val tag : string -> (string * string) list -> xml_item list * 'a -> 'a XmlWriter Full name: Formlets.XmlWriter.tag val t : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> val attr : (string * string) list   type: (string * string) list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<string * string>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<string * string>   implements: System.Collections.IEnumerable val v : 'a XmlWriter val x : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<xml_item>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<xml_item>   implements: System.Collections.IEnumerable namespace System.Xml namespace System.Xml.Linq val render : xml_item list -> XDocument Full name: Formlets.XmlWriter.render val xml : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<xml_item>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<xml_item>   implements: System.Collections.IEnumerable type XDocument =   class     inherit System.Xml.Linq.XContainer     new : unit -> System.Xml.Linq.XDocument     new : obj [] -> System.Xml.Linq.XDocument     new : System.Xml.Linq.XDeclaration * obj [] -> System.Xml.Linq.XDocument     new : System.Xml.Linq.XDocument -> System.Xml.Linq.XDocument     member Declaration : System.Xml.Linq.XDeclaration with get, set     member DocumentType : System.Xml.Linq.XDocumentType     member NodeType : System.Xml.XmlNodeType     member Root : System.Xml.Linq.XElement     member Save : string -> unit     member Save : System.IO.TextWriter -> unit     member Save : System.Xml.XmlWriter -> unit     member Save : string * System.Xml.Linq.SaveOptions -> unit     member Save : System.IO.TextWriter * System.Xml.Linq.SaveOptions -> unit     member WriteTo : System.Xml.XmlWriter -> unit     static member Load : string -> System.Xml.Linq.XDocument     static member Load : System.IO.TextReader -> System.Xml.Linq.XDocument     static member Load : System.Xml.XmlReader -> System.Xml.Linq.XDocument     static member Load : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument     static member Load : System.IO.TextReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument     static member Load : System.Xml.XmlReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument     static member Parse : string -> System.Xml.Linq.XDocument     static member Parse : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument   end Full name: System.Xml.Linq.XDocument   type: XDocument   implements: System.Xml.IXmlLineInfo   inherits: XContainer   inherits: XNode   inherits: XObject type XName =   class     member Equals : obj -> bool     member GetHashCode : unit -> int     member LocalName : string     member Namespace : System.Xml.Linq.XNamespace     member NamespaceName : string     member ToString : unit -> string     static member Get : string -> System.Xml.Linq.XName     static member Get : string * string -> System.Xml.Linq.XName   end Full name: System.Xml.Linq.XName   type: XName   implements: System.IEquatable<XName>   implements: System.Runtime.Serialization.ISerializable XName.op_Implicit(expandedName: string) : XName val xtext : (string -> XObject) type XText =   class     inherit System.Xml.Linq.XNode     new : string -> System.Xml.Linq.XText     new : System.Xml.Linq.XText -> System.Xml.Linq.XText     member NodeType : System.Xml.XmlNodeType     member Value : string with get, set     member WriteTo : System.Xml.XmlWriter -> unit   end Full name: System.Xml.Linq.XText   type: XText   implements: System.Xml.IXmlLineInfo   inherits: XNode   inherits: XObject type XObject =   class     member AddAnnotation : obj -> unit     member Annotation<'T> : unit -> 'T     member Annotation : System.Type -> obj     member Annotations<'T> : unit -> System.Collections.Generic.IEnumerable<'T>     member Annotations : System.Type -> System.Collections.Generic.IEnumerable<obj>     member BaseUri : string     member Document : System.Xml.Linq.XDocument     member NodeType : System.Xml.XmlNodeType     member Parent : System.Xml.Linq.XElement     member RemoveAnnotations<'T> : unit -> unit     member RemoveAnnotations : System.Type -> unit   end Full name: System.Xml.Linq.XObject   type: XObject   implements: System.Xml.IXmlLineInfo val xattr : (string * 'a -> XObject) val name : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> val value : 'a type XAttribute =   class     inherit System.Xml.Linq.XObject     new : System.Xml.Linq.XName * obj -> System.Xml.Linq.XAttribute     new : System.Xml.Linq.XAttribute -> System.Xml.Linq.XAttribute     member IsNamespaceDeclaration : bool     member Name : System.Xml.Linq.XName     member NextAttribute : System.Xml.Linq.XAttribute     member NodeType : System.Xml.XmlNodeType     member PreviousAttribute : System.Xml.Linq.XAttribute     member Remove : unit -> unit     member SetValue : obj -> unit     member ToString : unit -> string     member Value : string with get, set     static member EmptySequence : System.Collections.Generic.IEnumerable<System.Xml.Linq.XAttribute>   end Full name: System.Xml.Linq.XAttribute   type: XAttribute   implements: System.Xml.IXmlLineInfo   inherits: XObject val xattrs : ((string * 'a) list -> XObject list) val attr : (string * 'a) list   type: (string * 'a) list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<string * 'a>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<string * 'a>   implements: System.Collections.IEnumerable val map : ('T -> 'U) -> 'T list -> 'U list Full name: Microsoft.FSharp.Collections.List.map val xelem : (string -> 'a list -> 'a list -> XObject) val attr : 'a list   type: 'a list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<'a>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'a>   implements: System.Collections.IEnumerable val children : 'a list   type: 'a list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<'a>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'a>   implements: System.Collections.IEnumerable type XElement =   class     inherit System.Xml.Linq.XContainer     new : System.Xml.Linq.XName -> System.Xml.Linq.XElement     new : System.Xml.Linq.XName * obj -> System.Xml.Linq.XElement     new : System.Xml.Linq.XName * obj [] -> System.Xml.Linq.XElement     new : System.Xml.Linq.XElement -> System.Xml.Linq.XElement     new : System.Xml.Linq.XStreamingElement -> System.Xml.Linq.XElement     member AncestorsAndSelf : unit -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>     member AncestorsAndSelf : System.Xml.Linq.XName -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>     member Attribute : System.Xml.Linq.XName -> System.Xml.Linq.XAttribute     member Attributes : unit -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XAttribute>     member Attributes : System.Xml.Linq.XName -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XAttribute>     member DescendantNodesAndSelf : unit -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XNode>     member DescendantsAndSelf : unit -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>     member DescendantsAndSelf : System.Xml.Linq.XName -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>     member FirstAttribute : System.Xml.Linq.XAttribute     member GetDefaultNamespace : unit -> System.Xml.Linq.XNamespace     member GetNamespaceOfPrefix : string -> System.Xml.Linq.XNamespace     member GetPrefixOfNamespace : System.Xml.Linq.XNamespace -> string     member HasAttributes : bool     member HasElements : bool     member IsEmpty : bool     member LastAttribute : System.Xml.Linq.XAttribute     member Name : System.Xml.Linq.XName with get, set     member NodeType : System.Xml.XmlNodeType     member RemoveAll : unit -> unit     member RemoveAttributes : unit -> unit     member ReplaceAll : obj -> unit     member ReplaceAll : obj [] -> unit     member ReplaceAttributes : obj -> unit     member ReplaceAttributes : obj [] -> unit     member Save : string -> unit     member Save : System.IO.TextWriter -> unit     member Save : System.Xml.XmlWriter -> unit     member Save : string * System.Xml.Linq.SaveOptions -> unit     member Save : System.IO.TextWriter * System.Xml.Linq.SaveOptions -> unit     member SetAttributeValue : System.Xml.Linq.XName * obj -> unit     member SetElementValue : System.Xml.Linq.XName * obj -> unit     member SetValue : obj -> unit     member Value : string with get, set     member WriteTo : System.Xml.XmlWriter -> unit     static member EmptySequence : System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>     static member Load : string -> System.Xml.Linq.XElement     static member Load : System.IO.TextReader -> System.Xml.Linq.XElement     static member Load : System.Xml.XmlReader -> System.Xml.Linq.XElement     static member Load : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement     static member Load : System.IO.TextReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement     static member Load : System.Xml.XmlReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement     static member Parse : string -> System.Xml.Linq.XElement     static member Parse : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement   end Full name: System.Xml.Linq.XElement   type: XElement   implements: System.Xml.IXmlLineInfo   implements: System.Xml.Serialization.IXmlSerializable   inherits: XContainer   inherits: XNode   inherits: XObject val renderForest : (xml_item list -> XObject list) val render' : (xml_item -> XObject) val children : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<xml_item>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<xml_item>   implements: System.Collections.IEnumerable val root : XObject   type: XObject   implements: System.Xml.IXmlLineInfo val puree : 'a -> 'a Environ XmlWriter Full name: Formlets.EnvironXmlWriter.puree Multiple items module Environ from Formlets -------------------- type 'a Environ = (string * string) list -> 'a Full name: Formlets.Environ<_> Multiple items module XmlWriter from Formlets -------------------- type 'a XmlWriter = xml_item list * 'a Full name: Formlets.XmlWriter<_> val f : ('a -> 'b) Environ XmlWriter val a : 'a Environ XmlWriter val lift : ('a -> 'b) -> xml_item list * 'a Environ -> 'b Environ XmlWriter Full name: Formlets.EnvironXmlWriter.lift val refine : xml_item list * 'a -> 'a Environ XmlWriter Full name: Formlets.EnvironXmlWriter.refine val x : 'a XmlWriter type 'a Formlet = 'a Environ XmlWriter NameGen Full name: Formlets.Formlet<_> Multiple items module NameGen from Formlets -------------------- type 'a NameGen = int -> 'a * int Full name: Formlets.NameGen<_> type AutoOpenAttribute =   class     inherit System.Attribute     new : unit -> AutoOpenAttribute     new : path:string -> AutoOpenAttribute     member Path : string   end Full name: Microsoft.FSharp.Core.AutoOpenAttribute   type: AutoOpenAttribute   implements: System.Runtime.InteropServices._Attribute   inherits: System.Attribute Multiple items module Formlet from Formlets -------------------- type 'a Formlet = 'a Environ XmlWriter NameGen Full name: Formlets.Formlet<_> val puree : 'a -> 'a Formlet Full name: Formlets.Formlet.puree module EnvironXmlWriter from Formlets val f : ('a -> 'b) Formlet val a : 'a Formlet val lift : ('a -> 'b) -> 'a Formlet -> 'b Formlet Full name: Formlets.Formlet.lift val lift2 : ('a -> 'b -> 'c) -> 'a Formlet -> 'b Formlet -> 'c Formlet Full name: Formlets.Formlet.lift2 val b : 'b Formlet val f : 'a Formlet val a : 'b Formlet val z : 'b val z : 'a val xml : xml_item list -> unit Formlet Full name: Formlets.Formlet.xml val text : string -> unit Formlet Full name: Formlets.Formlet.text val tag : string -> (string * string) list -> 'a Formlet -> 'a Formlet Full name: Formlets.Formlet.tag val input : string Formlet Full name: Formlets.Formlet.input val xml : (string -> 'a XmlWriter -> 'a XmlWriter) val lookup : (string -> string Environ XmlWriter) val tag : (string -> string Environ XmlWriter) val br : unit Formlet Full name: Formlets.Formlet.br val run : 'a Formlet -> 'a Environ Full name: Formlets.Formlet.run val render : 'a Formlet -> XDocument Full name: Formlets.Formlet.render [Less]
Posted about 14 years ago by [email protected] (Mauricio Scheffer)
I've been blogging lately about functors in F#, in order to explain applicative functors, all so I could explain formlets now. Formlets are an abstraction of HTML forms. Formlets are composable, reusable, type-safe (as much as the host language is ... [More] type-safe anyway) and extensible. And they are described, of course, in terms of applicative functors.The concept of formlets was originally developed by Ezra Cooper, Sam Lindley, Philip Wadler, and Jeremy Yallop, as part of their research for Links, a new programming language oriented to web applications.Since its introduction in 2008, formlets proved to be a very popular concept, being ported to Haskell, Scheme (Racket), Common Lisp (Hunchentoot), Scala and JavaScript. If you regularly read the news about F# you probably know about WebSharper, a cool web framework for F#, which has formlets as one of its many features. Being a commercial product, WebSharper has great documentation on how to use formlets, but they only briefly mention the underlying concepts on how they work.Let's see a sample formlet in an ASP.NET MVC application:open System open System.Web open System.Web.Mvc open System.Collections.Generic [<HandleError>] type HomeController() = inherit Controller() let inputInt = lift int input let inputDate = puree (fun month day year -> DateTime(year,month,day)) <*> text "Month: " *> inputInt <*> text "Day: " *> inputInt <*> text "Year: " *> inputInt let formlet = puree (fun name order ship amount -> name,order,ship,amount) <*> text "Name: " *> input <* br <*> text "Order date: " *> inputDate <* br <*> text "Shipping date: " *> inputDate <* br <*> text "Qty: " *> inputInt <* br member x.Index() = x.ViewData.["Message"] <- "Welcome to ASP.NET MVC!" x.View(box <| render formlet) :> ActionResult member x.Register() = let env = fromNV x.Request.Form let name,orderDate,shippingDate,amount = run formlet env x.ViewData.["Name"] <- name x.ViewData.["Ordered"] <- orderDate x.ViewData.["Shipping"] <- shippingDate x.ViewData.["Amount"] <- amount x.View() :> ActionResult F# Web SnippetsIndex.aspx:<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> Home Page asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2><%: ViewData["Message"] %>h2> <p> <% using (Html.BeginForm("Register", "Home")) { %> <%= Model%> <input type="submit" value="Submit" /> <% } %> p> asp:Content> Register.aspx<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> Register asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <%: ViewData["Ordered"] %>: <%: ViewData["Name"] %> has requested <%: ViewData["Amount"] %> items to be delivered on <%: ViewData["Shipping"] %> asp:Content> You can hover over any value on the F# snippets in this post and get a tooltip with types, thanks to F# Web Snippets.If you read my previous articles on applicative functors you'll recognize the standard operations pure (or puree), <*> and lift. Note how the final formlet is composed from more basic formlets: one that models date input, which in turn uses an formlet to input integers. They all rely on a primitive input formlet (of type string Formlet) that models an . Also note that we didn't define the name attribute for any input; formlets generate them automatically and also match them when handling submitted values. Running the app shows a form like this:First thing that should spring to your mind when seeing this is "boy, this is ugly", otherwise I'm not doing my job right :) Let's see now, step by step, how formlets can be implemented. We start by defining the types. First, a simple model of XML to represent HTML:type xml_item = | Text of string | Tag of string * (string*string) list * xml_item list // name * attributes * childrenWe define an environment that will hold key-value pairs from submitted forms:type Env = (string*string) listAnd finally, the Formlet type:type 'a Formlet = int -> (xml_item list * (Env -> 'a) * int)Type signatures carry a lot of information, so let's take a moment to read this. It's a function that takes an int and returns a tuple of stuff. We'll see that the int it takes is used to generate the form element name. The tuple of stuff returned contains: an XML forest (representing HTML), a function we'll call collector, and an int that will be used to generate the next form element name. The collector is used to lookup the submitted value from the environment.A formlet is an applicative functor, so let's define puree:[<AutoOpen>] module Formlet = let puree (v: 'a) : 'a Formlet = fun i -> [], (fun _ -> v), iIt's an empty XML, a constant collector, and the same input i. Here's the definition of <*> :let (<*>) (f: ('a -> 'b) Formlet) (a: 'a Formlet) : 'b Formlet = fun i -> let x1,g,i = f i let x2,q,i = a i x1 @ x2, (fun env -> g env (q env)), iStep by step: we apply f, since it holds an 'a -> 'b, the collector g collects a function 'a -> 'b. Then we apply a. We return the concatenation of XML forests, the composition of collectors, and the final name seed.Now some standard functions for applicative functors:let lift f a = puree f <*> a let lift2 f a b : _ Formlet = puree f <*> a <*> b let ( *>) f a : _ Formlet = lift2 (fun _ z -> z) f a let ( <*) f a : _ Formlet = lift2 (fun z _ -> z) f aA couple of functions to lift pure markup and text:let xml (x: xml_item list) : unit Formlet = fun i -> x, (fun _ -> ()), i let text (t: string) : unit Formlet = xml [Text t]The tag function creates a tag that wraps a formlet:let tag (name: string) (attr: (string*string) list) (f: 'a Formlet) : 'a Formlet = fun i -> let xml,env,i = f i [Tag(name, attr, xml)],env,inextName is responsible for generating form element names:let nextName = fun i -> "input_" + i.ToString(), i+1Note that it returns an incremented counter, to be used in the following name. The input primitive:let input : string Formlet = fun i -> let name,i = nextName i let collector = List.find (fun (k,_) -> k = name) >> snd let tag = Tag("input", ["name",name], []) [tag], collector, iThe input formlet shows how everything fits together. It generates a fresh name to be used both in the collector and the input tag. This is what keeps the rendered form element and its respective collected value in sync.Here's a simple for formatting:let br: unit Formlet = xml [Tag("br",[],[])]The run function gets the collector of a formlet, which in turn will produce the collected value when applied to the environment: let run (f: 'a Formlet) : Env -> 'a = let _,e,_ = f 0 eThat zero is the seed for form element names. We render a formlet by mapping it to a System.Xml.Linq.XDocument: open System.Xml.Linq let render (f: 'a Formlet) : XDocument = let xml,_,_ = f 0 let (!!) t = XName.op_Implicit t let xtext (s: string) = XText s :> XObject let xattr (name, value) = XAttribute(!!name, value) :> XObject let xattrs attr = List.map xattr attr let xelem name attr children = XElement(!!name, attr @ children) :> XObject let rec renderForest x = let render' = function | Text t -> xtext t | Tag(name, attr, children) -> xelem name (xattrs attr) (renderForest children) List.map render' x let root = xelem "div" [] (renderForest xml) XDocument rootA helper function to make an Env from a NameValueCollection (such as Request.Form):open System.Collections.Specialized let fromNV (a: NameValueCollection) = a.AllKeys |> Seq.collect (fun k -> a.GetValues k |> Seq.map (fun v -> k,v)) |> Seq.toListEven though this was a bare bones implementation of formlets, I hope it served to illustrate how they work. This implementation was taken almost verbatim from the paper The Essence of Form Abstraction [PDF] by the original developers of Formlets mentioned at the beginning of this article. I kept it as simple and clear as possible, with many type annotations that weren't really necessary and intentionally cutting out features.Full source code is here.Next, we'll see how formlets can be factored to a composition of primitive applicatives.Read this article in Serbo-Croatian (translated by Anja Skrba from Webhostinggeeks.com) namespace Formlets union case xml_item.Text: string -> xml_item Multiple items val string : 'T -> string Full name: Microsoft.FSharp.Core.Operators.string -------------------- type string = System.String Full name: Microsoft.FSharp.Core.string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable   implements: seq   implements: System.Collections.IEnumerable   implements: System.IEquatable union case xml_item.Tag: string * (string * string) list * xml_item list -> xml_item type 'T list = List<'T> Full name: Microsoft.FSharp.Collections.list<_>   type: 'T list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'T>   implements: System.Collections.IEnumerable type xml_item =   | Text of string   | Tag of string * (string * string) list * xml_item list Full name: Formlets.xml_item   type: xml_item   implements: System.IEquatable   implements: System.Collections.IStructuralEquatable   implements: System.IComparable   implements: System.IComparable   implements: System.Collections.IStructuralComparable type Env = (string * string) list Full name: Formlets.Env   type: Env   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable   implements: System.Collections.IEnumerable type 'a Formlet = int -> xml_item list * (Env -> 'a) * int Full name: Formlets.Formlet<_> Multiple items val int : 'T -> int (requires member op_Explicit) Full name: Microsoft.FSharp.Core.Operators.int -------------------- type int<'Measure> = int Full name: Microsoft.FSharp.Core.int<_>   type: int<'Measure>   implements: System.IComparable   implements: System.IConvertible   implements: System.IFormattable   implements: System.IComparable>   implements: System.IEquatable>   inherits: System.ValueType -------------------- type int = int32 Full name: Microsoft.FSharp.Core.int   type: int   implements: System.IComparable   implements: System.IFormattable   implements: System.IConvertible   implements: System.IComparable   implements: System.IEquatable   inherits: System.ValueType type AutoOpenAttribute =   class     inherit System.Attribute     new : unit -> AutoOpenAttribute     new : path:string -> AutoOpenAttribute     member Path : string   end Full name: Microsoft.FSharp.Core.AutoOpenAttribute   type: AutoOpenAttribute   implements: System.Runtime.InteropServices._Attribute   inherits: System.Attribute val puree : 'a -> int -> xml_item list * (Env -> 'a) * int Full name: Formlets.Formlet.puree val v : 'a val i : int   type: int   implements: System.IComparable   implements: System.IFormattable   implements: System.IConvertible   implements: System.IComparable   implements: System.IEquatable   inherits: System.ValueType val f : ('a -> 'b) Formlet val a : 'a Formlet val x1 : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable   implements: System.Collections.IEnumerable val g : (Env -> 'a -> 'b) val x2 : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable   implements: System.Collections.IEnumerable val q : (Env -> 'a) val env : Env   type: Env   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable   implements: System.Collections.IEnumerable val lift : ('a -> 'b) -> 'a Formlet -> 'b Formlet Full name: Formlets.Formlet.lift val f : ('a -> 'b) val lift2 : ('a -> 'b -> 'c) -> 'a Formlet -> 'b Formlet -> 'c Formlet Full name: Formlets.Formlet.lift2 val f : ('a -> 'b -> 'c) val b : 'b Formlet val f : 'a Formlet val a : 'b Formlet val z : 'b val z : 'a val xml : xml_item list -> int -> xml_item list * (Env -> unit) * int Full name: Formlets.Formlet.xml val x : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable   implements: System.Collections.IEnumerable type unit = Unit Full name: Microsoft.FSharp.Core.unit   type: unit   implements: System.IComparable val text : string -> unit Formlet Full name: Formlets.Formlet.text val t : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable   implements: seq   implements: System.Collections.IEnumerable   implements: System.IEquatable val tag : string -> (string * string) list -> 'a Formlet -> int -> xml_item list * (Env -> 'a) * int Full name: Formlets.Formlet.tag val name : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable   implements: seq   implements: System.Collections.IEnumerable   implements: System.IEquatable val attr : (string * string) list   type: (string * string) list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable   implements: System.Collections.IEnumerable val xml : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable   implements: System.Collections.IEnumerable val env : (Env -> 'a) val nextName : int -> string * int Full name: Formlets.Formlet.nextName Multiple overloads System.Object.ToString() : string System.Int32.ToString(provider: System.IFormatProvider) : string System.Int32.ToString(format: string) : string System.Int32.ToString(format: string, provider: System.IFormatProvider) : string val input : int -> xml_item list * (Env -> string) * int Full name: Formlets.Formlet.input val lookup : ((string * string) list -> string) Multiple items module List from Microsoft.FSharp.Collections -------------------- type List<'T> =   | ( [] )   | ( :: ) of 'T * 'T list   with     interface System.Collections.IEnumerable     interface System.Collections.Generic.IEnumerable<'T>     member Head : 'T     member IsEmpty : bool     member Item : index:int -> 'T with get     member Length : int     member Tail : 'T list     static member Cons : head:'T * tail:'T list -> 'T list     static member Empty : 'T list   end Full name: Microsoft.FSharp.Collections.List<_>   type: List<'T>   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'T>   implements: System.Collections.IEnumerable val find : ('T -> bool) -> 'T list -> 'T Full name: Microsoft.FSharp.Collections.List.find val k : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable   implements: seq   implements: System.Collections.IEnumerable   implements: System.IEquatable val snd : ('T1 * 'T2) -> 'T2 Full name: Microsoft.FSharp.Core.Operators.snd val tag : xml_item   type: xml_item   implements: System.IEquatable   implements: System.Collections.IStructuralEquatable   implements: System.IComparable   implements: System.IComparable   implements: System.Collections.IStructuralComparable val br : unit Formlet Full name: Formlets.Formlet.br val run : 'a Formlet -> (Env -> 'a) Full name: Formlets.Formlet.run val e : (Env -> 'a) namespace System namespace System.Xml namespace System.Xml.Linq val render : 'a Formlet -> XDocument Full name: Formlets.Formlet.render type XDocument =   class     inherit System.Xml.Linq.XContainer     new : unit -> System.Xml.Linq.XDocument     new : obj [] -> System.Xml.Linq.XDocument     new : System.Xml.Linq.XDeclaration * obj [] -> System.Xml.Linq.XDocument     new : System.Xml.Linq.XDocument -> System.Xml.Linq.XDocument     member Declaration : System.Xml.Linq.XDeclaration with get, set     member DocumentType : System.Xml.Linq.XDocumentType     member NodeType : System.Xml.XmlNodeType     member Root : System.Xml.Linq.XElement     member Save : string -> unit     member Save : System.IO.TextWriter -> unit     member Save : System.Xml.XmlWriter -> unit     member Save : string * System.Xml.Linq.SaveOptions -> unit     member Save : System.IO.TextWriter * System.Xml.Linq.SaveOptions -> unit     member WriteTo : System.Xml.XmlWriter -> unit     static member Load : string -> System.Xml.Linq.XDocument     static member Load : System.IO.TextReader -> System.Xml.Linq.XDocument     static member Load : System.Xml.XmlReader -> System.Xml.Linq.XDocument     static member Load : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument     static member Load : System.IO.TextReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument     static member Load : System.Xml.XmlReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument     static member Parse : string -> System.Xml.Linq.XDocument     static member Parse : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument   end Full name: System.Xml.Linq.XDocument   type: XDocument   implements: System.Xml.IXmlLineInfo   inherits: XContainer   inherits: XNode   inherits: XObject type XName =   class     member Equals : obj -> bool     member GetHashCode : unit -> int     member LocalName : string     member Namespace : System.Xml.Linq.XNamespace     member NamespaceName : string     member ToString : unit -> string     static member Get : string -> System.Xml.Linq.XName     static member Get : string * string -> System.Xml.Linq.XName   end Full name: System.Xml.Linq.XName   type: XName   implements: System.IEquatable   implements: System.Runtime.Serialization.ISerializable XName.op_Implicit(expandedName: string) : XName val xtext : (string -> XObject) val s : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable   implements: seq   implements: System.Collections.IEnumerable   implements: System.IEquatable type XText =   class     inherit System.Xml.Linq.XNode     new : string -> System.Xml.Linq.XText     new : System.Xml.Linq.XText -> System.Xml.Linq.XText     member NodeType : System.Xml.XmlNodeType     member Value : string with get, set     member WriteTo : System.Xml.XmlWriter -> unit   end Full name: System.Xml.Linq.XText   type: XText   implements: System.Xml.IXmlLineInfo   inherits: XNode   inherits: XObject type XObject =   class     member AddAnnotation : obj -> unit     member Annotation<'T> : unit -> 'T     member Annotation : System.Type -> obj     member Annotations<'T> : unit -> System.Collections.Generic.IEnumerable<'T>     member Annotations : System.Type -> System.Collections.Generic.IEnumerable     member BaseUri : string     member Document : System.Xml.Linq.XDocument     member NodeType : System.Xml.XmlNodeType     member Parent : System.Xml.Linq.XElement     member RemoveAnnotations<'T> : unit -> unit     member RemoveAnnotations : System.Type -> unit   end Full name: System.Xml.Linq.XObject   type: XObject   implements: System.Xml.IXmlLineInfo val xattr : (string * 'b -> XObject) val value : 'b type XAttribute =   class     inherit System.Xml.Linq.XObject     new : System.Xml.Linq.XName * obj -> System.Xml.Linq.XAttribute     new : System.Xml.Linq.XAttribute -> System.Xml.Linq.XAttribute     member IsNamespaceDeclaration : bool     member Name : System.Xml.Linq.XName     member NextAttribute : System.Xml.Linq.XAttribute     member NodeType : System.Xml.XmlNodeType     member PreviousAttribute : System.Xml.Linq.XAttribute     member Remove : unit -> unit     member SetValue : obj -> unit     member ToString : unit -> string     member Value : string with get, set     static member EmptySequence : System.Collections.Generic.IEnumerable   end Full name: System.Xml.Linq.XAttribute   type: XAttribute   implements: System.Xml.IXmlLineInfo   inherits: XObject val xattrs : ((string * 'b) list -> XObject list) val attr : (string * 'b) list   type: (string * 'b) list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable   implements: System.Collections.IEnumerable val map : ('T -> 'U) -> 'T list -> 'U list Full name: Microsoft.FSharp.Collections.List.map val xelem : (string -> 'b list -> 'b list -> XObject) val attr : 'b list   type: 'b list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'b>   implements: System.Collections.IEnumerable val children : 'b list   type: 'b list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'b>   implements: System.Collections.IEnumerable type XElement =   class     inherit System.Xml.Linq.XContainer     new : System.Xml.Linq.XName -> System.Xml.Linq.XElement     new : System.Xml.Linq.XName * obj -> System.Xml.Linq.XElement     new : System.Xml.Linq.XName * obj [] -> System.Xml.Linq.XElement     new : System.Xml.Linq.XElement -> System.Xml.Linq.XElement     new : System.Xml.Linq.XStreamingElement -> System.Xml.Linq.XElement     member AncestorsAndSelf : unit -> System.Collections.Generic.IEnumerable     member AncestorsAndSelf : System.Xml.Linq.XName -> System.Collections.Generic.IEnumerable     member Attribute : System.Xml.Linq.XName -> System.Xml.Linq.XAttribute     member Attributes : unit -> System.Collections.Generic.IEnumerable     member Attributes : System.Xml.Linq.XName -> System.Collections.Generic.IEnumerable     member DescendantNodesAndSelf : unit -> System.Collections.Generic.IEnumerable     member DescendantsAndSelf : unit -> System.Collections.Generic.IEnumerable     member DescendantsAndSelf : System.Xml.Linq.XName -> System.Collections.Generic.IEnumerable     member FirstAttribute : System.Xml.Linq.XAttribute     member GetDefaultNamespace : unit -> System.Xml.Linq.XNamespace     member GetNamespaceOfPrefix : string -> System.Xml.Linq.XNamespace     member GetPrefixOfNamespace : System.Xml.Linq.XNamespace -> string     member HasAttributes : bool     member HasElements : bool     member IsEmpty : bool     member LastAttribute : System.Xml.Linq.XAttribute     member Name : System.Xml.Linq.XName with get, set     member NodeType : System.Xml.XmlNodeType     member RemoveAll : unit -> unit     member RemoveAttributes : unit -> unit     member ReplaceAll : obj -> unit     member ReplaceAll : obj [] -> unit     member ReplaceAttributes : obj -> unit     member ReplaceAttributes : obj [] -> unit     member Save : string -> unit     member Save : System.IO.TextWriter -> unit     member Save : System.Xml.XmlWriter -> unit     member Save : string * System.Xml.Linq.SaveOptions -> unit     member Save : System.IO.TextWriter * System.Xml.Linq.SaveOptions -> unit     member SetAttributeValue : System.Xml.Linq.XName * obj -> unit     member SetElementValue : System.Xml.Linq.XName * obj -> unit     member SetValue : obj -> unit     member Value : string with get, set     member WriteTo : System.Xml.XmlWriter -> unit     static member EmptySequence : System.Collections.Generic.IEnumerable     static member Load : string -> System.Xml.Linq.XElement     static member Load : System.IO.TextReader -> System.Xml.Linq.XElement     static member Load : System.Xml.XmlReader -> System.Xml.Linq.XElement     static member Load : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement     static member Load : System.IO.TextReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement     static member Load : System.Xml.XmlReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement     static member Parse : string -> System.Xml.Linq.XElement     static member Parse : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement   end Full name: System.Xml.Linq.XElement   type: XElement   implements: System.Xml.IXmlLineInfo   implements: System.Xml.Serialization.IXmlSerializable   inherits: XContainer   inherits: XNode   inherits: XObject val renderForest : (xml_item list -> XObject list) val render' : (xml_item -> XObject) val children : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable   implements: System.Collections.IEnumerable val root : XObject   type: XObject   implements: System.Xml.IXmlLineInfo namespace System.Collections namespace System.Collections.Specialized val fromNV : NameValueCollection -> (string * string) list Full name: Formlets.Formlet.fromNV val a : NameValueCollection   type: NameValueCollection   implements: System.Collections.ICollection   implements: System.Collections.IEnumerable   implements: System.Runtime.Serialization.ISerializable   implements: System.Runtime.Serialization.IDeserializationCallback   inherits: NameObjectCollectionBase type NameValueCollection =   class     inherit System.Collections.Specialized.NameObjectCollectionBase     new : unit -> System.Collections.Specialized.NameValueCollection     new : System.Collections.Specialized.NameValueCollection -> System.Collections.Specialized.NameValueCollection     new : System.Collections.IHashCodeProvider * System.Collections.IComparer -> System.Collections.Specialized.NameValueCollection     new : int -> System.Collections.Specialized.NameValueCollection     new : System.Collections.IEqualityComparer -> System.Collections.Specialized.NameValueCollection     new : int * System.Collections.IEqualityComparer -> System.Collections.Specialized.NameValueCollection     new : int * System.Collections.Specialized.NameValueCollection -> System.Collections.Specialized.NameValueCollection     new : int * System.Collections.IHashCodeProvider * System.Collections.IComparer -> System.Collections.Specialized.NameValueCollection     member Add : System.Collections.Specialized.NameValueCollection -> unit     member Add : string * string -> unit     member AllKeys : string []     member Clear : unit -> unit     member CopyTo : System.Array * int -> unit     member Get : string -> string     member Get : int -> string     member GetKey : int -> string     member GetValues : string -> string []     member GetValues : int -> string []     member HasKeys : unit -> bool     member Item : string -> string with get, set     member Item : int -> string     member Remove : string -> unit     member Set : string * string -> unit   end Full name: System.Collections.Specialized.NameValueCollection   type: NameValueCollection   implements: System.Collections.ICollection   implements: System.Collections.IEnumerable   implements: System.Runtime.Serialization.ISerializable   implements: System.Runtime.Serialization.IDeserializationCallback   inherits: NameObjectCollectionBase property NameValueCollection.AllKeys: string [] module Seq from Microsoft.FSharp.Collections val collect : ('T -> #seq<'U>) -> seq<'T> -> seq<'U> Full name: Microsoft.FSharp.Collections.Seq.collect Multiple overloads NameValueCollection.GetValues(index: int) : string [] NameValueCollection.GetValues(name: string) : string [] val map : ('T -> 'U) -> seq<'T> -> seq<'U> Full name: Microsoft.FSharp.Collections.Seq.map val v : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable   implements: seq   implements: System.Collections.IEnumerable   implements: System.IEquatable val toList : seq<'T> -> 'T list Full name: Microsoft.FSharp.Collections.Seq.toList namespace System.Web namespace System.Web.Mvc namespace System.Collections.Generic type HandleErrorAttribute =   class     inherit System.Web.Mvc.FilterAttribute     new : unit -> System.Web.Mvc.HandleErrorAttribute     member ExceptionType : System.Type with get, set     member Master : string with get, set     member OnException : System.Web.Mvc.ExceptionContext -> unit     member View : string with get, set   end Full name: System.Web.Mvc.HandleErrorAttribute   type: HandleErrorAttribute   implements: Runtime.InteropServices._Attribute   implements: IExceptionFilter   inherits: FilterAttribute   inherits: Attribute type HomeController =   class     inherit Controller     new : unit -> HomeController     member Index : unit -> ActionResult     member Register : unit -> ActionResult   end Full name: Formlets.HomeController   type: HomeController   implements: IController   implements: IActionFilter   implements: IAuthorizationFilter   implements: IDisposable   implements: IExceptionFilter   implements: IResultFilter   inherits: Controller   inherits: ControllerBase   inherits: MarshalByRefObject type Controller =   class     inherit System.Web.Mvc.ControllerBase     member ActionInvoker : System.Web.Mvc.IActionInvoker with get, set     member Dispose : unit -> unit     member HttpContext : System.Web.HttpContextBase     member ModelState : System.Web.Mvc.ModelStateDictionary     member Request : System.Web.HttpRequestBase     member Response : System.Web.HttpResponseBase     member RouteData : System.Web.Routing.RouteData     member Server : System.Web.HttpServerUtilityBase     member Session : System.Web.HttpSessionStateBase     member TempDataProvider : System.Web.Mvc.ITempDataProvider with get, set     member Url : System.Web.Mvc.UrlHelper with get, set     member User : System.Security.Principal.IPrincipal   end Full name: System.Web.Mvc.Controller   type: Controller   implements: IController   implements: IActionFilter   implements: IAuthorizationFilter   implements: IDisposable   implements: IExceptionFilter   implements: IResultFilter   inherits: ControllerBase   inherits: MarshalByRefObject val inputInt : int Formlet Multiple items val int : 'T -> int (requires member op_Explicit) Full name: Microsoft.FSharp.Core.Operators.int -------------------- type int<'Measure> = int Full name: Microsoft.FSharp.Core.int<_>   type: int<'Measure>   implements: IComparable   implements: IConvertible   implements: IFormattable   implements: IComparable>   implements: IEquatable>   inherits: ValueType -------------------- type int = int32 Full name: Microsoft.FSharp.Core.int   type: int   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: IComparable   implements: IEquatable   inherits: ValueType val inputDate : DateTime Formlet val month : int   type: int   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: IComparable   implements: IEquatable   inherits: ValueType val day : int   type: int   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: IComparable   implements: IEquatable   inherits: ValueType val year : int   type: int   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: IComparable   implements: IEquatable   inherits: ValueType type DateTime =   struct     new : int64 -> System.DateTime     new : int64 * System.DateTimeKind -> System.DateTime     new : int * int * int -> System.DateTime     new : int * int * int * System.Globalization.Calendar -> System.DateTime     new : int * int * int * int * int * int -> System.DateTime     new : int * int * int * int * int * int * System.DateTimeKind -> System.DateTime     new : int * int * int * int * int * int * System.Globalization.Calendar -> System.DateTime     new : int * int * int * int * int * int * int -> System.DateTime     new : int * int * int * int * int * int * int * System.DateTimeKind -> System.DateTime     new : int * int * int * int * int * int * int * System.Globalization.Calendar -> System.DateTime     new : int * int * int * int * int * int * int * System.Globalization.Calendar * System.DateTimeKind -> System.DateTime     member Add : System.TimeSpan -> System.DateTime     member AddDays : float -> System.DateTime     member AddHours : float -> System.DateTime     member AddMilliseconds : float -> System.DateTime     member AddMinutes : float -> System.DateTime     member AddMonths : int -> System.DateTime     member AddSeconds : float -> System.DateTime     member AddTicks : int64 -> System.DateTime     member AddYears : int -> System.DateTime     member CompareTo : obj -> int     member CompareTo : System.DateTime -> int     member Date : System.DateTime     member Day : int     member DayOfWeek : System.DayOfWeek     member DayOfYear : int     member Equals : obj -> bool     member Equals : System.DateTime -> bool     member GetDateTimeFormats : unit -> string []     member GetDateTimeFormats : System.IFormatProvider -> string []     member GetDateTimeFormats : char -> string []     member GetDateTimeFormats : char * System.IFormatProvider -> string []     member GetHashCode : unit -> int     member GetTypeCode : unit -> System.TypeCode     member Hour : int     member IsDaylightSavingTime : unit -> bool     member Kind : System.DateTimeKind     member Millisecond : int     member Minute : int     member Month : int     member Second : int     member Subtract : System.DateTime -> System.TimeSpan     member Subtract : System.TimeSpan -> System.DateTime     member Ticks : int64     member TimeOfDay : System.TimeSpan     member ToBinary : unit -> int64     member ToFileTime : unit -> int64     member ToFileTimeUtc : unit -> int64     member ToLocalTime : unit -> System.DateTime     member ToLongDateString : unit -> string     member ToLongTimeString : unit -> string     member ToOADate : unit -> float     member ToShortDateString : unit -> string     member ToShortTimeString : unit -> string     member ToString : unit -> string     member ToString : string -> string     member ToString : System.IFormatProvider -> string     member ToString : string * System.IFormatProvider -> string     member ToUniversalTime : unit -> System.DateTime     member Year : int     static val MinValue : System.DateTime     static val MaxValue : System.DateTime     static member Compare : System.DateTime * System.DateTime -> int     static member DaysInMonth : int * int -> int     static member Equals : System.DateTime * System.DateTime -> bool     static member FromBinary : int64 -> System.DateTime     static member FromFileTime : int64 -> System.DateTime     static member FromFileTimeUtc : int64 -> System.DateTime     static member FromOADate : float -> System.DateTime     static member IsLeapYear : int -> bool     static member Now : System.DateTime     static member Parse : string -> System.DateTime     static member Parse : string * System.IFormatProvider -> System.DateTime     static member Parse : string * System.IFormatProvider * System.Globalization.DateTimeStyles -> System.DateTime     static member ParseExact : string * string * System.IFormatProvider -> System.DateTime     static member ParseExact : string * string * System.IFormatProvider * System.Globalization.DateTimeStyles -> System.DateTime     static member ParseExact : string * string [] * System.IFormatProvider * System.Globalization.DateTimeStyles -> System.DateTime     static member SpecifyKind : System.DateTime * System.DateTimeKind -> System.DateTime     static member Today : System.DateTime     static member TryParse : string * System.DateTime -> bool     static member TryParse : string * System.IFormatProvider * System.Globalization.DateTimeStyles * System.DateTime -> bool     static member TryParseExact : string * string * System.IFormatProvider * System.Globalization.DateTimeStyles * System.DateTime -> bool     static member TryParseExact : string * string [] * System.IFormatProvider * System.Globalization.DateTimeStyles * System.DateTime -> bool     static member UtcNow : System.DateTime   end Full name: System.DateTime   type: DateTime   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: Runtime.Serialization.ISerializable   implements: IComparable   implements: IEquatable   inherits: ValueType val formlet : (string * DateTime * DateTime * int) Formlet val name : string   type: string   implements: IComparable   implements: ICloneable   implements: IConvertible   implements: IComparable   implements: seq   implements: Collections.IEnumerable   implements: IEquatable val order : DateTime   type: DateTime   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: Runtime.Serialization.ISerializable   implements: IComparable   implements: IEquatable   inherits: ValueType val ship : DateTime   type: DateTime   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: Runtime.Serialization.ISerializable   implements: IComparable   implements: IEquatable   inherits: ValueType val amount : int   type: int   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: IComparable   implements: IEquatable   inherits: ValueType val x : HomeController   type: HomeController   implements: IController   implements: IActionFilter   implements: IAuthorizationFilter   implements: IDisposable   implements: IExceptionFilter   implements: IResultFilter   inherits: Controller   inherits: ControllerBase   inherits: MarshalByRefObject member HomeController.Index : unit -> ActionResult Full name: Formlets.HomeController.Index property ControllerBase.ViewData: ViewDataDictionary Multiple overloads Controller.View() : ViewResult Controller.View(view: IView) : ViewResult Controller.View(viewName: string) : ViewResult Controller.View(model: obj) : ViewResult Controller.View(view: IView, model: obj) : ViewResult Controller.View(viewName: string, model: obj) : ViewResult Controller.View(viewName: string, masterName: string) : ViewResult Controller.View(viewName: string, masterName: string, model: obj) : ViewResult val box : 'T -> obj Full name: Microsoft.FSharp.Core.Operators.box val render : 'a Formlet -> Xml.Linq.XDocument Full name: Formlets.Formlet.render type ActionResult =   class     member ExecuteResult : System.Web.Mvc.ControllerContext -> unit   end Full name: System.Web.Mvc.ActionResult member HomeController.Register : unit -> ActionResult Full name: Formlets.HomeController.Register val env : (string * string) list   type: (string * string) list   implements: Collections.IStructuralEquatable   implements: IComparable>   implements: IComparable   implements: Collections.IStructuralComparable   implements: IEnumerable   implements: Collections.IEnumerable val fromNV : Collections.Specialized.NameValueCollection -> (string * string) list Full name: Formlets.Formlet.fromNV property Controller.Request: HttpRequestBase property HttpRequestBase.Form: Collections.Specialized.NameValueCollection val orderDate : DateTime   type: DateTime   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: Runtime.Serialization.ISerializable   implements: IComparable   implements: IEquatable   inherits: ValueType val shippingDate : DateTime   type: DateTime   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: Runtime.Serialization.ISerializable   implements: IComparable   implements: IEquatable   inherits: ValueType [Less]
Posted about 14 years ago by [email protected] (Mauricio Scheffer)
I've been blogging lately about functors in F#, in order to explain applicative functors, all so I could explain formlets now. Formlets are an abstraction of HTML forms. Formlets are composable, reusable, type-safe (as much as the host language is ... [More] type-safe anyway) and extensible. And they are described, of course, in terms of applicative functors.The concept of formlets was originally developed by Ezra Cooper, Sam Lindley, Philip Wadler, and Jeremy Yallop, as part of their research for Links, a new programming language oriented to web applications.Since its introduction in 2008, formlets proved to be a very popular concept, being ported to Haskell, Scheme (Racket), Common Lisp (Hunchentoot), Scala and JavaScript. If you regularly read the news about F# you probably know about WebSharper, a cool web framework for F#, which has formlets as one of its many features. Being a commercial product, WebSharper has great documentation on how to use formlets, but they only briefly mention the underlying concepts on how they work.Let's see a sample formlet in an ASP.NET MVC application:open System open System.Web open System.Web.Mvc open System.Collections.Generic [<HandleError>] type HomeController() = inherit Controller() let inputInt = lift int input let inputDate = puree (fun month day year -> DateTime(year,month,day)) <*> text "Month: " *> inputInt <*> text "Day: " *> inputInt <*> text "Year: " *> inputInt let formlet = puree (fun name order ship amount -> name,order,ship,amount) <*> text "Name: " *> input <* br <*> text "Order date: " *> inputDate <* br <*> text "Shipping date: " *> inputDate <* br <*> text "Qty: " *> inputInt <* br member x.Index() = x.ViewData.["Message"] <- "Welcome to ASP.NET MVC!" x.View(box <| render formlet) :> ActionResult member x.Register() = let env = fromNV x.Request.Form let name,orderDate,shippingDate,amount = run formlet env x.ViewData.["Name"] <- name x.ViewData.["Ordered"] <- orderDate x.ViewData.["Shipping"] <- shippingDate x.ViewData.["Amount"] <- amount x.View() :> ActionResult F# Web SnippetsIndex.aspx:<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> Home Page </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2><%: ViewData["Message"] %></h2> <p> <% using (Html.BeginForm("Register", "Home")) { %> <%= Model%> <input type="submit" value="Submit" /> <% } %> </p> </asp:Content> Register.aspx<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %> <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> Register </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <%: ViewData["Ordered"] %>: <%: ViewData["Name"] %> has requested <%: ViewData["Amount"] %> items to be delivered on <%: ViewData["Shipping"] %> </asp:Content> You can hover over any value on the F# snippets in this post and get a tooltip with types, thanks to F# Web Snippets.If you read my previous articles on applicative functors you'll recognize the standard operations pure (or puree), <*> and lift. Note how the final formlet is composed from more basic formlets: one that models date input, which in turn uses an formlet to input integers. They all rely on a primitive input formlet (of type string Formlet) that models an <input type="text"/>. Also note that we didn't define the name attribute for any input; formlets generate them automatically and also match them when handling submitted values. Running the app shows a form like this:First thing that should spring to your mind when seeing this is "boy, this is ugly", otherwise I'm not doing my job right :) Let's see now, step by step, how formlets can be implemented. We start by defining the types. First, a simple model of XML to represent HTML:type xml_item = | Text of string | Tag of string * (string*string) list * xml_item list // name * attributes * childrenWe define an environment that will hold key-value pairs from submitted forms:type Env = (string*string) listAnd finally, the Formlet type:type 'a Formlet = int -> (xml_item list * (Env -> 'a) * int)Type signatures carry a lot of information, so let's take a moment to read this. It's a function that takes an int and returns a tuple of stuff. We'll see that the int it takes is used to generate the form element name. The tuple of stuff returned contains: an XML forest (representing HTML), a function we'll call collector, and an int that will be used to generate the next form element name. The collector is used to lookup the submitted value from the environment.A formlet is an applicative functor, so let's define puree:[<AutoOpen>] module Formlet = let puree (v: 'a) : 'a Formlet = fun i -> [], (fun _ -> v), iIt's an empty XML, a constant collector, and the same input i. Here's the definition of <*> :let (<*>) (f: ('a -> 'b) Formlet) (a: 'a Formlet) : 'b Formlet = fun i -> let x1,g,i = f i let x2,q,i = a i x1 @ x2, (fun env -> g env (q env)), iStep by step: we apply f, since it holds an 'a -> 'b, the collector g collects a function 'a -> 'b. Then we apply a. We return the concatenation of XML forests, the composition of collectors, and the final name seed.Now some standard functions for applicative functors:let lift f a = puree f <*> a let lift2 f a b : _ Formlet = puree f <*> a <*> b let ( *>) f a : _ Formlet = lift2 (fun _ z -> z) f a let ( <*) f a : _ Formlet = lift2 (fun z _ -> z) f aA couple of functions to lift pure markup and text:let xml (x: xml_item list) : unit Formlet = fun i -> x, (fun _ -> ()), i let text (t: string) : unit Formlet = xml [Text t]The tag function creates a tag that wraps a formlet:let tag (name: string) (attr: (string*string) list) (f: 'a Formlet) : 'a Formlet = fun i -> let xml,env,i = f i [Tag(name, attr, xml)],env,inextName is responsible for generating form element names:let nextName = fun i -> "input_" + i.ToString(), i+1Note that it returns an incremented counter, to be used in the following name. The input primitive:let input : string Formlet = fun i -> let name,i = nextName i let collector = List.find (fun (k,_) -> k = name) >> snd let tag = Tag("input", ["name",name], []) [tag], collector, iThe input formlet shows how everything fits together. It generates a fresh name to be used both in the collector and the input tag. This is what keeps the rendered form element and its respective collected value in sync.Here's a simple <br/> for formatting:let br: unit Formlet = xml [Tag("br",[],[])]The run function gets the collector of a formlet, which in turn will produce the collected value when applied to the environment: let run (f: 'a Formlet) : Env -> 'a = let _,e,_ = f 0 eThat zero is the seed for form element names. We render a formlet by mapping it to a System.Xml.Linq.XDocument: open System.Xml.Linq let render (f: 'a Formlet) : XDocument = let xml,_,_ = f 0 let (!!) t = XName.op_Implicit t let xtext (s: string) = XText s :> XObject let xattr (name, value) = XAttribute(!!name, value) :> XObject let xattrs attr = List.map xattr attr let xelem name attr children = XElement(!!name, attr @ children) :> XObject let rec renderForest x = let render' = function | Text t -> xtext t | Tag(name, attr, children) -> xelem name (xattrs attr) (renderForest children) List.map render' x let root = xelem "div" [] (renderForest xml) XDocument rootA helper function to make an Env from a NameValueCollection (such as Request.Form):open System.Collections.Specialized let fromNV (a: NameValueCollection) = a.AllKeys |> Seq.collect (fun k -> a.GetValues k |> Seq.map (fun v -> k,v)) |> Seq.toListEven though this was a bare bones implementation of formlets, I hope it served to illustrate how they work. This implementation was taken almost verbatim from the paper The Essence of Form Abstraction [PDF] by the original developers of Formlets mentioned at the beginning of this article. I kept it as simple and clear as possible, with many type annotations that weren't really necessary and intentionally cutting out features.Full source code is here.Next, we'll see how formlets can be factored to a composition of primitive applicatives.Read this article in Serbo-Croatian (translated by Anja Skrba from Webhostinggeeks.com) namespace Formlets union case xml_item.Text: string -> xml_item Multiple items val string : 'T -> string Full name: Microsoft.FSharp.Core.Operators.string -------------------- type string = System.String Full name: Microsoft.FSharp.Core.string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> union case xml_item.Tag: string * (string * string) list * xml_item list -> xml_item type 'T list = List<'T> Full name: Microsoft.FSharp.Collections.list<_>   type: 'T list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<'T>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'T>   implements: System.Collections.IEnumerable type xml_item =   | Text of string   | Tag of string * (string * string) list * xml_item list Full name: Formlets.xml_item   type: xml_item   implements: System.IEquatable<xml_item>   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<xml_item>   implements: System.IComparable   implements: System.Collections.IStructuralComparable type Env = (string * string) list Full name: Formlets.Env   type: Env   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<string * string>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<string * string>   implements: System.Collections.IEnumerable type 'a Formlet = int -> xml_item list * (Env -> 'a) * int Full name: Formlets.Formlet<_> Multiple items val int : 'T -> int (requires member op_Explicit) Full name: Microsoft.FSharp.Core.Operators.int -------------------- type int<'Measure> = int Full name: Microsoft.FSharp.Core.int<_>   type: int<'Measure>   implements: System.IComparable   implements: System.IConvertible   implements: System.IFormattable   implements: System.IComparable<int<'Measure>>   implements: System.IEquatable<int<'Measure>>   inherits: System.ValueType -------------------- type int = int32 Full name: Microsoft.FSharp.Core.int   type: int   implements: System.IComparable   implements: System.IFormattable   implements: System.IConvertible   implements: System.IComparable<int>   implements: System.IEquatable<int>   inherits: System.ValueType type AutoOpenAttribute =   class     inherit System.Attribute     new : unit -> AutoOpenAttribute     new : path:string -> AutoOpenAttribute     member Path : string   end Full name: Microsoft.FSharp.Core.AutoOpenAttribute   type: AutoOpenAttribute   implements: System.Runtime.InteropServices._Attribute   inherits: System.Attribute val puree : 'a -> int -> xml_item list * (Env -> 'a) * int Full name: Formlets.Formlet.puree val v : 'a val i : int   type: int   implements: System.IComparable   implements: System.IFormattable   implements: System.IConvertible   implements: System.IComparable<int>   implements: System.IEquatable<int>   inherits: System.ValueType val f : ('a -> 'b) Formlet val a : 'a Formlet val x1 : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<xml_item>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<xml_item>   implements: System.Collections.IEnumerable val g : (Env -> 'a -> 'b) val x2 : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<xml_item>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<xml_item>   implements: System.Collections.IEnumerable val q : (Env -> 'a) val env : Env   type: Env   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<string * string>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<string * string>   implements: System.Collections.IEnumerable val lift : ('a -> 'b) -> 'a Formlet -> 'b Formlet Full name: Formlets.Formlet.lift val f : ('a -> 'b) val lift2 : ('a -> 'b -> 'c) -> 'a Formlet -> 'b Formlet -> 'c Formlet Full name: Formlets.Formlet.lift2 val f : ('a -> 'b -> 'c) val b : 'b Formlet val f : 'a Formlet val a : 'b Formlet val z : 'b val z : 'a val xml : xml_item list -> int -> xml_item list * (Env -> unit) * int Full name: Formlets.Formlet.xml val x : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<xml_item>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<xml_item>   implements: System.Collections.IEnumerable type unit = Unit Full name: Microsoft.FSharp.Core.unit   type: unit   implements: System.IComparable val text : string -> unit Formlet Full name: Formlets.Formlet.text val t : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> val tag : string -> (string * string) list -> 'a Formlet -> int -> xml_item list * (Env -> 'a) * int Full name: Formlets.Formlet.tag val name : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> val attr : (string * string) list   type: (string * string) list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<string * string>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<string * string>   implements: System.Collections.IEnumerable val xml : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<xml_item>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<xml_item>   implements: System.Collections.IEnumerable val env : (Env -> 'a) val nextName : int -> string * int Full name: Formlets.Formlet.nextName Multiple overloads System.Object.ToString() : string System.Int32.ToString(provider: System.IFormatProvider) : string System.Int32.ToString(format: string) : string System.Int32.ToString(format: string, provider: System.IFormatProvider) : string val input : int -> xml_item list * (Env -> string) * int Full name: Formlets.Formlet.input val lookup : ((string * string) list -> string) Multiple items module List from Microsoft.FSharp.Collections -------------------- type List<'T> =   | ( [] )   | ( :: ) of 'T * 'T list   with     interface System.Collections.IEnumerable     interface System.Collections.Generic.IEnumerable<'T>     member Head : 'T     member IsEmpty : bool     member Item : index:int -> 'T with get     member Length : int     member Tail : 'T list     static member Cons : head:'T * tail:'T list -> 'T list     static member Empty : 'T list   end Full name: Microsoft.FSharp.Collections.List<_>   type: List<'T>   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<'T>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'T>   implements: System.Collections.IEnumerable val find : ('T -> bool) -> 'T list -> 'T Full name: Microsoft.FSharp.Collections.List.find val k : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> val snd : ('T1 * 'T2) -> 'T2 Full name: Microsoft.FSharp.Core.Operators.snd val tag : xml_item   type: xml_item   implements: System.IEquatable<xml_item>   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<xml_item>   implements: System.IComparable   implements: System.Collections.IStructuralComparable val br : unit Formlet Full name: Formlets.Formlet.br val run : 'a Formlet -> (Env -> 'a) Full name: Formlets.Formlet.run val e : (Env -> 'a) namespace System namespace System.Xml namespace System.Xml.Linq val render : 'a Formlet -> XDocument Full name: Formlets.Formlet.render type XDocument =   class     inherit System.Xml.Linq.XContainer     new : unit -> System.Xml.Linq.XDocument     new : obj [] -> System.Xml.Linq.XDocument     new : System.Xml.Linq.XDeclaration * obj [] -> System.Xml.Linq.XDocument     new : System.Xml.Linq.XDocument -> System.Xml.Linq.XDocument     member Declaration : System.Xml.Linq.XDeclaration with get, set     member DocumentType : System.Xml.Linq.XDocumentType     member NodeType : System.Xml.XmlNodeType     member Root : System.Xml.Linq.XElement     member Save : string -> unit     member Save : System.IO.TextWriter -> unit     member Save : System.Xml.XmlWriter -> unit     member Save : string * System.Xml.Linq.SaveOptions -> unit     member Save : System.IO.TextWriter * System.Xml.Linq.SaveOptions -> unit     member WriteTo : System.Xml.XmlWriter -> unit     static member Load : string -> System.Xml.Linq.XDocument     static member Load : System.IO.TextReader -> System.Xml.Linq.XDocument     static member Load : System.Xml.XmlReader -> System.Xml.Linq.XDocument     static member Load : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument     static member Load : System.IO.TextReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument     static member Load : System.Xml.XmlReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument     static member Parse : string -> System.Xml.Linq.XDocument     static member Parse : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XDocument   end Full name: System.Xml.Linq.XDocument   type: XDocument   implements: System.Xml.IXmlLineInfo   inherits: XContainer   inherits: XNode   inherits: XObject type XName =   class     member Equals : obj -> bool     member GetHashCode : unit -> int     member LocalName : string     member Namespace : System.Xml.Linq.XNamespace     member NamespaceName : string     member ToString : unit -> string     static member Get : string -> System.Xml.Linq.XName     static member Get : string * string -> System.Xml.Linq.XName   end Full name: System.Xml.Linq.XName   type: XName   implements: System.IEquatable<XName>   implements: System.Runtime.Serialization.ISerializable XName.op_Implicit(expandedName: string) : XName val xtext : (string -> XObject) val s : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> type XText =   class     inherit System.Xml.Linq.XNode     new : string -> System.Xml.Linq.XText     new : System.Xml.Linq.XText -> System.Xml.Linq.XText     member NodeType : System.Xml.XmlNodeType     member Value : string with get, set     member WriteTo : System.Xml.XmlWriter -> unit   end Full name: System.Xml.Linq.XText   type: XText   implements: System.Xml.IXmlLineInfo   inherits: XNode   inherits: XObject type XObject =   class     member AddAnnotation : obj -> unit     member Annotation<'T> : unit -> 'T     member Annotation : System.Type -> obj     member Annotations<'T> : unit -> System.Collections.Generic.IEnumerable<'T>     member Annotations : System.Type -> System.Collections.Generic.IEnumerable<obj>     member BaseUri : string     member Document : System.Xml.Linq.XDocument     member NodeType : System.Xml.XmlNodeType     member Parent : System.Xml.Linq.XElement     member RemoveAnnotations<'T> : unit -> unit     member RemoveAnnotations : System.Type -> unit   end Full name: System.Xml.Linq.XObject   type: XObject   implements: System.Xml.IXmlLineInfo val xattr : (string * 'b -> XObject) val value : 'b type XAttribute =   class     inherit System.Xml.Linq.XObject     new : System.Xml.Linq.XName * obj -> System.Xml.Linq.XAttribute     new : System.Xml.Linq.XAttribute -> System.Xml.Linq.XAttribute     member IsNamespaceDeclaration : bool     member Name : System.Xml.Linq.XName     member NextAttribute : System.Xml.Linq.XAttribute     member NodeType : System.Xml.XmlNodeType     member PreviousAttribute : System.Xml.Linq.XAttribute     member Remove : unit -> unit     member SetValue : obj -> unit     member ToString : unit -> string     member Value : string with get, set     static member EmptySequence : System.Collections.Generic.IEnumerable<System.Xml.Linq.XAttribute>   end Full name: System.Xml.Linq.XAttribute   type: XAttribute   implements: System.Xml.IXmlLineInfo   inherits: XObject val xattrs : ((string * 'b) list -> XObject list) val attr : (string * 'b) list   type: (string * 'b) list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<string * 'b>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<string * 'b>   implements: System.Collections.IEnumerable val map : ('T -> 'U) -> 'T list -> 'U list Full name: Microsoft.FSharp.Collections.List.map val xelem : (string -> 'b list -> 'b list -> XObject) val attr : 'b list   type: 'b list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<'b>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'b>   implements: System.Collections.IEnumerable val children : 'b list   type: 'b list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<'b>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<'b>   implements: System.Collections.IEnumerable type XElement =   class     inherit System.Xml.Linq.XContainer     new : System.Xml.Linq.XName -> System.Xml.Linq.XElement     new : System.Xml.Linq.XName * obj -> System.Xml.Linq.XElement     new : System.Xml.Linq.XName * obj [] -> System.Xml.Linq.XElement     new : System.Xml.Linq.XElement -> System.Xml.Linq.XElement     new : System.Xml.Linq.XStreamingElement -> System.Xml.Linq.XElement     member AncestorsAndSelf : unit -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>     member AncestorsAndSelf : System.Xml.Linq.XName -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>     member Attribute : System.Xml.Linq.XName -> System.Xml.Linq.XAttribute     member Attributes : unit -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XAttribute>     member Attributes : System.Xml.Linq.XName -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XAttribute>     member DescendantNodesAndSelf : unit -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XNode>     member DescendantsAndSelf : unit -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>     member DescendantsAndSelf : System.Xml.Linq.XName -> System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>     member FirstAttribute : System.Xml.Linq.XAttribute     member GetDefaultNamespace : unit -> System.Xml.Linq.XNamespace     member GetNamespaceOfPrefix : string -> System.Xml.Linq.XNamespace     member GetPrefixOfNamespace : System.Xml.Linq.XNamespace -> string     member HasAttributes : bool     member HasElements : bool     member IsEmpty : bool     member LastAttribute : System.Xml.Linq.XAttribute     member Name : System.Xml.Linq.XName with get, set     member NodeType : System.Xml.XmlNodeType     member RemoveAll : unit -> unit     member RemoveAttributes : unit -> unit     member ReplaceAll : obj -> unit     member ReplaceAll : obj [] -> unit     member ReplaceAttributes : obj -> unit     member ReplaceAttributes : obj [] -> unit     member Save : string -> unit     member Save : System.IO.TextWriter -> unit     member Save : System.Xml.XmlWriter -> unit     member Save : string * System.Xml.Linq.SaveOptions -> unit     member Save : System.IO.TextWriter * System.Xml.Linq.SaveOptions -> unit     member SetAttributeValue : System.Xml.Linq.XName * obj -> unit     member SetElementValue : System.Xml.Linq.XName * obj -> unit     member SetValue : obj -> unit     member Value : string with get, set     member WriteTo : System.Xml.XmlWriter -> unit     static member EmptySequence : System.Collections.Generic.IEnumerable<System.Xml.Linq.XElement>     static member Load : string -> System.Xml.Linq.XElement     static member Load : System.IO.TextReader -> System.Xml.Linq.XElement     static member Load : System.Xml.XmlReader -> System.Xml.Linq.XElement     static member Load : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement     static member Load : System.IO.TextReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement     static member Load : System.Xml.XmlReader * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement     static member Parse : string -> System.Xml.Linq.XElement     static member Parse : string * System.Xml.Linq.LoadOptions -> System.Xml.Linq.XElement   end Full name: System.Xml.Linq.XElement   type: XElement   implements: System.Xml.IXmlLineInfo   implements: System.Xml.Serialization.IXmlSerializable   inherits: XContainer   inherits: XNode   inherits: XObject val renderForest : (xml_item list -> XObject list) val render' : (xml_item -> XObject) val children : xml_item list   type: xml_item list   implements: System.Collections.IStructuralEquatable   implements: System.IComparable<List<xml_item>>   implements: System.IComparable   implements: System.Collections.IStructuralComparable   implements: System.Collections.Generic.IEnumerable<xml_item>   implements: System.Collections.IEnumerable val root : XObject   type: XObject   implements: System.Xml.IXmlLineInfo namespace System.Collections namespace System.Collections.Specialized val fromNV : NameValueCollection -> (string * string) list Full name: Formlets.Formlet.fromNV val a : NameValueCollection   type: NameValueCollection   implements: System.Collections.ICollection   implements: System.Collections.IEnumerable   implements: System.Runtime.Serialization.ISerializable   implements: System.Runtime.Serialization.IDeserializationCallback   inherits: NameObjectCollectionBase type NameValueCollection =   class     inherit System.Collections.Specialized.NameObjectCollectionBase     new : unit -> System.Collections.Specialized.NameValueCollection     new : System.Collections.Specialized.NameValueCollection -> System.Collections.Specialized.NameValueCollection     new : System.Collections.IHashCodeProvider * System.Collections.IComparer -> System.Collections.Specialized.NameValueCollection     new : int -> System.Collections.Specialized.NameValueCollection     new : System.Collections.IEqualityComparer -> System.Collections.Specialized.NameValueCollection     new : int * System.Collections.IEqualityComparer -> System.Collections.Specialized.NameValueCollection     new : int * System.Collections.Specialized.NameValueCollection -> System.Collections.Specialized.NameValueCollection     new : int * System.Collections.IHashCodeProvider * System.Collections.IComparer -> System.Collections.Specialized.NameValueCollection     member Add : System.Collections.Specialized.NameValueCollection -> unit     member Add : string * string -> unit     member AllKeys : string []     member Clear : unit -> unit     member CopyTo : System.Array * int -> unit     member Get : string -> string     member Get : int -> string     member GetKey : int -> string     member GetValues : string -> string []     member GetValues : int -> string []     member HasKeys : unit -> bool     member Item : string -> string with get, set     member Item : int -> string     member Remove : string -> unit     member Set : string * string -> unit   end Full name: System.Collections.Specialized.NameValueCollection   type: NameValueCollection   implements: System.Collections.ICollection   implements: System.Collections.IEnumerable   implements: System.Runtime.Serialization.ISerializable   implements: System.Runtime.Serialization.IDeserializationCallback   inherits: NameObjectCollectionBase property NameValueCollection.AllKeys: string [] module Seq from Microsoft.FSharp.Collections val collect : ('T -> #seq<'U>) -> seq<'T> -> seq<'U> Full name: Microsoft.FSharp.Collections.Seq.collect Multiple overloads NameValueCollection.GetValues(index: int) : string [] NameValueCollection.GetValues(name: string) : string [] val map : ('T -> 'U) -> seq<'T> -> seq<'U> Full name: Microsoft.FSharp.Collections.Seq.map val v : string   type: string   implements: System.IComparable   implements: System.ICloneable   implements: System.IConvertible   implements: System.IComparable<string>   implements: seq<char>   implements: System.Collections.IEnumerable   implements: System.IEquatable<string> val toList : seq<'T> -> 'T list Full name: Microsoft.FSharp.Collections.Seq.toList namespace System.Web namespace System.Web.Mvc namespace System.Collections.Generic type HandleErrorAttribute =   class     inherit System.Web.Mvc.FilterAttribute     new : unit -> System.Web.Mvc.HandleErrorAttribute     member ExceptionType : System.Type with get, set     member Master : string with get, set     member OnException : System.Web.Mvc.ExceptionContext -> unit     member View : string with get, set   end Full name: System.Web.Mvc.HandleErrorAttribute   type: HandleErrorAttribute   implements: Runtime.InteropServices._Attribute   implements: IExceptionFilter   inherits: FilterAttribute   inherits: Attribute type HomeController =   class     inherit Controller     new : unit -> HomeController     member Index : unit -> ActionResult     member Register : unit -> ActionResult   end Full name: Formlets.HomeController   type: HomeController   implements: IController   implements: IActionFilter   implements: IAuthorizationFilter   implements: IDisposable   implements: IExceptionFilter   implements: IResultFilter   inherits: Controller   inherits: ControllerBase   inherits: MarshalByRefObject type Controller =   class     inherit System.Web.Mvc.ControllerBase     member ActionInvoker : System.Web.Mvc.IActionInvoker with get, set     member Dispose : unit -> unit     member HttpContext : System.Web.HttpContextBase     member ModelState : System.Web.Mvc.ModelStateDictionary     member Request : System.Web.HttpRequestBase     member Response : System.Web.HttpResponseBase     member RouteData : System.Web.Routing.RouteData     member Server : System.Web.HttpServerUtilityBase     member Session : System.Web.HttpSessionStateBase     member TempDataProvider : System.Web.Mvc.ITempDataProvider with get, set     member Url : System.Web.Mvc.UrlHelper with get, set     member User : System.Security.Principal.IPrincipal   end Full name: System.Web.Mvc.Controller   type: Controller   implements: IController   implements: IActionFilter   implements: IAuthorizationFilter   implements: IDisposable   implements: IExceptionFilter   implements: IResultFilter   inherits: ControllerBase   inherits: MarshalByRefObject val inputInt : int Formlet Multiple items val int : 'T -> int (requires member op_Explicit) Full name: Microsoft.FSharp.Core.Operators.int -------------------- type int<'Measure> = int Full name: Microsoft.FSharp.Core.int<_>   type: int<'Measure>   implements: IComparable   implements: IConvertible   implements: IFormattable   implements: IComparable<int<'Measure>>   implements: IEquatable<int<'Measure>>   inherits: ValueType -------------------- type int = int32 Full name: Microsoft.FSharp.Core.int   type: int   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: IComparable<int>   implements: IEquatable<int>   inherits: ValueType val inputDate : DateTime Formlet val month : int   type: int   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: IComparable<int>   implements: IEquatable<int>   inherits: ValueType val day : int   type: int   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: IComparable<int>   implements: IEquatable<int>   inherits: ValueType val year : int   type: int   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: IComparable<int>   implements: IEquatable<int>   inherits: ValueType type DateTime =   struct     new : int64 -> System.DateTime     new : int64 * System.DateTimeKind -> System.DateTime     new : int * int * int -> System.DateTime     new : int * int * int * System.Globalization.Calendar -> System.DateTime     new : int * int * int * int * int * int -> System.DateTime     new : int * int * int * int * int * int * System.DateTimeKind -> System.DateTime     new : int * int * int * int * int * int * System.Globalization.Calendar -> System.DateTime     new : int * int * int * int * int * int * int -> System.DateTime     new : int * int * int * int * int * int * int * System.DateTimeKind -> System.DateTime     new : int * int * int * int * int * int * int * System.Globalization.Calendar -> System.DateTime     new : int * int * int * int * int * int * int * System.Globalization.Calendar * System.DateTimeKind -> System.DateTime     member Add : System.TimeSpan -> System.DateTime     member AddDays : float -> System.DateTime     member AddHours : float -> System.DateTime     member AddMilliseconds : float -> System.DateTime     member AddMinutes : float -> System.DateTime     member AddMonths : int -> System.DateTime     member AddSeconds : float -> System.DateTime     member AddTicks : int64 -> System.DateTime     member AddYears : int -> System.DateTime     member CompareTo : obj -> int     member CompareTo : System.DateTime -> int     member Date : System.DateTime     member Day : int     member DayOfWeek : System.DayOfWeek     member DayOfYear : int     member Equals : obj -> bool     member Equals : System.DateTime -> bool     member GetDateTimeFormats : unit -> string []     member GetDateTimeFormats : System.IFormatProvider -> string []     member GetDateTimeFormats : char -> string []     member GetDateTimeFormats : char * System.IFormatProvider -> string []     member GetHashCode : unit -> int     member GetTypeCode : unit -> System.TypeCode     member Hour : int     member IsDaylightSavingTime : unit -> bool     member Kind : System.DateTimeKind     member Millisecond : int     member Minute : int     member Month : int     member Second : int     member Subtract : System.DateTime -> System.TimeSpan     member Subtract : System.TimeSpan -> System.DateTime     member Ticks : int64     member TimeOfDay : System.TimeSpan     member ToBinary : unit -> int64     member ToFileTime : unit -> int64     member ToFileTimeUtc : unit -> int64     member ToLocalTime : unit -> System.DateTime     member ToLongDateString : unit -> string     member ToLongTimeString : unit -> string     member ToOADate : unit -> float     member ToShortDateString : unit -> string     member ToShortTimeString : unit -> string     member ToString : unit -> string     member ToString : string -> string     member ToString : System.IFormatProvider -> string     member ToString : string * System.IFormatProvider -> string     member ToUniversalTime : unit -> System.DateTime     member Year : int     static val MinValue : System.DateTime     static val MaxValue : System.DateTime     static member Compare : System.DateTime * System.DateTime -> int     static member DaysInMonth : int * int -> int     static member Equals : System.DateTime * System.DateTime -> bool     static member FromBinary : int64 -> System.DateTime     static member FromFileTime : int64 -> System.DateTime     static member FromFileTimeUtc : int64 -> System.DateTime     static member FromOADate : float -> System.DateTime     static member IsLeapYear : int -> bool     static member Now : System.DateTime     static member Parse : string -> System.DateTime     static member Parse : string * System.IFormatProvider -> System.DateTime     static member Parse : string * System.IFormatProvider * System.Globalization.DateTimeStyles -> System.DateTime     static member ParseExact : string * string * System.IFormatProvider -> System.DateTime     static member ParseExact : string * string * System.IFormatProvider * System.Globalization.DateTimeStyles -> System.DateTime     static member ParseExact : string * string [] * System.IFormatProvider * System.Globalization.DateTimeStyles -> System.DateTime     static member SpecifyKind : System.DateTime * System.DateTimeKind -> System.DateTime     static member Today : System.DateTime     static member TryParse : string * System.DateTime -> bool     static member TryParse : string * System.IFormatProvider * System.Globalization.DateTimeStyles * System.DateTime -> bool     static member TryParseExact : string * string * System.IFormatProvider * System.Globalization.DateTimeStyles * System.DateTime -> bool     static member TryParseExact : string * string [] * System.IFormatProvider * System.Globalization.DateTimeStyles * System.DateTime -> bool     static member UtcNow : System.DateTime   end Full name: System.DateTime   type: DateTime   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: Runtime.Serialization.ISerializable   implements: IComparable<DateTime>   implements: IEquatable<DateTime>   inherits: ValueType val formlet : (string * DateTime * DateTime * int) Formlet val name : string   type: string   implements: IComparable   implements: ICloneable   implements: IConvertible   implements: IComparable<string>   implements: seq<char>   implements: Collections.IEnumerable   implements: IEquatable<string> val order : DateTime   type: DateTime   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: Runtime.Serialization.ISerializable   implements: IComparable<DateTime>   implements: IEquatable<DateTime>   inherits: ValueType val ship : DateTime   type: DateTime   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: Runtime.Serialization.ISerializable   implements: IComparable<DateTime>   implements: IEquatable<DateTime>   inherits: ValueType val amount : int   type: int   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: IComparable<int>   implements: IEquatable<int>   inherits: ValueType val x : HomeController   type: HomeController   implements: IController   implements: IActionFilter   implements: IAuthorizationFilter   implements: IDisposable   implements: IExceptionFilter   implements: IResultFilter   inherits: Controller   inherits: ControllerBase   inherits: MarshalByRefObject member HomeController.Index : unit -> ActionResult Full name: Formlets.HomeController.Index property ControllerBase.ViewData: ViewDataDictionary Multiple overloads Controller.View() : ViewResult Controller.View(view: IView) : ViewResult Controller.View(viewName: string) : ViewResult Controller.View(model: obj) : ViewResult Controller.View(view: IView, model: obj) : ViewResult Controller.View(viewName: string, model: obj) : ViewResult Controller.View(viewName: string, masterName: string) : ViewResult Controller.View(viewName: string, masterName: string, model: obj) : ViewResult val box : 'T -> obj Full name: Microsoft.FSharp.Core.Operators.box val render : 'a Formlet -> Xml.Linq.XDocument Full name: Formlets.Formlet.render type ActionResult =   class     member ExecuteResult : System.Web.Mvc.ControllerContext -> unit   end Full name: System.Web.Mvc.ActionResult member HomeController.Register : unit -> ActionResult Full name: Formlets.HomeController.Register val env : (string * string) list   type: (string * string) list   implements: Collections.IStructuralEquatable   implements: IComparable<List<string * string>>   implements: IComparable   implements: Collections.IStructuralComparable   implements: IEnumerable<string * string>   implements: Collections.IEnumerable val fromNV : Collections.Specialized.NameValueCollection -> (string * string) list Full name: Formlets.Formlet.fromNV property Controller.Request: HttpRequestBase property HttpRequestBase.Form: Collections.Specialized.NameValueCollection val orderDate : DateTime   type: DateTime   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: Runtime.Serialization.ISerializable   implements: IComparable<DateTime>   implements: IEquatable<DateTime>   inherits: ValueType val shippingDate : DateTime   type: DateTime   implements: IComparable   implements: IFormattable   implements: IConvertible   implements: Runtime.Serialization.ISerializable   implements: IComparable<DateTime>   implements: IEquatable<DateTime>   inherits: ValueType [Less]