Phillip Trelford's Array

POKE 36879,255

Compiling F# to JavaScript with Pit

JavaScript is the old new thing.

First shipped in Netscape Navigator 2.0 in 1995, JavaScript is supported by most popular browsers. Because of this, JavaScript has become an intermediate language for other languages to target. CoffeeScript (late 2009), ClojureScript (2011) and Dart (2011) are relatively recent examples of languages targeting JavaScript.

The F# programming language makes it relatively easy to transform F# code to another form through a feature called code quotations. Simply annotating F# code with the ReflectedDefinition attribute makes it available as a quotation at runtime. Aliasing this attribute to say JavaScript or Js allows you to declaratively specify which code to generate.

There are a number of tools providing support for targeting JavaScript from F#. FSharp.WebTools by Tomas Petricek, among other things, targeted JavasScript from F# way back in 2007. FSharp.JavaScript simply converts F# to JavaScript. While WebSharper is a commercial product with advanced features.

Enter Pit

Pit is a new F# to JavaScript compiler community project released just a few days ago. And it’s early days at version 0.1, but it’s already very useable.

Check out the samples and documentation.

Download and run the setup project, and within seconds a new Pit application project type is added to Visual Studio. This produces a simple hello world app:

let [<Js>] main() =        
    alert("Hello World!!!")

On top of the usual alert box debugging for JavaScript, Pit lets you set breakpoints!

So far I’ve found Pit very intuitive to develop against.

This was my first app in Pit, a simple calculator:

PitCalc

Which runs happily on iPhone, Blackberry and WP7! All from 80 odd lines of code:

namespace Pit

open Pit
open Pit.Dom

module Calculator =

    let [<Js>] (?) (el:DomElement) name =
        el.GetAttribute(name)
    let [<Js>] (?<-) (el:DomElement) name value =
        el.SetAttribute(name,value)
    type DomAttribute = { Name:string; Value:obj }
    let [<Js>] (@=) name (value:'a) =
        { Name=name; Value=box value }
    let [<Js>] tag name (attributes:DomAttribute list) =
        let el = document.CreateElement(name)
        for a in attributes do el.SetAttribute(a.Name,a.Value.ToString())
        el

    let [<Js>] display =
        tag "input" ["type"@="text";"value"@=0;"style"@="text-align:right"]
    let [<Js>] mutable operation : (int -> int) option = None
    let [<Js>] mutable append = false

    let [<Js>] clear () =
        display?value <- "0"
        append <- false

    let [<Js>] enter s =
        s, fun () ->
            let value = display?value
            if append then display?value <- value + s
            else display?value <- s; append <- true

    let [<Js>] calculate () =
        let value = int display?value
        operation |> Option.iter (fun op ->
            let newValue = op value
            display?value <- newValue.ToString() 
        )
        operation <- None
        append <- false

    let [<Js>] operator op () =
        calculate ()
        let value = display?value |> int
        operation <- op value |> Some
   
    let [<Js>] add = (+) 
    let [<Js>] sub = (-)
    let [<Js>] mul = (*)
    let [<Js>] div = (/)

    let [<Js>] buttons =
        [[enter "7"; enter "8"; enter "9"; "/", operator div]
         [enter "4"; enter "5"; enter "6"; "*", operator mul]
         [enter "1"; enter "2"; enter "3"; "-", operator sub]
         ["C", clear; enter "0"; "=", calculate; "+", operator add]]

    [<DomEntryPoint>]
    let [<Js>] main() =
        let table = (tag "table" [] |> DomTable.Of)  
        let tr = tag "tr" []
        let td = tag "td" ["colspan"@="4"]
        td.AppendChild display
        tr.AppendChild td 
        table.AppendChild tr 
        buttons |> List.iter (fun row ->
            let tr = tag "tr" [] 
            row |> List.iter (fun (text,action) ->
                let td = tag "td" []
                let input = 
                    tag "input" 
                        ["type"@="button";"value"@=text;"style"@="width:32px"]
                input |> Event.click |> Event.add (fun _ -> action ())
                td.AppendChild input
                tr.AppendChild td
            )
            table.AppendChild tr
        )
        let div = document.GetElementById "calculator"
        div.AppendChild table

Operator Overload


For convenience the code above makes use of some F# operator overloading.

The dynamic lookup operator is overloaded for getting and setting attributes:

let [<Js>] (?) (el:DomElement) name =
    el.GetAttribute(name)
let [<Js>] (?<-) (el:DomElement) name value =
    el.SetAttribute(name,value)

This can be used to get and set the value of the input tag of the calculator’s display:

let value = display?value
display?value <- value

The HTML input element for the calculator’s display is created dynamically:

let [<Js>] display =
    tag "input" ["type"@="text";"value"@=0;"style"@="text-align:right"]

The tag function creates a HTML element from a specified name and a list of attributes:

let [<Js>] tag name (attributes:DomAttribute list) =
    let el = document.CreateElement(name)
    for a in attributes do el.SetAttribute(a.Name,a.Value.ToString())
    el

And a custom operator (@=) creates an attribute record:

type DomAttribute = { Name:string; Value:obj }
let [<Js>] (@=) name (value:'a) =
    { Name=name; Value=box value }

JavaScript as an Intermediate Language?


Q: Why use an expressive statically typed high-level language like F# over plain JavaScript?

A: This will probably come down to a mixture of problem context and personal preference.

Certain problems, like parsing, are easy in F#. Alexander Stojanovic sums it up well:

You must understand a (programming) language as a way of seeing. Languages don't just express thoughts, they shape them.

Validation code written in F# can be run on both the client, via JavaScript, and the server, via .Net IL code.

An area of particular interest for the F# community is generating live samples from code snippets. The F# Snippets site let you target your snippet against Silverlight via tryfsharp.org or an echo of the console via tryfs.net. For example here a game of Missile Command or Tetris running as scripts.

Pit’s build libraries offer the potential to run F# snippets via JavaScript.

Resources


Comments (1) -

  • Jacobo

    11/24/2011 1:21:09 AM |

    Great!

    I've been following the websharper project for awhile and have always been willing to have the javascript part as open source, so this is very interesting.

    One question if you don't mind... I'm supposng Pit can only translate your own independent F# code, right. I mean, can it translate it in this two situations?
    1. When you call F# core libraries
    2. When you call any other .Net library such as System.IO or System.Net, etc...

    Looking pretty good... will have a closer look at Pit.

    Thanks!

Pingbacks and trackbacks (2)+

Comments are closed