Haskell in web browser

From HaskellWiki
Revision as of 02:33, 15 March 2008 by DimitryGolubovsky (talk | contribs) (URI)
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
Haskell in web browser
A Tutorial

Preface

This Tutorial is written in connection with launch of the experimental Yhc Web Service, an online tool to explore the possibilities of Haskell use as a language to program client part (that is, running in a browser) of Web applications by the means of conversion of Haskell source to Javascript.

The idea of such conversion has been around for a while <insert citations>, and Yhc Javascript backend is its practical implementation. Use of functional languages for client-side Web programming is a relatively new phenomenon, and hopefully this Tutorial along with the Service online will attract more attention and subsequently programmers resources into this area.

Basics of programming for web browser

The most widespread (and natively interpreted by most existing Web browsers) language for client-side Web programming is Javascript. Several APIs are exposed by browser to Javascript programs, such as Document Object Model (DOM), Cascading Style Sheets (CSS), and XML HTTP Request to name a few. In order for a Haskell program to communicate with browser using these APIs, proper language bindings were created. The subsections of this section discuss approaches and methods used to create those bindings. Although direct use fo these bindings may not even be needed in most cases, it is useful to have understanding of how things work at this level. Please note though, that this Tutorial does not focus on the specifics of DOM/CSS/XMLHTTP programming per se; it contains information how to use these interfaces in a Haskell program. For details please refer to the original sources at the Web Consortium. Also, W3 Schools website contains very useful information and practical excercises.

DOM

Formalized description of DOM interfaces is provided by the Web Consortium in the form of OMG IDL definitions. An example of such definitions can be found here.

It was necessary to convert these definitions to Haskell function declarations to make them available to Haskell programs for Web browser. The special utility, domconv is part of the Javascript backend toolset. The utility is based on H/Direct, although most of non-IDL related functionality was stripped, and Haskell source generator was completely rewritten. In this section, we discuss the logic of IDL to Haskell conversion.

DOM interfaces vs. Haskell classes

Web Consortuim's DOM definitions are presented as a hierarchy of interfaces. For example, the Node interface is a parent to the majority of other interfaces, such as Document (direct ancestor), or HTMLElement (not a direct ancestor, but HTMLElement should inherit all properties and methods of Node).

This is achieved by defining Haskell type classes whose hierarchy repeats the hierarchy of DOM interfaces. Thus, we have the CNode and CDocument classes. For each DOM interface, also a phantom data type is defined: TNode, and TDocument correspondingly. Phantom types are assigned to concrete values (references to DOM objects) while type classes are used to constrain types of parameters of functions working with those DOM objects. The CDocument class is defined as:

class CNode a => CDocument a
data TNode
data TDocument
instance CNode TNode
instance CDocument TDocument
instance CNode TDocument

to reflect inheritance of Document from Node. Accordingly, continuing our example, for HTMLElement, we have:

class CNode a => CElement a
class CElement a => CHTMLElement a
data THTMLElement
instance CElement THTMLElement
instance CHTMLElement THTMLElement
instance CNode THTMLElement

and so on. This means that a value of THTMLElement may be passed as argument to a function expecting an instance of CNode, but not the opposite. Similarly, a value of TDocument can by no means be passed to a function expecting a THTMLElement. This increases type safety of a program which, if written in Javascript, would have lacked such safety completely (or eventually would end up throwing an exception from runtime type check).

Below is an example of such type constrained function:

hasChildNodes :: CNode this => this -> CPS c Bool

which corresponds to the hasChildNodes function defined within the Node interface. Any DOM object which is a Node can be passed to this function (by reference) as the this argument.

Attributes vs. getters and setters

Within interfaces, DOM specification defines attributes and methods. Attributes are either read-only (such as nodeName of the Node interface) or read-write (such as nodeValue of the same interface). In Haskell bindings, getter (for read-only attributes), and both getter and setter (for read-write attributes) functions are defined in straightforward manner:

get'nodeName :: CNode this => this -> CPS c String
set'nodeValue :: CNode zz => String -> zz -> CPS c zz
get'nodeValue :: CNode this => this -> CPS c String

Getters always take the object containing an attribute as the first argument, this, and it is always constrained to the type class corresponding to the DOM interface. Setters always take the value to be set as the first argument, and the object containing the attribute as the second argument. Setters always return reference to the same object where an attribute was set. The latter property allows to concatenate multiple setters in Continuation-passing style, such as:

........$ \he ->
  (set'id "myid")
  (set'lang "en")
  (set'title "Hello")

This whole construction will pass the same object (he) to the continuation, but continuation will deal with updated object.

The setters in the example above are defined in the DOM.Level2.HTMLElement module.

Methods vs. functions

Interface methods are translated to Haskell functions whose type signatures have proper type coetraints. Thus, the getElementById function defined in the Document interface as

Element getElementById(in DOMString elementId);

translates to Haskell function:

getElementById :: (CDocument this, CElement zz) => this -> String -> CPS c zz

as follows from its type, getElementById does not return a value of concrete type, but rather a type-constrained value. Values of types corresponding to DOM interfaces, translate to type-constrained rather than concrete values. This sometimes makes it necessary to supply explicit type signatures unless a function receiving the returned constrained value has a type signature that brings a constrained type down to a concrete type.

Maker functions

The IDL conversion utility domconv auto-creates convenient functions that serve as constructors of DOM objects corresponding to HTML tags. An example of such maker function is:

mkDiv :: CHTMLDocument a => a -> CPS c THTMLDivElement

which creates a DOM node tagged with < DIV >. Such maker functions are defined for most of HTML elements. Maker functions always return values of concrete type.

Threads

Threads emulation

Execution of Javascript in Web browser is always single-threaded. It is however possible and useful to emulate multi-threaded execution with window.setTimeout method.

Javascript threads available to Haskell programs have must of "real" threads features stripped off: there are no thread identifiers, threads must always explicitly yield execution, time-wise threads scheduling is very loose, etc.

Please refer to the Control.Concurrent.JSThreads module documentation. A working threads example (including inter-thread message passing, see below) can be found here.

CPS and threads

Continuation Passing Style allows for very efficient implementation of threads: in fact, switching context between threads is merely saving a continuation of currently executing thread in some static memory object, and evaluating continuation of (resuming) an thread that was similarly suspended earlier.

Message passing between threads

Threads may pass messages to each other, using Message Boxes. Sending messages is asynchronous (although rescheduling of threads execution occurs to resume the receiving thread). Sending a message may fail if there is a message in the Message Box (no message buffering). Receiving messages is always a blocking operation. Receiving a message may fail if there is already a thread waiting on the Message Box. Therefore more than one thread may send messages to the same Message Box (but sending all messages is not guaranteed unless result of sending is checked), but more than one thread may not receive messages from the same Message Box.

Events handling

From Javascript standpoint, attaching an event handler to a HTML element is simply setting appropriate on- attribute with reference to appropriate function to which event information will be passed. The low-level events API available to Haskell programs provides means for a thread to wait on a HTML element for a certain event to occur. Refer to the CDOM.Level2.Events module for more information.

XML HTTP

XML HTTP API is available to Haskell programs running in Web browser. Refer to the Network.XMLHTTP module for more information.

JSON

Although not part of the native browser API, JSON is a useful way to access properties of Javascript objects. Besides, CouchDB on which the Yhc Web Service is based, uses JSON as encoding format for queries and responses.

The JSON API provided to Haskell programs is based on the opaque type JsonNode. Operations over JSON nodes are monadic as they may fail. For example, the getValueByName function fails if the JSON node queried does not have a value with given name. Monadic interface allows to write code that retrieves values from JSON node in do notation and compose operations using >>= (monadic bind).

Thus, the following code:

uri = fromMaybe "----" $ do
  buri <- splitURI loc
  prot <- getValueByName "protocol" buri >>= getString
  auth <- getValueByName "authority" buri >>= getString
  anchor <- getValueByName "anchor" buri >>= getString
  let uri = prot ++ "://" ++ auth ++ "/" ++ anchor
  return uri

retrieves parts of an URI (see below) represented as a JSON node, and composes a new URI out of them. If the URI JSON node does not contain any of the values requested, the whole monadic sequence fails, and the fall-back value "----" will be returned.

Refer to the Data.JsonNode module for more information.

URI

Haskell programs running in Web browser may operate on Uniform Resource Identifiers (URI) by transforming URI strings into JSON nodes. The function for such conversion is splitURI defined in the Network.XMLHTTP module. It also has monadic interface (see example in the JSON section). The function itself is a wrapper for the URI parser based on Steven Levithan's parseUri Javascript function.

Haskell web toolkit