Tuesday, January 25, 2011

A factored implementation of formlets in F#

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

No comments: