Haskell in web browser
- 1 Preface
- 2 Basics of programming for web browser
- 3 Haskell web toolkit
Basics of programming for web browser
It was necessary to convert these definitions to Haskell function declarations to make them available to Haskell programs for Web browser. The special utility,
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
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
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.
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.
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.
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.