Phil Trelford's Array
POKE 36879, 255

Compiling F# to JavaScript with Pit

November 14, 2011 11:15 by phil

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



Tags:
Categories: F# | JavaScript
Actions: E-mail | Permalink | Comments (3) | Comment RSSRSS comment feed

Comments

November 21. 2011 19:27

trackback

Compiling F# to JavaScript with Pit

You've been kicked (a good thing) - Trackback from DotNetKicks.com

DotNetKicks.com

November 24. 2011 01:21

Jacobo

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!

Jacobo

April 11. 2012 18:22

trackback

Is Javascript code always so full of bugs?

Slightly off topic... The "debugging options" got turned on in my browser recently. I think this happened

Don Syme's WebLog on F# and Related Topics

Add comment


(Will show your Gravatar icon)

  Country flag

biuquote
  • Comment
  • Preview
Loading