Personal tools

Haskell in web browser/Basics

From HaskellWiki

Jump to: navigation, search

Contents

1 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 HaskellDirect, 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.

1.1 DOM interfaces vs. Haskell type classes

Web Consortium'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.

1.2 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.

1.3 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.

1.4 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.

2 Threads

2.1 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.

2.2 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) a thread that was similarly suspended earlier.

2.3 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, and appropriate action taken if sendMsg fails), but more than one thread may not receive messages from the same Message Box.

2.4 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.

3 XML HTTP

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

4 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.

5 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.