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