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]
|