Basic templating engine in F# using F# syntax I've been working on a rough idea for a templating engine (mostly as a learning project) using the F# syntax. How "correct" is my code in terms of being idiomatic F#? Are there F# features which would make the code simpler? More easily understood? 

The template files are loaded at runtime using a script I've not posted here The code is also provided as a gist for easier reading.

Template

#r "program.exe"
open System
open Lib

let intToText i = div [] [Text((sprintf "I is %i" i))]

Template (
    html [] [
        div [("id", "123");("class", "me")] [
            div [] [   
                Text "SOME TEXT"
            ]
            div [] [
                Text "SOME MORE TEXT"
            ]
            foreach [1;2;3] intToText
            input [("type", "text"); ("value", "1")]
        ]
    ]
)


Lib

module Lib
open System
open System.Text

type Attributes = (string * string) list

type Element =
    | Tag of (string * Attributes * Element list)
    | TagSelfClosing of (string * Attributes)
    | Collection of (Element list)
    | Text of string

let html (attrs:Attributes) el:Element = Tag("html", attrs, el)
let div (attrs:Attributes) el:Element = Tag("div", attrs, el)
let input (attrs:Attributes) = TagSelfClosing("input", attrs)
let foreach data fn = 
    let elems = data |&gt; List.map fn
    Collection(elems)

type Token = 
    | OPEN_TAG of (string * Attributes)
    | CLOSE_TAG of string
    | SELF_CLOSING_TAG of (string * Attributes)
    | TEXT of string 

let Template el =
    let rec expandTree el output = 
        match el with
        | Tag(tagName, attrs, children) -&gt; 
            let childContent = 
                children
                |&gt; List.fold (fun state item -&gt; state @ (expandTree item [])) []

            let addition = [OPEN_TAG(tagName, attrs)]@childContent@[CLOSE_TAG(tagName)]
            output@addition
        | TagSelfClosing(tagName, attrs) -&gt;
            let addition = [SELF_CLOSING_TAG(tagName, attrs)]
            output@addition
        | Collection(elements) -&gt;
            let collectionContent =
                elements
                |&gt; List.map (fun e -&gt; expandTree e output)
                |&gt; List.fold (fun state item -&gt; state@item) []
            output @ collectionContent
        | Text(string) -&gt;
            output@[TEXT(string)]

    let tokenWriter token = 
        let attributeWriter (tuple:string*string) =
            let (name, value) = tuple
            sprintf " %s=\"%s\"" name value

        let foldAttributes attrs =
            attrs
            |&gt; List.map attributeWriter
            |&gt; List.fold (fun state item -&gt; state+item) "" 

        match token with
        | OPEN_TAG(name, attrs) -&gt; 
            let attributes = foldAttributes attrs
            sprintf "&lt;%s%s&gt;" name attributes
        | CLOSE_TAG(name) -&gt; sprintf "&lt;/%s&gt;" name
        | SELF_CLOSING_TAG(name, attrs) -&gt; 
            let attributes = foldAttributes attrs
            sprintf "&lt;%s%s /&gt;" name attributes
        | TEXT(content) -&gt; sprintf "%s" content 

    let expandedTree = expandTree el []
    let content = 
        expandedTree 
        |&gt; List.map tokenWriter 
        |&gt; List.fold (fun state item -&gt; state+item) ""

    printf "%s" content
There are a few suggestions which immediately occur to me:  some are minor changes which could improve the clarity of the code and there is one major change which would shorten the code quite a bit.

The minor changes:


For List.fold you can just pass a function which matches the expected type and not write the full lambda.  With an operator like + it's not obvious, but you can clean up your code a bit using:

attrs
|&gt; List.map (fun (name,value) -&gt; sprintf "%s=\"%s\"" name value)
|&gt; List.fold (+) ""

In the function childContent, I would move the call to expandTree into a List.map, to reduce the amount of nesting and to allow us to clean up the List.fold:

children
|&gt; List.map (fun child -&gt; expandTree child [])
|&gt; List.fold (@) []

With Text(string) you should use a name other than string since that's the name of an existing type and that could cause confusion for someone reading your code.


The major change is that you can combine the tokenWriter and the expandTree into a single function.  At the heart, what you are doing is an in order traversal of a tree to convert your HTML structure into a string:  expandTree does an in order traversal to flatten the tree into a list and then tokenWriter converts those tokens into strings.  You're not gaining much by flattening the tree and then generating the output string; so, you could just use the tree traversal to generate the output string.

Making this change also allows you to remove the Token type, further reducing the overall complexity of your code.

Here's the updated code:

    let Template el =
        let rec elementToString el output = 
            let attributesToString attrs =
                attrs
                |&gt; List.map (fun (name,value) -&gt; sprintf "%s=\"%s\"" name value)
                |&gt; List.fold (+) ""

            match el with
            | Tag(tagName, attrs, children) -&gt; 
                let attributes = attributesToString attrs
                let tagStr = sprintf "&lt;%s%s&gt;" tagName attributes
                let childContent = 
                    children
                    |&gt; List.map (fun item -&gt; elementToString item "" )
                    |&gt; List.fold (+) tagStr
                let closeTagStr = sprintf "&lt;/%s&gt;" tagName
                sprintf "%s%s%s" output childContent closeTagStr
            | TagSelfClosing(tagName, attrs) -&gt;
                let attributes = attributesToString attrs
                sprintf "%s&lt;%s%s /&gt;" output tagName attributes
            | Collection(elements) -&gt;
                elements
                |&gt; List.map (fun e -&gt; elementToString e output)
                |&gt; List.fold (+) ""
            | Text(text) -&gt;
                sprintf "%s%s" output text

        let expandedTree = elementToString el ""
        let content = 
            expandedTree

        printf "%s" content