In the age of the web Typed functional-first programming revisited

Most programming languages were designed before the age of web. This matters because the web changes many assumptions that typed functional language designers tak for granted. For example, programs do not run in a closed world, but must instead interact with (changing and likely unreliable) services and data sources, communication is often asynchronous or event-driven, and programs need to interoperate with untyped environments like JavaScript libraries.

For dynamically-typed languages, the changing assumptions are not such a big issue (and e.g. Clojure with ClojureScript adapt extremely well), but how should statically-typed programming languages adapt to the modern world? In this article, I look at one possible answer that is inspired by the F# language and various F# libraries. In F#, we use type providers for integration with external information sources and for integration with untyped programming environments. We use lightweight meta-programming for targeting JavaScript and computation expressions for writing asynchronous code.

This blog post is a shorter version of a ML workshop paper that I co-authored earlier this year and you should read this more as a position statement. I'm not sure if F# and the solutions shown here are the best ones, but I think they highlight very important questions in programming language design that I very much see as unsolved.

The article has two sections. First, I'll go through a simple case study showing how F# can be used to build a client-side web app. Then, I'll discuss some of the implications for programming language design based on the example.

Case Study: Web-based data analytics

We write a web application, which lets the user compare university enrollment in a number of selected countries and regions around the world. The result runs as JavaScript and fetches data dynamically from the World Bank. The demo is simple and works with just a single data source, but it is a realistic app that could be built in context like data journalism to accompany an article. You can run the demo live on the FunScript web site. Here, I'll use a slightly modified version to better illustrate the issues. It builds a dashboard like this one:

Accessing World Bank data with type providers

The dashboard shows university enrollment and lets the user choose among a number of pre-defined countries. The list of countries is generated from a list in the source code that accesses countries using the WorldBank type provider from the F# Data library. The type provider exposes the individual countries as members of an object:

type WorldBank = WorldBankDataProvider<Asynchronous=true>

let data = WorldBank.GetDataContext() 
let countries = 
  [ data.Countries.``European Union``
    data.Countries.``Czech Republic`` 
    data.Countries.``United Kingdom`` 
    data.Countries.``United States`` ]

The type provider connects to the World Bank and obtains a list of countries at compile-time and at edit-time (when using auto-completion in an editor). This means that the list is always up-to-date and we get a compile time error when accessing a country that no longer exists.

On the first line, we provide a static parameter Asynchronous to instruct the type provider to generate only non-blocking operations. This is necessary for a web-based application, because JavaScript only supports non-blocking calls (using AJAX callbacks) to fetch the data.

Interoperating with JavaScript libraries

To run the sample application on the client-side we use FunScript, which is a library that translates F# code to JavaScript. Aside from running as JavaScript, we also want to use standard JavaScript libraries, including jQuery for DOM manipulation and Highcharts for charting. FunScript comes with a type provider that imports TypeScript definitions for JavaScript libraries (the latest version of FunScript uses code generation rather than type providers for technical reasons):

type j = TypeScript.Api<"../files/jquery.d.ts">
type h = TypeScript.Api<"../files/highcharts.d.ts">

The d.ts files are type annotations created for the TypeScript language. Here, the type provider mechanism lets us leverage an existing effort for annotating common JavaScript libraries. The type provider analyses those definitions and maps them into F# types named j and h that contain statically typed functions for calling the JavaScript libraries. We use these to generate checkboxes that appear on the right.

let jQuery (command:string) = 

let infos = 
  countries |> (fun country -> 
    let inp = jQuery("<input>").attr("type","checkbox") 
    jQuery("#panel").append([| inp |])
    jQuery("#panel").append([| box country.Name |]) 
    country.Name, country.Indicators, inp)

To manipulate the DOM, we are using the jQuery library in a way that is very similar to code that one would write in JavaScript. Note that members like \ident{append} and \ident{attr} are standard jQuery methods and the compiler sees them as ordinary object members (this blog is using F# Compiler Service and so you can hover over the identifiers and see the same tooltip that you would see in any F# editor).

Although the jQuery library is not perfect, it is a de facto standard in web development. The FunScript type provider makes it possible to integrate with it painlessly without explicitly specifying any FFI interface and without manual wrapping.

Note that we use a standard F# function \ident{} to iterate over the countries. The function passed as an argument has a side-effect of creating the HTML elements, but it also returns a new list. The result is a list of `string Indicators jQuery$ values representing the country name, its indicators (for accessing the World Bank data) and the created DOM object representing the checkbox.

Loading data and updating the user interface

The main part of the sample program is a function render that asynchronously fetches data for selected countries and generates a chart. To keep the code simple, we iterate over the `infos list from the previous section and load data for countries one by one:

let render () = async { 
  let head = "School enrollment, tertiary (% gross)" 
  let o = h.HighchartsOptions() 
  o.chart <- h.HighchartsChartOptions(renderTo = "plc") 
  o.title <- h.HighchartsTitleOptions(text = head) 
  o.series <- [| |] 

  for name, ind, check in infos do 
    if unbox<bool> (":checked")) then 
      let! vals = ind.``School enrollment, tertiary (% gross)`` 
      let data = vals |> (fun (k,v) -> 
        [| number k; number v |]) |> Array.ofSeq 
      o.series.push(h.HighchartsSeriesOptions(data,name)) } 

We're using F# asynchronous workflows to fetch the data without blocking - when accessing the value of the School enrollment, tertiary (\% gross) indicator, we use the let! keyword. The indicator is a member exposed by the WorldBank type provider as an asynchronous computation (as requested by the static parameter). The rest of the code is mostly dealing with the DOM and the Highcharts library using the API imported by FunScript -- we iterate over all checkboxes and generate a new chart series for each checked country.

Two notable points here are that async translated to JavaScript is restricted to a single thread, which is not the case for ordinary F# code and that the HighchartOption object preserves some of the underlying JavaScript semantics - we create an empty array o.series and then add elements to it using o.series.push which would not be possible in ordinary F# code.

Finally, the last part of the example code registers event handlers that redraw the chart when the checkbox is clicked:

for _, _, check in infos do _ -> Async.StartImmediate(render()))

The click operation (exposed by jQuery) takes a function that should be called when the event occurs. Calling it is a side-effectful operation that registers the handler. As render is an asynchronous operation, we invoke it using the StartImmediate primitive from the F# library, which starts the computation without waiting for the result (the only way to start a non-blocking operation in JavaScript).

Analysis: Learning from the case study

The case study is quite simple, but it shows that you can build a simple interactive visualization in just 30 lines of F#. It also illustrates many of the important issues that we face when working with the web. There are a number of good things that we get to keep from statically-typed functional-first programming style:

There are also a few things that are a bit unexpected when you look at the example through the perspective of traditional statically-typed languages:

The approach that you can use with F# often has both positive and negative side. For example, we can easily access data from World Bank, but it means that we have to give up some of the safety properties. The approach highlighted here is just one possible and the case study shows that it works in practice, but there might be other options.

I want to spend the rest of this section discussing some of the important implications for the design of statically-typed programming languages of the future. If languages want to provide the user experience showed in the above case study, what do they need to look like?

Integrating with external data sources

Statically-typed programming languages are designed with the assumption that the program runs in a world that is closed - all we can call and access comes from a library that is defined within the world of the language. This is not the case. Even before the web, applications need to perform I/O, but the web makes this even more obvious. In terms of programming language theory, the starting point of a programming language needs to change as follows:

(Empty world)
(Type providers)

The syntax \(\Gamma \vdash e : \tau\) denotes that a program \(e\) has a type \(\tau\) in a context defined by \(\Gamma\). The context typically contains variables, library functions and so on. With type providers, the context is much richer - a type provider is like a projection \(\pi\) that can import anything from the outside world into the programming language context.

The World Bank provider is an interesting example - it is designed for one specific data source (in contrast to the XML, JSON and CSV type providers). The projection it implements generates a type Countries with individual countries as members. Each country returns a value of Indicators that is also generated and it contains all World Bank indicators as members. The type provider is erased during compilation and replaced with runtime implementation as follows:

〚 data.``Czech Republic`` 〛=
〚 cz.``School enrollment, tertiary (% gross)`` 〛=

The underlying operations (GetCountry, AsyncGetIndicator) are normal functions of an underlying runtime library. The type provider generates a light layer on top of this runtime library. An important thing is that the type provider generates code that refers to the country and indicator using a code (even though the type we see uses a more friendly name). Knowing this helps us understand the properties of the type povider:

Using type providers to access external data certainly relaxes traditional type safety conditions. However, rather than introducing new unsafety, this just makes existing issues more apparent - if we wrote data.GetCountries().GetCountry("CZE"), we would get the same runtime error if Czech Republic disappeared. With type providers, this is now reflected in the type system, but with a twist that the type safety depends on the external data source.

Integrating with external environments

The case study uses type providers not just for external data sources, but also for integration with external execution environments - in particular, for importing definitions of JavaScript libraries. Another example of this kind of use is the R type provider that allows F# programs to call R functions.

The TypeScript type provider makes it possible to use jQuery and Highcharts almost as if they were native F# libraries, but the word almost is important here, because there is always going to be some mismatch between F# and any other external environment. Let's look at a part of the jquery.d.ts file that defines TypeScript types for jQuery:

declare var jQuery : JQueryStatic;
interface JQueryStatic { 
  (selector : string, context? : any) : JQuery; 
interface JQuery { 
  attr(attributeName : string) : string; 
  attr(attributeName : string, value : any) : JQuery; 

The snippet defines a global variable jQuery which is an invokable object that returns JQuery value with an overloaded attr method. When mapping this to F#, we have to solve the following issues:

With F# used as the host language, we are quite fortunate, because we get many functional language features but also many object-oriented features. This means that when mapping external environments into F#, there is often a corresponding construct in F#. However, there are still things that cannot be expressed in F# and for those, we have to find alternative encoding.

Alternatives for environment integration

There are two alternatives for interoperating external worlds that you can find in other X-to-JavaScript compilers:

I believe that modern programming languages will increasingly need to be able to access external environments and so the F# approach with type providers is an important direction. After all, we need to access rich visualization libraries built in the JavaScript environment or rich statistical functionality available in R.

Mixing stronger and weaker type systems

Another interesting issue that becomes apparent in the case study is that we are mixing the F# type system with a type system of TypeScript. Now, the TypeScript type system is deliberately unsound. Does this mean that we are breaking the soundness of F# too? This is not really happening directly in this case, because we are just importing TypeScript library definitions - but a JavaScript library we are calling can certainly return an unexpected value.

In a heterogeneous environment, we will always be mixing type systems that have different strength or expressivity.

When interoperating with another environment, there will always be some mismatch. We either need to reconstruct some information (e.g. using jquery.d.ts annotation file), or through some other mechanism (R provider uses runtime reflection). With F#, we have the obj type and unsafe features like unbox, so this gives us at least some way of encoding unsafe operations, but arguably, having something akin to C# dynamic would sometimes be useful.

Heterogeneous execution

Finally, the last interesting aspect of the case study is that we were able to use a large number of standard F# language features and libraries even though the code was translated and executed as JavaScript and the JavaScript semantics differ in a number of ways. For example, we used ordinary F# async workflows and wrote:

let render () = async { 
  let o = h.HighchartsOptions() 
  (Initialize Highcharts options)
  for name, ind, check in infos do 
    if unbox<bool> (":checked")) then 
      let! vals = ind.``School enrollment, tertiary (% gross)`` 
      let data = (Convert values to array)
      o.series.push(h.HighchartsSeriesOptions(data,name)) } 

Strictly speaking, F# asynchronous workflows cannot be translated to JavaScript. They run in parallel and are scheduled using thread pool, which is not available when running code as JavaScript and they have other properties (they can capture SynchronizationContext of the thread, etc.)

The alternative would be to define a JavaScript-based version of asynchronous workflows, say jsasync { .. } and require the developer to write code using this semantically correct version instead. This makes the code clumsier, but arguably more correct. However, the semantic differences with JavaScript go much further. JavaScript has different exceptions and even different numerical type. To be fully correct, we'd have to use jsfloat, jsexception and so on!

The example in the case study follows the less correct path - it maps ordinary F# async workflows to the closest thing that can be done in JavaScript. It provides mapping for Async.StartImmediate (which has close equivalent in the browser) but not for Async.Start (which would require a background thread).

F# numerical types are mapped to native JS numerical types with all the potential issues this brings (floating point numbers use JavaScript semantics). Also, F# arrays are mapped to JavaScript arrays, which allows us to use some of the JavaScript semantics and use o.series.push to append element to an array (which cannot be done in pure F#).

Conclusions: Languages for the modern age

This article uses the "age of the web" phrase, because it nicely illustrates many of the important problems that modern languages face. More than before, we need to access data and call services from the outside world (that cannot be fully trusted) and we need to interoperate with other execution environments (which have different features and different levels of safety).

Programming languages that we use today were not designed with these constraints in mind. They assume that programs live in a closed world (where everything has been created in the same language or comes from the same runtime).

Integrating with the open world is much easier for dynamically-typed languages like Clojure with ClojureScript. In the world of statically-typed languages, the situation is much harder - and I think that the F# type provider mechanism is the first step towards languages that give the useful benefits of (smart) static type systems while being a good fit for solving problems that come with modern kinds of applications.

Going over the case study and the analysis, I think there are two key points.

Flexibility with escape options

First, when we want to interoperate with outside environments, the host language needs to provide a lot of flexibility. In case of F#, you get support for both functional and object-oriented style. This means that more of the outside world can be mapped to a close construct in the F# world. I believe that this is crucial - a language can practically interoperate with the outside world if the mapping is not too cumbersome.

The other aspect is that if the host language is strict in some way, it needs to provide some escaping mechanism. F# does this with the obj type and unbox. This works, but there is probably more that could be done here. (And there are also other situations where an escape mechanism would help.)

Relativized safety and semantics

Does the flexibility and the escaping hurt the nice safety properties of the host language? If you are a strict theoretician, then the answer is of course yes. But I think there is a useful middle ground here. Using F# as we did in the case study here, we get a nice property:

When you use the core subset of F#, the program will behave according to the usual core F# semantics and you are guaranteed the usual core F# properties. Using non-core features in other environments may have different semantics and different properties.

To an extent, you can already see this with F# when working with .NET. F# types do not allow the null value, but .NET types do. When you use functional style with F#, you do not need type annotations, but when you use objects, you do. So for F#, even .NET is an outside environment that breaks some of the language properties.


I think we need to give up on the idea that programming languages should be designed for one controlled execution environment. There should be a core with certain core properties (like the ML core of F#) with additional layers that are more flexible and adaptable to allow running in the heterogenous modern world. How exactly this should be done, that's an interesting open question, but I think F# with type providers shows one important component of the solution.

Multiple items
namespace FSharp

namespace Microsoft.FSharp
Multiple items
namespace FSharp.Data

namespace Microsoft.FSharp.Data
namespace FunScript
namespace FunScript.TypeScript
type WorldBank = WorldBankDataProvider<...>

Full name: Typed-revisited.WorldBank
type WorldBankDataProvider

Full name: FSharp.Data.WorldBankDataProvider

<summary>Typed representation of WorldBank data with additional configuration parameters. See for terms and conditions.</summary>
                        <param name='Sources'>The World Bank data sources to include, separated by semicolons. Defaults to `World Development Indicators;Global Financial Development`.
                        If an empty string is specified, includes all data sources.</param>
                        <param name='Asynchronous'>Generate asynchronous calls. Defaults to false.</param>
val data : WorldBankDataProvider<...>.ServiceTypes.WorldBankDataService

Full name:
WorldBankDataProvider<...>.GetDataContext() : WorldBankDataProvider<...>.ServiceTypes.WorldBankDataService
val countries : WorldBankDataProvider<...>.ServiceTypes.Country list

Full name: Typed-revisited.countries
property WorldBankDataProvider<...>.ServiceTypes.WorldBankDataService.Countries: WorldBankDataProvider<...>.ServiceTypes.Countries
type j = Api<...>

Full name: Typed-revisited.j
type Api

Full name: FunScript.TypeScript.Api
type h = Api<...>

Full name: Typed-revisited.h
val jQuery : command:string -> Api<...>.JQuery

Full name: Typed-revisited.jQuery
val command : string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

type string = System.String

Full name: Microsoft.FSharp.Core.string
property Api<...>.jQuery: Api<...>.JQueryStatic
Api<...>.JQueryStatic.Invoke() : Api<...>.JQuery
Api<...>.JQueryStatic.Invoke(selector: string) : Api<...>.JQuery
Api<...>.JQueryStatic.Invoke(element: Api<...>.Element) : Api<...>.JQuery
Api<...>.JQueryStatic.Invoke(elementArray: Api<...>.Element []) : Api<...>.JQuery
Api<...>.JQueryStatic.Invoke(object: Api<...>.JQuery) : Api<...>.JQuery
Api<...>.JQueryStatic.Invoke(func: Api<...>.Function) : Api<...>.JQuery
Api<...>.JQueryStatic.Invoke(selector: string, context: obj) : Api<...>.JQuery
val infos : (string * WorldBankDataProvider<...>.ServiceTypes.Indicators * obj) list

Full name: Typed-revisited.infos
Multiple items
module List

from Microsoft.FSharp.Collections

type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name:
val country : WorldBankDataProvider<...>.ServiceTypes.Country
val inp : obj
val box : value:'T -> obj

Full name:
property Runtime.WorldBank.Country.Name: string
property WorldBankDataProvider<...>.ServiceTypes.Country.Indicators: WorldBankDataProvider<...>.ServiceTypes.Indicators

<summary>The indicators for the country</summary>
val render : unit -> Async<unit>

Full name: Typed-revisited.render
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
val head : string
val o : Api<...>.HighchartsOptions
property Api<...>.HighchartsOptions.chart: Api<...>.HighchartsChartOptions
type HighchartsChartOptions =
  new : unit -> HighchartsChartOptions
  member alignTicks : bool with get, set
  member animation : HighchartsBoolOrAnimation with get, set
  member backgroundColor : HighchartsColorOrGradient with get, set
  member borderColor : string with get, set
  member borderRadius : float with get, set
  member borderWidth : float with get, set
  member className : string with get, set
  member defaultSeriesType : string with get, set
  member events : HighchartsChartEvents with get, set

Full name: FunScript.TypeScript.Api,files="../files/highcharts.d.ts".HighchartsChartOptions
property Api<...>.HighchartsOptions.title: Api<...>.HighchartsTitleOptions
type HighchartsTitleOptions =
  new : unit -> HighchartsTitleOptions
  member align : string with get, set
  member floating : bool with get, set
  member margin : float with get, set
  member style : HighchartsCSSObject with get, set
  member text : string with get, set
  member useHTML : bool with get, set
  member verticalAlign : string with get, set
  member x : float with get, set
  member y : float with get, set

Full name: FunScript.TypeScript.Api,files="../files/highcharts.d.ts".HighchartsTitleOptions
property Api<...>.HighchartsOptions.series: Api<...>.HighchartsSeriesOptions []
val name : string
val ind : WorldBankDataProvider<...>.ServiceTypes.Indicators
val check : obj
val unbox : value:obj -> 'T

Full name: Microsoft.FSharp.Core.Operators.unbox
type bool = System.Boolean

Full name: Microsoft.FSharp.Core.bool
val vals : seq<obj * obj>
val data : obj [] []
module Seq

from Microsoft.FSharp.Collections
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>

Full name:
val k : obj
val v : obj
module Array

from Microsoft.FSharp.Collections
val ofSeq : source:seq<'T> -> 'T []

Full name: Microsoft.FSharp.Collections.Array.ofSeq
type HighchartsSeriesOptions =
  new : unit -> HighchartsSeriesOptions
  member data : obj with get, set
  member index : float with get, set
  member legendIndex : float with get, set
  member name : string with get, set
  member stack : obj with get, set
  member ``type`` : string with get, set
  member xAxis : float with get, set
  member yAxis : float with get, set

Full name: FunScript.TypeScript.Api,files="../files/highcharts.d.ts".HighchartsSeriesOptions
Multiple items
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task -> Async<unit>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
static member Async.StartImmediate : computation:Async<unit> * ?cancellationToken:System.Threading.CancellationToken -> unit
val o : obj
type HighchartsOptions =
  new : unit -> HighchartsOptions
  member chart : HighchartsChartOptions with get, set
  member colors : string [] with get, set
  member credits : HighchartsCreditsOptions with get, set
  member exporting : HighchartsExportingOptions with get, set
  member ``global`` : HighchartsGlobalOptions with get, set
  member labels : HighchartsLabelsOptions with get, set
  member lang : HighchartsLangOptions with get, set
  member legend : HighchartsLegendOptions with get, set
  member loading : HighchartsLoadingOptions with get, set

Full name: FunScript.TypeScript.Api,files="../files/highcharts.d.ts".HighchartsOptions
o.chart <- h.HighchartsChartOptions(renderTo = "plc")
  o.title <- h.HighchartsTitleOptions(text = head)
  o.series <- [| |]
vals |> (fun (k,v) ->
        [| number k; number v |]) |> Array.ofSeq

Published: Wednesday, 9 September 2015, 5:14 PM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: f#, type providers, web, functional programming, research