Monday, June 14, 2010

WebSharper™: Towards Automated JavaScript Bindings

Cross-posted from the IntelliFactory blog.

This blog describes the pre-release WebSharper™ Extensibility Framework.

How would I call reasonably complex JavaScript functions from WebSharper™? For an example, consider jQuery.ajax.

A first attempt would look like this:

[<Inline "jQuery.ajax($settings)">]
let ajax (settings: obj) : obj = null

This is hardly acceptable as it throws away all the information present on the documentation page and makes it the user's responsibility to figure out what exactly the ajax function expects. But spelling out the precise types requires a lot more work.

In practice, a big part of WebSharper™ codebase turns out to consist of stubs like these - classes and functions that instruct F# how to call a particular JavaScript API (think jQuery, Ext JS) and provide types for the API. The WebSharper™ JavaScript-binding code is repetitive, tedious to write and maintain and not straightforward to generate.

At IntelliFactory we have been exprimenting with several approaches to the problem, culminating in something we currently call the Extensibility Framework, or EF for short. The basic idea behind EF is that instead of writing the bindings by hand, we use an F#-embedded DSL to describe them.

jQuery.ajax with EF might look like this:

let private AjaxConfig =
Pattern.Config "AjaxConfig" [] [
"async" =~ T<bool>
"beforeSend" =~ J.XMLHttpRequest ^-> T<unit>
"cache" =~ T<bool>
"complete" =~ J.XMLHttpRequest * T<string> ^-> T<unit>
"contentType" =~ T<string>
"context" =~ T<obj>
// ..
]

let private JQueryClass =
Code.Class "JQuery"
|=> JQuery
|+> [
"jQuery.ajax" => AjaxConfig ^-> T<unit>
"jQuery.ajaxSetup" => AjaxConfig ^-> T<unit>
"jQuery.contains" =>
J.Element?container * J.Node?contained ^-> T<bool>
// ...

The use of the function would then look like this:

jQuery.Ajax(AjaxConfig(Async = false, ...))

The good news are several:

  • This is code-generating code - repetitive patterns can be abstracted over with plain functions in F#.

  • If a particular framework provides good documentation, this documentation can be parsed into EF values and then amended by merging it with hand-coded EF values where needed. Bindings generation is then semi-automatic.

  • Some common code-generating patterns will come with EF. Consider Pattern.Config above which generates a new configuration object class with the given required and optional fields.

  • Overload generation is automated. In EF, the type for a parameter that accepts either an int or a string can be spelled as T<int> + T<string>. The framework then figures out the correct overloads.

There is nothing particularly blog-worthy in the implementation of EF. It is mostly about figuring out the right interface (the most simple and yet diffuclt task there is). I will mention just one implementation detail I am particularly fond of. It is the use of F# operator overloading to describe method and function types. For example, these equalities are made to hold:

    T<int -> int -> int> = T<int> ^-> T<int> ^-> T<int>
T<int * int -> int> = T<int> * T<int> ^-> T<int>

Friday, June 11, 2010

Getting Started with PowerShell

In an attempt to get out of the Cygwin habbit, I have tried Windows PowerShell. It is definitely going in the right direction - not perfect yet, but worth a look.

First, download the Windows Management Framework.

Then, run powershell_ise (on Vista+ - by right-clicking the icon and selecting to run as Administrator). powershell is the shell itself, and runs in the usual command window. powershell_ise is a graphical wrapper around it. The ISE is generally helpful. Note that the ISE fails to run interactive sessions, however, such as the F# interactive.

Next step: run the following in PowerShell, otherwise no scripts will run.

Set-ExecutionPolicy RemoteSigned

Then, you really should create a profile. I store mine in Dropbox, so that I can share it across all the computers:

$dir = split-path -parent $profile # poor-man's dirname
mkdir -f $dir
echo '. "\My Dropbox\powershell\profile.ps1"' > $dir/profile.ps1

Now editing profile.ps1..

Designing a Core/Simplified JavaScript

I am working towards a core language for JavaScript... JavaScript by itself is nice for humans but not for compilers. The language definition has just one special case too many.

Here is a take on the Core:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
type UnaryOperator =
| TypeOf = 0
| Delete = 1
| BitwiseNot = 2

type BinaryOperator =
| Multiply = 0
| Divide = 1
| Modulo = 2
| Add = 3
| Subtract = 4
| LeftShift = 5
| SignedRightShift = 6
| UnsignedRightShift = 7
| LessThan = 8
| GreaterThan = 9
| Equal = 10
| Identical = 11
| InstanceOf = 12
| In = 13
| BitAnd = 14
| BitXor = 15
| BitOr = 16

type Primitive =
| Null
| Boolean of bool
| Integer of int64
| Double of double
| String of string
| Array of list<Expr>
| Object of list<string * Expr>
| Unary of UnaryOperator * Expr
| Binary of BinaryOperator * Expr * Expr
| New of Expr * list<Expr>
| Get of Expr * Expr
| Set of Expr * Expr * Expr
| Lookup of string
| Throw of Expr
| Try of Expr * Var * Expr
| While of Expr * Expr

And then expressions are instances of Core Scheme expressions instantiated with Primitive.

1
2
3
4
5
6
7
8
9
    type Expression<'T> =
| Assign of Var * Expression<'T>
| Apply of Expression<'T> * list<Expression<'T>>
| If of Expression<'T> * Expression<'T> * Expression<'T>
| Lambda of list<Id> * Expression<'T>
| Let of Id * Expression<'T> * Expression<'T>
| Var of Id
| Primitive of 'T
| Undefined

Notes:

  • No expression/statement distinction - this should be automated.

  • JavaScript calls are modelled with Apply where the first argument is always this argument. Therefore foo.bar(baz) becomes Apply (foo.[bar], foo :: baz).

  • Global variable references are modelled as Lookup, say window becomes Lookup "window".

  • LetRec clause, as in Scheme, is derived - it is modelled with Assign and Let.

  • Some JavaScript assignments are field assignments and not variable assignments - and these are a primitive, for example foo.bar = 1 becomes Set (foo, bar, 1).

  • No try-finally - can be reduced to Try.

  • No loops other than While - others can be reduced to it.

This seems to be expressive enough. I will probably update the definition after trying to write a few optimizations.

Thursday, June 10, 2010

VisualStudio Xml Editor Completion

Problem:

Get autocompletion for Apache Ivy files in VisualStudio.

Solution:

<?xml version="1.0" encoding="utf-8"?>
<ivy-module version="2.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://doiop.com/ivy-2.1.0.xsd">
</ivy-module>

How:

Copied the schema shipping with Ivy to Public folder in Dropbox and shortened the URL.