WebSharper Interface Generator

The WebSharper Interface Generator (WIG) is a tool (build task) for generating WebSharper bindings to JavaScript libraries. Bindings allow developers to access these libraries from typed F# code that gets compiled to JavaScript by WebSharper. While it is possible to create bindings manually, WIG allows to write the binding definitions in F#, making full use of the language to streamline repetitive tasks.

Simply put, WIG takes an F# value representing a set of classes, interfaces, and members together with their documentation and mappings to JavaScript, and generates a binding assembly from that definition. The binding is a .NET assembly containing the generated types and method stubs annotated with raw JavaScript code using the InlineAttribute custom attribute.

Getting Started

WIG is included with the WebSharper installer. To create a new project select the "Extension" template from WebSharper templates. This project file contains the line required for the WIG build task to run.

<WebSharperProject>InterfaceGenerator</WebSharperProject>

You get a small example in the Main.fs file, which ends in this:

[<Sealed>]
type Extension() =
    interface IExtension with
        member ext.Assembly =
            Definition.Assembly

[<assembly: Extension(typeof<Extension>)>]
do ()

This exposes the value defined by Definition.Assembly to the WIG compiler. The library that you write is used just as a generator for this value, any other code that it has will have no effect on the final assembly produced by the WIG build task.

Constructing Types

Defining classes, interfaces and member signatures requires an abstraction for types. Types are represented as WebSharper.InterfaceGenerator.Type.IType values. These values can describe both types from other assemblies (external) or type definitions in current WIG project.

Immutability and Identity

Adding members or attributes to a type declaration is mutable and returns the type declaration for chaining. However, all operators and helper functions work non-desctructively on member, attribute and resource definitions. This allows mutual recursion between types:

let A = Class "A"
let B = Class "B"

A |+> Instance [
    "getB" => T<unit> ^-> B
] |> ignore // "GetB" method was addded to method list of A

B |+> Instance [
    "getA" => T<unit> ^-> A
] |> ignore // "GetA" method was addded to method list of B

Example for immutability of members:

let GetCount = Method "getCount" (T<unit> ^-> T<int>)
let GetCountObs = GetCount |> Obsolete

A |+> Instance [ GetCount ]
|> ignore // A will have "GetCount" without the Obsolete attribute

B |+> Instance [ GetCountObs ]
|> ignore // B will have "GetCount" with the Obsolete attribute

Operator Reference

Function Operator Description
Method => Defines a method from name and signature
Property =@ Defines a property with a getter and setter
Getter =? Defines a read-only property
Setter =! Defines a write-only property
? Defines a named parameter
^-> Defines a function type
-* Defines the type of the this parameter on a function
*+ Defines the rest parameter (ParamArray in .NET)
Type.ArrayOf !| Defines an array type from its element type
!+ Defines arguments parameter (single ParamArray in .NET)
!? Defines an optional parameter, property or return type
* Defines a tuple type or joins parameters
+ Defines an overloaded parameter or a Choice property or return type
|=> Copies type definition identifier or applies attributes
|+> Adds members to a type definition

Side cases

  • When defining tuples, * expands the tuple if the left hand argument is already a tuple. If you want to define the type (A * B) * C, you must use Type.Tuple [ A * B; C ].

  • When defining functions like A * B ^-> C, the tuple on the left hand side is automatically converted to a Type.Parameters which creates multiple arguments from the tuple elements. If you want to describe a JavaScript function that do take a single 2-length array as argument, you must convert it to a single parameter explicitly: (A * B).Parameter ^-> C.

External Types

External types can be defined by using the T type function, for example T<int>, <int>, T<list<string>>, <list<string>>, T<MyOtherLibrary.SomeType>.

Type Combinators

Simpler types can be combined to form more complex types, including arrays, tuples, function types, and generic instantiations.

Type.ArrayOf T<int>
    // array, equivalent to T<int[]>
    // alternate syntax: !| T<int>

T<int> * T<float> * T<string>
    // tuple, equivalent to T<int * float * string>
    // alternate syntax: Type.Tuple [T<int>; T<float>; T<string>]

T<int> ^-> T<unit>
    // function, equivalent to T<int -> unit>

T<System.Collections.Generic.Dictionary<_,_>>.[T<int>, MyTypeDef]
    // adding type parameters to a generic type
    // compile-time error if number of parameters do not match

In addition, delegate types can be formed. WebSharper treats delegate types specially: their are compiled to JavaScript functions accepting the first argument through the implicit this parameter. For example when this can be helpful, consider following JavaScript function:

function iterate(callback, array) {
    for (var i = 0; i < array.length; i++) {
        callback.call(array[i], i);
    }
}

To bind this function to WebSharper one needs to provide a type for the callback parameter, which is a function called with an element of the array passed through the this implicit parameter and the array index passed through the first parameter. This can be achieved thus:

let callbackType = T<obj> -* T<int> ^-> T<unit>
let iterateType  = callbackType * Type.ArrayOf T<obj> ^-> T<unit>

The type of the callback is then compiled to a delegate type in F#, Func<obj,int,unit>.

Self Placeholder

The TSelf type value will be evaluated to the type the defined member is added to. This allows creating a member, list of members or ClassMembers value (list of members marked with Instance or Static) and reuse it by adding it to multiple type declarations.

Defining Members

The primary use of type values is the definition of member signatures, methods, constructors, properties and fields.

Methods

Method representations are constructed using the Method (short form: =>) combinator that takes the name of the method and the corresponding functional type. Some examples:

let methods =
    [
        Method "dispose" T<unit -> unit>
        Method "increment" (T<int> ^-> T<int>)
        "add" => T<int> * T<int> ^-> T<int>
    ]

Void return types and empty parameter lists are indicated by the unit type, multiple parameters are indicated by tuple types. It is an error to define a method with a non-functional type.

Parameter Names

By default, method parameters get autogenerated names. You can customize parameter names as follows:

let methods =
    [
        Method "dispose" (T<unit>?object ^-> T<unit>)
        Method "increment" (T<int>?value ^-> T<int>)
        "add" => T<int>?x * T<int>?y ^-> T<int>
    ]

Variable-Argument Signatures

F# supports variable-argument methods via the System.ParamArrayAttribute annotation. WebSharper understands this annotation and compiles such methods and delegates to variable-argument accepting functions in JavaScript. Here is the syntax to define a variable-argument signature:

let methods =
    [
        "t1" => !+ T<obj> ^-> T<unit>
        "t2" => T<string> *+ T<obj> ^-> T<unit>
    ]

When compiled to F#, these methods will have the following signatures:

val t1 : ([<System.ParamArray>] args: obj []) -> unit
val t2 : string * ([<System.ParamArray>] args: obj []) -> unit

Optional Parameters

Parameters can be made optional:

Method "exit" (!? T<string>?reason ^-> T<unit>)

Signatures such as the one above generate multiple members by implicit overloading (see below).

Implicit Overloads

Type unions facilitate describing JavaScript methods that accept arguments of either-or types. Type unions are implemented by implicit overloading of generated members. For example:

"add" => (T<int> + T<string>) * (T<obj> + T<string>) ^-> T<unit>

This method can accept either string or an int as the first argument, and either an obj value or a string as the second. Four overloads are generated for this signature.

Properties

Properties can be generated with a getter, a setter or both. Below are the full and abbreviated syntax forms:

let properties =
    [
        Getter "ReadOnly" T<int>
        Setter "WriteOnly" T<int>
        Property "Mutable" T<int>
    ]

let shorthand =
    [
        "ReadOnly"  =? T<int>
        "WriteOnly" =! T<int>
        "Mutable"   =@ T<int>
    ]

Indexed properties

Properties can have indexers. "" =@ T<string> |> Indexed T<int> creates an indexed property where x.[n] : string translates to x[n]. If the property name is not empty: "Lines" =@ T<string> |> Indexed T<int> creates an indexed property where x.Lines.[n] translates to x.Lines[n].

If you define a custom inline, use $index to refer to the index parameter.

Constructors

Constructors definitions are similar to methods but do not carry a return type. Examples:

let constructors =
    [
        Constructor T<unit>
        Constructor (T<int>?width * T<int>?height)
    ]

JavaScript Object Expression

ObjectConstructor (T<int>?x * T<int>?y) defines a .NET constructor with JavaScript inline { x = $x, y = $y }.

Interfaces

Interfaces are defined using the Interface keyword and then extended with members.

Interface "IAccessible"

Member Definitions

Member definitions are appended using the |+> combinator, for example:

Interface "IAccessible"
|+> [
    "Access" => T<unit->unit>
    "LastAccessTime" =? T<System.DateTime>
]

Interface definitions take a list<CodeModel.IInterfaceMember>. Class definitions take a CodeModel.ClassMembers value, which can be constructed from a list<CodeModel.IClassMember> using the Instance and Static functions.

Inheritance

Interfaces can inherit or extend multiple other interfaces. The syntax is as follows:

Interface "IAccessible"
|=> Extends [T<System.IComparable>; T<System.IEnumerable<int>>]
|+> [
    "Access"         => T<unit->unit>
    "LastAccessTime" =? T<System.DateTime>
]

Classes

Class definition is very similar to interface definition. It starts with the Class keyword:

let Pear =
    Class "Pear"
    |+> Static [
        "Create" => T<unit> ^-> TSelf
    ]
    |+> Instance [
        "Eat"     => T<unit->unit>
        "IsEaten" =? T<bool>
    ]

Class Inheritance

The syntax for class inheritance is as follows:

Class "ChildClass"
|=> Inherits BaseClass

Interface Implementation

The syntax for interface implementation is as follows:

Class "MyClass"
|=> Implements [T<System.IComparable>]

Nested Classes

Class nesting is allowed:

Class "MyClass"
|=> Nested [
    Class "SubClass"
]

Generics

Generic Types

Generic types and interfaces are defined by prefixing the definition with the code of the form Generic --- fun t1 t2 t3 ->. The t1..t3 parameters can be used as types in the definition and represent the generic parameters. The length of the operator should be equal to the number of parameters (up to 4).

For example:

Generic -- fun t1 t2 ->
    Interface "IDictionary"
    |+> [
        "Lookup"      => t1 ^-> t2
        "ContainsKey" => t1 ^-> T<bool>
        "Add"         => t1 * t2 ^-> T<unit>
        "Remove"      => t1 ^-> T<unit>
    ]

This compiles to the following signature:

type IDictionary<'T1,'T2> =
    abstract member Lookup : 'T1 -> 'T2
    abstract member ContainsKey : 'T1 -> bool
    abstract member Add : 'T1 * 'T2 -> unit
    abstract member Remove : 'T1 -> unit

This syntax can produce up to 4 type parameters. To have more, the Generic - helper can be nested, or use GenericN n - fun [t1; t2; .. tN] ->. Although this gives an incomplete pattern match warning, there will be no runtime errors if the matching the list for the provided length n.

Generic Methods

Similarly, generic methods are generated using lambda expressions, for example:

Generic - fun t ->
    "length" => T<list<_>>.[t] ^-> T<int>

This code would generate the following F# signature:

val Length<'T> : list<'T> -> int

You can use Generic % to add the same generics to a list of members, Generic * to add the same generics to a ClassMembers value. Also Generic + ["a"; "b"; ...] --- ... specifies the names of the type parameters.

Type Constraints

You can now set type constraints on parameters using p.Constraints <- [...] inside the lambda passed to Generic -. Previous WithConstraints helper is removed as we want to have all helper functions named With... to be non-destructive.

Modifiers

Documentation Comments

Documentation comments can be added using the WithComment function.

Customizing JavaScript

By default, inline JavaScript definitions are inferred for all methods and properties from their names. This is intuitive and convenient but not fully general. Therefore it is possible to bypass the inferred inlines and customize the generated bindings.

Default Inline Generation

Default bindings are name-based. For example, a static function called foo with three arguments on a class called Bar, produces the JavaScript inline Bar.foo($0,$1,$2).

Generated .NET names are automatically capitalized, so that this function is accessible as Bar.Foo from F#.

Qualified names can be used on classes that are accessible in JavaScript with a qualified name, for example:

Class "geometry.Point"

This generates a .NET class Point which binds all static members as geometry.Point.foo() in JavaScript.

Inline Transformations

Functions

The FSharpFunc<'TArg, 'TRes> type (which is used for lambdas by the F# compiler) always take one parameter (which can be a tuple). In WebSharper translation, these become JavaScript functions taking a single fixed-length array (although curried functions only used in local scope are optimized). However, it is often required that we pass functions defined in F# to a JavaScript library (for example event handlers, callbacks, functional-style libraries). WIG automatically converts between these function calling conventions.

Erased unions

Union types (for example T<int> + T<string>) can create method overloads, but also Union typed properties or method return types when the cases can be distinguished in JavaScript using the typeof, Array.isArray and the arr.length functions. In F# this means either at most one array case or possibly multiple tuple cases with all different length, at most one number type (including DateTime and TimeSpan, which are proxied as a Number), string, bool, and at most one other object type. If there are cases which can't be separated, the type will default to obj.

Option

By using the !? operator on the type of a property, it will be an option type in F# which is converted to and from an existing or missing field on a JavaScript object. On method returns, undefined is converted to None, all other values (including null) to Some ....

Custom Inline Transformations

You can add a custom defined inline transformation with the WithInterop helper. This takes a record with an In and an Out field, both string -> string. For example:

let Index =
    T<int> |> WithInterop {
        In  = fun s -> s + " - 1"
        Out = fun s -> s + " + 1"
    }

Use this Type.Type value instead of T<int> in your member declarations where you want to handle an index as 1-based in your code, but pass it to and get it from a 0-based value in a JavaScript library. On method parameters and property setters the In function will be used on the parameter or property value in the automatic inline. On method return values and property getters the Out function will be used on the whole inline of the method or property getter.

Erasing Inline Transformations

Use the WithNoInterop helper to clean any automatic and custom inline transformations from a Type.Type value.

Customizations

Custom Names

The simplest form of customization allows to decouple the .NET name of a member from the name used by the inline generation process. This is done by the WithSourceName function. For example:

"ClonePoint" => Point ^-> Point
|> WithSourceName "clone"

This generates a method that is available as ClonePoint from .NET but calls clone in JavaScript.

Custom Inline Methods

The method and constructor inlines can be set explicitly by using WithInline.

Custom Inline Properties

Properties have separate inlines for the getter and the setter methods. These can be set explicitly by using WithGetterInline and WithSetterInline respectively.

Custom Inlines Using Transformations

To define a custom inline for a method that still makes use of the default or custom inline transformations on parameters and return value, use the WithInteropInline helper. It takes a function typed (string -> string) -> string, use the provided function on a parameter name or index to get its transformed inline. For example, defining an on event setter with function argument detupling:

"onChange" => (T<int> * T<obj> ^-> T<unit>)?callback ^-> T<unit>
|> WithInteropInline (fun tr -> "$this.on('change', " + tr "callback" + ")"

Similar helpers exists for property getters and setters: WithInteropGetterInline and WithInteropSetterInline. For getters, provided function is only usable for transforming "index" in the case of an indexed property, and for setters transforming "value" or "index".

Obsoleted Members and Types

Use the Obsolate helper to mark a type or member definition with System.ObsoleteAttribute. ObsolateWithMessage also sets a custom warning message.

Best Practices

The benefit of using F# for generating the bindings is that repetitive tasks or patterns can be distributed as functions. Several such patterns are pre-defined in the standard library.

Configuration Class Patterns

These patterns for constructing member lists are useful for describing JavaScript configuarion objects. Configuration objects typically are simple collections of fields, most of them optional, that are used to describe how another object it to be constructed or operate. Let us take a simple example:

let MyConfig : Class =
    Class "classname"
    |+> Pattern.RequiredFields [
            "name", T<string>
        ]
    |+> Pattern.OptionalFields [
            "width", T<int>
            "height", T<int>
        ]

This definition would produce a class useable from F# that would compile to simple JavaScript object literals:

MyConfig("Alpha", Width=140)

The Width property is write-only, but there is also a WidthOpt property generated which allow access to the optional field typed as an F# option value.

Pattern.ObsoleteFields can be used similarly, to create a list of properties with the Obsolete attribute.

Enumeration Patterns

JavaScript functions often accept only a fixed set of constants, such as strings. Typing such parameters with string would be misleading to the user. The enumeration pattern allows to generate a type that is limited to a specific set of constants, specified as either inlines or strings literals. See Pattern.EnumInlines and Pattern.EnumStrings, both of which generate Class values.

Assembly and Namespaces

You have to provide the WIG compiler with a single CodeModel.Assembly value as described in the "Getting Started" section. Construct this with the Assembly helper which takes a list of CodeModel.Namespace values which can be created with the Namespace helper specifying its name and a list of CodeModel.NamespaceEntity values. These latter can be type definitions or resource types.

Missing Type Definitions

If you have a type definition which you refer to but does not get included in a namespace in the assembly definition, when building the library, you will get an error.

Resources

You can define WebSharper resource classes using the Resource function. Pass it to AssemblyWide to make the resource loaded if anything from the currently created assembly is used.

Namespace "WebSharper.JQuery.Resources" [
    Resource "JQuery" "http://code.jquery.com/jquery-1.11.2.min.js"
    |> AssemblyWide
]

Use the Requires helper to add a list of defined resources as a dependency to a type definition or another resource definition. Use the RequiresExternal to add a list of resource classes from another assembly to a type definition or a resource definition.