Difference between revisions of "Yhc/Javascript/Programmers guide"

From HaskellWiki
Jump to navigation Jump to search
m (Typo (inport))
(The first Echo example is now on separate page)
Line 1: Line 1:
 
==Up from the ground==
 
==Up from the ground==
This part of the Programmers Guide is a collection of notes taken during the development of the first Haskell-in-Browser demo, a program that accepts users' input using a HTML input element, and repeats whatever the user typed upon pressing <code>Enter</code>. Additionally, Roman numeral conversion will occur if user's input is a decimal or a Roman numeral that can be converted. A timer is provided to measure code performance.
+
This part of the Programmers Guide is a collection of notes taken during the development of the first Haskell-in-Browser demo, a program that accepts users' input using a HTML input element, and repeats whatever the user typed upon pressing <code>Enter</code>. Additionally, Roman numeral conversion will occur if user's input is a decimal or a Roman numeral that can be converted. A timer is provided to measure code performance. [http://www.haskell.org/haskellwiki/Yhc/Javascript/Programmers_guide/Up_from_the_ground More...]
 
At the moment this demo program is being written, there is no programming paradigm decided upon, no GUI framework for Haskell to use in web browser, no supporting libraries, nothing. Only the browser itself plus a small runtime support library. Just like programming in machine codes on "bare iron": that's how first programs were created.
 
 
This Programmers Guide shows how Haskell can be used to interact with web browser at the lowest possible level. Before one starts developing a programming paradigm, or a GUI framework, this area of low-level programming needs to be explored.
 
 
===Programming for a web browser===
 
 
A Haskell program converted into Javascript and running in a web browser faces environment different from "traditional": there are no input/output devices and operations as we are used to them, no file system, and no sequential flow of execution. Instead, there is the DOM tree of objects; some of them perform interaction with user input and provide visual output, and program execution is event-driven.
 
 
This Programmers Guide, using the demo Echo program as a basis, will describe programming approaches and paradigms suitable for such environment.
 
 
===The [[Yhc/Javascript/Programmers_guide/Echo_demo_source|Echo]] demo program===
 
 
The Echo demo program demonstrates primitive interaction with user input, dynamic modification of the DOM structure to show output, and integration with third-party Haskell code (Roman conversion module). It also demonstrates how Javascript exceptions can be handled, and how to measure time intervals when running in a web browser. The link in the section header points to a syntax-colored source of the demo program.
 
 
===The <code>main</code> function===
 
The demo program discussed in this Programmers Guide has a <code>main</code> function which is called when the page is loaded into a browser. It is important to mention that in general, such a program may have more than one "entry point", and none of them called <code>main</code>. For example, a program consisting of event handlers only, with handlers being attached statically to page elements using HTML.
 
 
It is necessary to remember that all "entry points" must be specified on the converter's command line as reachability roots.
 
 
Type signature of the <code>main</code> function depends only of the framework used. This demo program uses simple monadic framework, therefore the <code>main</code> function returns a monadic value. It may or may not have arguments, again, this is dependent of conventions used when building a web page to place the program on.
 
 
In this demo program, the only purpose of the <code>main</code> function is to create the initial page layout and define an event handler for the input element. All interaction with user is performed by the event handler.
 
 
===A simple monad===
 
One of possible ways to guarantee proper sequence of actions is to define a monad, and perform all actions that require execution ordering within. Here is an example of such monad:
 
 
<haskell>
 
data JS a = JS a
 
 
instance Monad JS where
 
(JS a) >>= fn = fn a
 
(JS a) >> fn = fn
 
return a = a `seq` (JS a)
 
</haskell>
 
 
This monad is sufficient to guarantee proper order of execution of Javascript code. Note that all of its operations force evaluation of their arguments. That is, the RHS expression of <code>bind</code> will not start executing until the LHS expression is completely evaluated. The same applies to <code>return</code>: control will not be passed furter until the returned expression is completely evaluated.
 
 
If this monadic framework is used, the <code>main</code> function has return type <hask>JS ()</hask>.
 
 
===Calling Javascript from Haskell: <code>unsafeJS</code>===
 
The <code>unsafeJS</code> function is not a function per se: it is rather a macro, or a compilation directive. Its purpose is to provide a Haskell-accessible wrapper with proper type signature for an arbitrary Javascript code which obeys certain coding rules.
 
 
The function has a type signature:
 
 
<code>
 
foreign import primitive unsafeJS :: String -> a
 
</code>
 
 
Which means that it takes a string. Type of the return value does not matter: the function itself is never executed. Its applications are detected by '''ycr2js''' at the time of Javascript generation.
 
 
The <code>unsafeJS</code> function should be called with a string literal. Neither explicitly coded (with <code>(:)</code>) list of characters nor concatenation of two or more strings will work. The converter will report an error in this situation.
 
 
A valid example of using unsafeJS is shown below:
 
 
<code>
 
 
global_YHC'_Primitive'_primIntSignum :: Int -> Int
 
 
global_YHC'_Primitive'_primIntSignum a = unsafeJS
 
"var ea = exprEval(a); if (ea>0) return 1; else if (ea<0) return -1; else return 0;"
 
</code>
 
 
This is a Javascript overlay (in the sense that it overlays the default Prelude definition of the <code>signum</code> function) of a function that returns sign of an <code>Int</code> value.
 
 
The string literal <code>unsafeJS</code> is applied to is the Javascript code to be wrapped.
 
 
Below is the Javascript representation of this function found in the <code>Echo</code> page source.
 
 
<code>
 
strIdx["F_hy"] = "YHC.Primitive.primIntSignum";
 
 
...
 
 
var F_hy=new HSFun("F_hy", 1, function(a){
 
var ea = exprEval(a); if (ea>0) return 1; else if (ea<0) return -1; else return 0;});
 
</code>
 
 
Here are the rules that govern the usage of <code>unsafeJS</code>:
 
 
* The <code>unsafeJS</code> function is contained in the <code>UnsafeJS</code> module and should be imported from there
 
* Its argument must be a string literal, and nothing else
 
* Its argument should be written entirely on a single line
 
* Formal parameter names visible to Javascript are <code>a</code>, <code>b</code>, <code>c</code>, etc. that is single lowercase letters
 
* Number of formal parameters should match the Haskell type signature
 
* It is recommended to name the function's formal parameters in Haskell declaration in the same way they are visible to Javascript, i. e. <code>a</code>, <code>b</code>, <code>c</code>, etc.
 
* Haskell values are passed to Javascript functions unevaluated: use <code>exprEval</code> to evaluate
 
* Javascript code passed to <code>unsafeJS</code> should not contain outermost Javascript function declaration and curly braces: '''ycr2js''' will provide those
 
* Javascript code is not limited in what it may contain<sup>*</sup>; common sense must be observed not to code in unsafe way when not really necessary: for instance it is possible to change fields of a Haskell data object from Javascript, but it is strongly discouraged: create a modified copy of the object and leave the original unchanged, like a Haskell program would do.
 
* <b>Javascript code must return a value</b>
 
 
So, in the <code>signum</code> function above, first thing done is evaluation of the argument <code>a</code>. Because of the proper Haskell type signature provided, it is safe to expect a numeric value as result of the evaluation.
 
 
Next, usual comparisons with zero are performed, to determine the sign of the argument. Results are returned.
 
 
----
 
<sup>*</sup> For instance, inner function declaration may be used, as in this more complex example below (implementation of integer division via modulus):
 
 
<code>
 
global_YHC'_Primitive'_primIntegerQuot :: Integer -> Integer -> Integer
 
 
global_YHC'_Primitive'_primIntegerQuot a b = unsafeJS
 
"(function(x,y){return (x - (x % y))/y;})(exprEval(a),exprEval(b));"
 
</code>
 
 
The purpose of having an inner function declaration is to reuse evaluated arguments <code>a</code> and <code>b</code>: even though every expression is evaluated only once, extra call to <code>exprEval</code> may be avoided this way.
 
 
===Calling Haskell from Javascript===
 
To call a Haskell function from within Javascript code, one has to construct application of this function to argument(s), and evaluate the application (may be done later, or not done at all in Javascript code).
 
 
Every Haskell expression visible to Javascript is represented by an object of type <code>HSFun</code> or <code>HSDly</code>. See [[Yhc/Javascript/Inner_workings#Structure_of_Javascript_Objects|Structure of Javascript Objects]] for more details about these objects' methods and properties.
 
 
Application of a function to its arguments is constructed by calling the <code>_ap</code> method of an object representing a function. The <code>_ap</code> method takes an array of values as its only argument.
 
 
So, if <code>objf</code> is a Javascript object representing a Haskell function, and <code>p1...pn</code> are the arguments, application is constructed like this:
 
 
<code>
 
objf._ap([p1,p2,...pn])
 
</code>
 
 
Construction of an application does not force the function to evaluate its code and return a value. In order to do this, a function from the Runtime support library should be called:
 
 
<code>
 
var v = exprEval(objf._ap([p1,p2,...pn]));
 
</code>
 
 
Then <code>v</code> will be assigned a value returned by the function referred to by <code>objf</code>.
 
 
Value for <code>objf</code> mey be obtained either from the Haskell code which calls a Javascript function or from the [[Yhc/Javascript/Inner_workings#Internal_Indices|index]] of function names.
 
 
Names of functions that were used in Haskell source code are not preserved in the generated Javascript code. They are replaced with meaningless sequences of letters and numbers. For instance, <code>Echo.main</code> function is renamed to <code>F_cj</code>. It cannot be known in advance, what will function names look like after renaming.
 
 
To be able to locate a renamed function by its name, the global object named <code>funIdx</code> exists. It is essentially a hash table mapping function names used in Haskell source to their names used in Javascript code. This hashtable contains only names of functions specified in the converter's command line as reachability roots.
 
 
To obtain name of a function after renaming, the following expression needs to be constructed: <code>funIdx['Echo.main']</code> (quotes may be double as well) for the <code>Echo.main</code> function. Function names should be qualified.
 
 
Result of function name lookup points to an object that may be used to call the function as it was described above.
 
 
An example of function name index usage is specifying the Javascript expression to be executed when web browser loads the page:
 
 
<pre>
 
<body onload="exprEval(funIdx['Echo.main'])">
 
</pre>
 
 
===Passing primitive values===
 
* Numeric values are passed from Haskell to Javascript and from Javascript to Haskell without any special wrappers.
 
 
* Boolean values are passed from Javascript to Haskell without wrappers, but passing from Haskell to Javascript requires evaluation and extracting value of the <code>_t</code> property.
 
 
That is, if Javascript code expects a Boolean value as its argument <code>a</code>, the following expression <code>exprEval(a)._t</code> extracts the primitive value of <code>true</code> or <code>false</code>.
 
 
===Passing strings===
 
Passing strings in both directions does not need any wrapping. When passed from Javascript to Haskell, strings are lazily converted into lists of characters. When passing from Haskell to Javascript, method <code>toString</code> overloaded in <code>HSCons</code> forces evaluation of every expression the list is built of, and finally, a string that Javascript can use is created.
 
 
===Passing arrays===
 
Javascript arrays when passed to Haskell code are lazily converted to lists of values. To convert a Haskell list reference to a Javascript array, one has to call the <code>_toArray</code> method on that reference.
 
 
An example of the latter can be seen in the <code>runMethod</code> function implementation. This function receives arguments of the method to be run as an array.
 
 
<hask>
 
runMethod :: JSObject -> String -> a -> JS JSObject
 
runMethod a b c = unsafeJS
 
"var a1=exprEval(a); return new HSData(conIdx['Echo.JS'],[cbrApply(a1[exprEval(b).toString()],a1,c._toArray())]);"
 
</hask>
 
 
Note that the <code>b</code> argument is evaluated, and <code>toString</code> is called upon it, and <code>c._toArray</code> make sure that the <code>c</code> argument will be visible to Javascript as an Array.
 
<i>Note that this might be a better idea to call </i><code>exprEval</code><i> on </i><code>c</code><i> too</i>.
 
 
===Passing objects===
 
Javascript objects may be passed to Haskell by reference. For this purpose, an opaque type may be defined:
 
 
<hask>
 
newtype JSObject = JSObject ()
 
</hask>
 
 
No values of this type will be directly created by Haskell code. But when it is necessary to pass to, or return from Javascript a reference to an object whose structure is not accessed by Haskell code, this is where it helps.
 
 
For example, the function to get the document interface of the web page currently loaded in the browser, one may define a function:
 
 
<hask>
 
getDocument :: JS JSObject
 
getDocument = unsafeJS "return new HSData(conIdx['Echo.JS'],[document]);"
 
</hask>
 
 
In this case, it is only needed to get a reference to the <code>document</code> object itself; nothing about its internal structure is known. Further in this Guide, it will be shown how individual properties of Javascript objects may be accessedm and methods run.
 
 
Another aspect of passing objects is ability to access internal structure and to create Haskell objects in Javascript code. Haskell data objects are visible to Javascript code as objects of type <code>HSData</code>. See [[Yhc/Javascript/Inner_workings#Structure_of_Javascript_Objects|Structure of Javascript Objects]] for more details about this object's methods and properties.
 
 
In general, constructor tag index is accessible as the <code>_t</code> property, and data fields as the <code>_f</code> property which is an Array. Order of fields in this array is same as it was declared in Haskell code.
 
 
The most widespread usage of the <code>_t</code> property of <code>HSData</code> objects is in Haskell <hask>case</hask> statements translated to Javascript when pattern matching is done by constructor tag.
 
 
In the example above, a monadic value of <code>document</code> object reference is constructed by calling the <code>HSData</code> constructor function with <code>Echo.JS</code> tag index (obtained via the <code>conIdx</code> lookup object), and a singleton Array consisting of the reference to the <code>document</code>.
 
 
If a Haskell data object belongs to a type declared as a "regular" data type, i. e. not with a record-style declaration, the only way to access individual fields is to use indexed (0-based) access to the <code>_f</code> property of a <code>HSData</code> object. For objects whose type was declared in the record style, it is potentially possible to use selector functions for individual fields, but the following needs to be remembered:
 
 
* It is necessary to obtain a function index (via <code>funIdx</code> lookup) for each selector function, therefore qualified name of the function must be specified as a root of reachability on the '''ycr2js''' command line
 
* It is therefore necessary to know exactly which module contains declaration for a particular data type to get a qualified name for the selector function.
 
 
This makes Javascript access to data fields of Haskell data objects something to avoid without extreme need. Indeed, it needs to be borne in mind that on the Javascript side, primitive values are better to process, and manipulation by Haskell-specific objects is better to perform on the Haskell side.
 
 
===Type coercions===
 
This section of the Guide discusses methods to coerce values contained in Javascript objects returned from Javascript code (<code>JSObject</code>) to values that Haskell understands. Internal representation of Javascript values does not contain explicit type information: based on the context where values are used, they may be treated differently, e. g. a number may be treated as a string (containing numeric value converted to a string). Haskell programs need type of every value to be specified at compile time.
 
 
Usually, to coerce a Javascript value to certain type some constructor or method must be called upon that Javascript value. After that, the value may be returned as if it had the required Haskell type. If the value cannot be coerced as required, Javascript code may throw an exception, or return an undefined value, or behave in some other way.
 
 
For example, a Javascript object that is expected to contain a numeric value, may be coerced from an abstract type <code>JSObject</code> to <code>Int</code>:
 
 
<hask>
 
asInt :: JSObject -> JS Int
 
 
asInt a = unsafeJS
 
"return new HSData(conIdx['Echo.JS'],[new Number(exprEval(a))]);"
 
</hask>
 
 
That is, there will be an attempt to create a numeric value out of the object. If unsuccessful, certain reaction will follow (see the Javascript documentation for details), but if successful, it is a numeric value that is returned.
 
 
Similarly, a stringified representation of a Javascript object may be obtained:
 
 
<hask>
 
asString :: JSObject -> JS String
 
 
asString a = unsafeJS
 
"return new HSData(conIdx['Echo.JS'],[new String(exprEval(a))]);"
 
</hask>
 
 
In the latter example, the <code>String</code> constructor function obtains string representation of an object (that is, whatever its <code>toString</code> overloaded method returns).
 
 
Thus, if a key code from an event object is expected to be an integer number, one may write:
 
 
<hask>
 
kcs <- getProperty e "keyCode" >>= asInt
 
</hask>
 
 
and if a value entered into an input element is to be treated as a string, one writes:
 
 
<hask>
 
v <- getProperty o "value" >>= asString
 
</hask>
 
 
Here, <code>e</code> refers to an <code>event</code> Javascript object, and <code>o</code> refers to a DOM object associated with the HTML &lt;input&gt; element.
 
 
===Getting and setting properties===
 
Javascript objects' properties may be accessed both using the dot-notation (<code>object.property</code>) and associative array notation (<code>object["property"]</code>) both for setting and getting properties' values.
 
 
The second method is more convenient because string passed to <hask>unsafeJS</hask> may only be a literal and cannot be parameterized.
 
 
A pair of functions below (names self-explanatory) gives an example how Javascript objects' properties may be accessed from Haskell.
 
 
<hask>
 
getProperty :: JSObject -> String -> JS JSObject
 
 
getProperty a b = unsafeJS
 
"return new HSData(conIdx['Echo.JS'],[exprEval(a)[exprEval(b).toString()]]);"
 
 
setProperty :: JSObject -> String -> a -> JS ()
 
 
setProperty a b c = unsafeJS
 
"exprEval(a)[exprEval(b).toString()]=exprEval(c);return new HSData(conIdx['Echo.JS'],[]);"
 
</hask>
 
 
These examples also feature wrapping of returning values into a monadic type.
 
 
Note that the <code>b</code> argument containing property name is evaluated before getting its string value. This argument may pass a concatenation of several strings, or an expression computing a string therefore its evaluation needs to be forced.
 
 
Also note that type of property value returned by <hask>getProperty</hask> is <hask>JS JSObject</hask>, and value a property is set to may be of any type. This is a consequence of Javascript's untypedness: no assumption can be made what is the actual type of property value. Use [[#Type_Coercions|coercion]] functions to cast property values to types needed in each case.
 
 
===Running methods===
 
Similarly, methods of Javascript objects may be run from a Haskell program. The following function can be used to provide this functionality:
 
 
<hask>
 
runMethod :: JSObject -> String -> a -> JS JSObject
 
 
runMethod a b c = unsafeJS
 
"var a1=exprEval(a); return new HSData(conIdx['Echo.JS'],[cbrApply(a1[exprEval(b).toString()],a1,exprEval(c)._toArray())]);"
 
</hask>
 
 
This function constructs a Haskell data object of type <hask>JS JSObject</hask> containing value returned from the method. Note that method's arguments are passed in a list.
 
 
This function calls the <code>cbrApply</code> function to apply the method to its arguments. This function provided by the Javascript Runtime Support library congains workaround for MSIE whose DOM methods do not have the <code>apply</code> method.
 
 
===Handling exceptions===
 
In certain situations, Javascript code may throw an exception. One possible usage of exceptions is implementation of the <hask>Prelude.error</hask> function which just throws an exception containing among other things the string given to <hask>Prelude.error</hask> as an argument.
 
 
To handle an exception (and thus make result of calling <hask>Prelude.error</hask> non-fatal for the program) a handler should be installed which is achieved by using try-catch blocks.
 
 
The function code below shows an example how to handle exceptions in a monadic framework. Its type signature is similar to one of the <hask>Prelude.catch</hask>:
 
 
<hask>
 
catchJS :: JS a -> (JSObject -> JS a) -> JS a
 
catchJS a b = unsafeJS
 
"try {return exprEval(a);} catch(_e) {return exprEval(b)._ap([_e]);}"
 
</hask>
 
 
The function installs an exception handler, evaluates the expression given as the first argument, and if exception is caught, passes it as a parameter to the expression given as the second argument.
 
   
 
==DOM framework==
 
==DOM framework==
   
In this section of the Yhc/Javascript Programmers Guide, the implementation of [http://www.w3c.org/DOM Document Object Model] in Haskell is described. Continuation Passing Style usage is discussed. The section provides details on conversion of DOM specifications from Interface Definition Language to Haskell, and related issues and features. Finally, examples of Haskell programming with DOM are provided.
+
In this section of the Yhc/Javascript Programmers Guide, the implementation of [http://www.w3c.org/DOM Document Object Model] in Haskell is described. Continuation Passing Style usage is discussed. The section provides details on conversion of DOM specifications from Interface Definition Language to Haskell, and related issues and features. Finally, examples of Haskell programming with DOM are provided.
 
 
===Continuation passing style===
 
===Continuation passing style===
   
Line 502: Line 206:
   
 
====Known omissions====
 
====Known omissions====
* Exception information (<code>raises...</code>) is completely ignored by the converter. If an exception in Javascript code occurs, it should be treated as described above, in the [[#Unsafe_Interfaces_with_CPS|Unsafe Interfaces with CPS]] section.
+
* Exception information (<code>raises...</code>) is completely ignored by the converter. If an exception in Javascript code occurs, it should be treated as described above, in the [[#Unsafe_interfaces_with_CPS|Unsafe interfaces with CPS]] section.
   
 
* The converter makes no distinction between <code>in</code> and <code>out</code> arguments.
 
* The converter makes no distinction between <code>in</code> and <code>out</code> arguments.

Revision as of 15:24, 18 June 2007

Up from the ground

This part of the Programmers Guide is a collection of notes taken during the development of the first Haskell-in-Browser demo, a program that accepts users' input using a HTML input element, and repeats whatever the user typed upon pressing Enter. Additionally, Roman numeral conversion will occur if user's input is a decimal or a Roman numeral that can be converted. A timer is provided to measure code performance. More...

DOM framework

In this section of the Yhc/Javascript Programmers Guide, the implementation of Document Object Model in Haskell is described. Continuation Passing Style usage is discussed. The section provides details on conversion of DOM specifications from Interface Definition Language to Haskell, and related issues and features. Finally, examples of Haskell programming with DOM are provided.

Continuation passing style

Rationale

Unlike the previous Echo example, the DOM framework uses CPS rather than monads to provide proper sequence of Haskell expressions evaluation. The choice of CPS is dictated by the internal structure of Fudget kernels which use CPS. An original Fudget (built on top of the X11 protocol and related I/O) sends a message to Fudlogue each time an input/output action is needed (even one not involving waiting for any asyncronous input, such as opening a window). With DOM interface implemented in CPS style, all synchronous operations (such as creating a DOM node, and basically all operations not involving event handling) can be performed without such message exchange, which significantly reduces execution overhead.

Wrapper functions

A function conforming the Continuation Passing Style always has as its last argument, continuation, which will take the result of this function's application to its other arguments, as an argument. Any non-CPS expression may be converted into a CPS one by applying a wrapper which transforms the expression into a function with one argument:

toCPS x = \k -> k x

where x is an expression to convert. The expression will be passed to the continuation unevaluated.

A variant of this wrapper:

toCPE x = \k -> x `seq` (k x)

forces evaluation of the expression before passing it to the continuation.

Consider obtaining a current date and time from the browser. Browser provides a Javascript function new Date().getTime() for this purpose. So, at the first look the following might be enough:

getTimeStamp' a = unsafeJS "return new Date().getTime();"

The dummy parameter a is necessary to prevent creation of a CAF, that is, every time the function is called with any value of this parameter, evaluation will take place.

To convert this expression, e. g. getTimeStamp' 0 in CPS, it needs to be given a parameter representing continuation which will use its result, that is, the current time. This may be written as:

getTimeStamp k = k `seq` (getTimeStamp' 0)

where k is a continuation which will be given the current time. The seq combinator ensures that the continuation will get an evaluated expression.

So, in a larger example:

main = getTimeStamp $ \t1 ->
       foo $ \_ ->
       bar $ \_ ->
       getTimeStamp $ \t2 ->
       putLine ("Time interval: " ++ show (t2 - t1) ++ " ms") $ id

two time stamps will be obtained, before and after the two computations foo and bar (whose results are not of interest) are performed. The result will be output with some imaginary function putLine.

The id call after putLine is necessary to "close" the chain of continuations: the value that putLine returns, becomes return value of main. If however it is necessary to return something else, say, the length of the time interval measured, the last row might look like:

       putLine ("Time interval: " ++ show (t2 - t1) ++ " ms") $ \_ ->
       (t2 - t1)

In general, the example above gives some idea how Haskell programs using DOM in CPS style look like.

The CPS module should be imported by any Haskell module using the Continuation Passing Style constructs and the DOM framework. The CPS type itself is defined as:

type CPS c a = (a -> c) -> c

So, if a function has the return type CPS x y, this means that its continuation would accept a value of type y and return a value of type x

Unsafe interfaces with CPS

Usage of unsafeJS has not changed from one described above. This is still a pseudo-function accepting a string literal with Javascript code as an argument. The Javascript code supplied will be wrapped into a Haskell-callable function.

To access properties of Javascript objects, the following CPS-aware functions are provided:

unsafeGetProperty :: String -> b -> CPS d c

unsafeSetProperty :: String -> b -> c -> CPS d c

The first function accepts Javascript property name as its first argument, and a reference to a Javascript object as the second. It passes the value of the property retrieved (in type-agnostic manner) to its continuation.

The second function accepts Javascript property name as its first argument, the value to set the property to as the second argument, and a reference to a Javascript object as the third. The continuation gets the reference to the Javascript object with updated property (that is, the update occurs in-place).

Both functions evaluate their arguments.

To unsafely convert Javascript values to Haskell Num and String values, the following two functions are provided:

unsafeToNum :: (Num b) => a -> CPS c b

unsafeToString :: a -> CPS c String

The first function calls the Number Javascript constructor on the argument's value, the second calls the String Javascript constructor on its argument. Both functions evaluate their argument first.

To catch exceptions, the following function is provided:

catchJS :: a -> (b -> a) -> a

This function takes its first argument and evaluates it. If an error occurs (Javascript exception is thrown), it is passed as an argument to the function specified as catchJS's second argument. The function handling an exception should either return a value of the same type as the failed expression does, or to (re)throw an exception. The error function from the Standard Prelude is implemented using the Javascript throw statement.

Programming examples

The EchoCPS Wiki page contains an example of a working Echo demo program written using the DOM interfaces.

DOM and the Web Consortium

The Document Object Model (DOM) is the base interface to access the content and structure of documents in a web browser. The Web Consortium has a page dedicated to DOM.

This Programmers Guide is based on the Document Object Model (DOM) Level 1 Specification (Second Edition) provided by the Web Consortium. This version of DOM, although not very new, can serve as the greatest common denominator for many types of web browsers available these days.

DOM and Interface Definition Language (IDL)

General information

The Web Consortium uses a subset of the Interface Definition Language proposed by the Object Management Group (OMG IDL) to describe the abstract interface to the Document Object Model, so it may be implemented in various programming languages. These definitions cover basic operations to create and delete document nodes, manipulate their attributes and contents, and insert/remove nodes within the document loaded into the browser.

Conversion to Haskell

In accordance with the Web Consortium Copyright Notice, IDL files provided by the Web Consortium may be freely redistributed by anybody. So, copy of these files is included with the Yhc Javascript Backend. A modified version of the HaskellDirect (trimmed down to only OMG IDL code symtax recognition, and with different model of Haskell code generation) is also included. This HaskellDirect-based utility runs automatically when the Javascript Backend is being installed, so the installation includes Haskell code autogenerated from the IDL files. Developers who define new interfaces on the browser side to be used with the Javascript Backend are encouraged to write their own IDL files, and use the same utility to produce Haskell interface code.

Technical details of IDL to Haskell conversion

This section gives general details of correspondence between IDL definitions and generated Haskell code. Deeper details related to programming will be discussed in next sections.

Consider this IDL definition (from the DOM section of the definitions):

 interface Attr : Node {
   readonly attribute DOMString        name;
   readonly attribute boolean          specified;
   // Modified in DOM Level 1:
            attribute DOMString        value;
                                       // raises(DOMException) on setting
 };

One interface definition in IDL results in creation of one Haskell module with the same name as the interface has. Module name will be prefixed with DOM.Level1, that is, the #pragma prefix "w3c.org" in the beginning of the file is ignored.

The Haskell translation is:

module DOM.Level1.Attr
       (get'name, get'specified, set'value, get'value) where
import DOM.Level1.Dom
import CPS
import UnsafeJS
import DOM.Level1.Document (createElement)
 
get'name :: (CAttr this) => this -> CPS c String
get'name = unsafeGetProperty "name"
 
get'specified :: (CAttr this) => this -> CPS c Bool
get'specified = unsafeGetProperty "specified"
 
set'value :: (CAttr zz) => String -> zz -> CPS c zz
set'value = unsafeSetProperty "value"
 
get'value :: (CAttr this) => this -> CPS c String
get'value = unsafeGetProperty "value"

Additionally, in the DOM.Level1.Dom module, the following is defined (comments added):

data TAttr = TAttr               -- phantom type for the interface
class (CNode a) => CAttr a       -- class reflecting inheritance from Node
instance CAttr TAttr             -- interfaces of Attr are implemented
instance CNode TAttr             -- interfaces of Node are implemented

Attributes that have readonly in their definitions only have getter methods (e. g. get'name). The rest of attributes also have setter methods (e. g. get'value, set'value). Getter and setter names are produced by prefixing IDL attribute name with get' and set' respectively.

The Attr interface does not have methods, only attributes. The following interface illustrates how methods are represented:

 interface NodeList {
   Node               item(in unsigned long index);
   readonly attribute unsigned long    length;
 };

module DOM.Level1.NodeList (item, get'length) where
import DOM.Level1.Dom
import CPS
import UnsafeJS
import DOM.Level1.Document (createElement)
 
item :: (CNodeList this, CNode zz) => this -> Int -> CPS c zz
item a b = toCPE (item' a b)
item' a b = unsafeJS "return((exprEval(a)).item(exprEval(b)));"
 
get'length :: (CNodeList this) => this -> CPS c Int
get'length = unsafeGetProperty "length"

There also are related lines of code in the DOM.Level1.Dom module (not shown as they are logically identical to already reviewed).

The item method, as implemented in Haskell, takes the reference to the DOM element (NodeList) as the first argument, this. The second argument is the index of a node in the NodeList, corresponding to the in unsigned long index in the IDL definition. The last argument is the continuation. Type constraints (CNodeList this, CNode zz) => state that the method operates on instances of the CNodeList class, and values passed to the continuation are instances of the CNode class.

Body of the method contains a type-aware wrapper over the unsafe code calling appropriate item method on the Javascript object implementing the NodeList interface.

Known omissions

  • Exception information (raises...) is completely ignored by the converter. If an exception in Javascript code occurs, it should be treated as described above, in the Unsafe interfaces with CPS section.
  • The converter makes no distinction between in and out arguments.
  • The converter does not tolerate multiple methods with the same name, but different number of arguments, within a single interface. It is however possible to have methods with the same name (regardless of number of arguments) in different interfaces. The focus and blur methods serve as a good example: they appear in at least two HTML elements: <input> and <textarea>. Developers are recommended to use import qualified statement for importing modules with conflicting method names, and use qualified names to resolve ambiguities.

Haskell DOM vs. Javascript DOM

Haskell phantom types vs. Javascript object types

Haskell type classes reflect inheritance

The "defaulting" problem and DOM utility functions

Programming examples

Fudgets On web

(once we implement that)