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

From HaskellWiki
Jump to navigation Jump to search
(Passing objects 3)
(Coerce 1)
Line 199: Line 199:
   
 
===Type Coercions===
 
===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>
   
 
===Getting/Setting Properties===
 
===Getting/Setting Properties===

Revision as of 16:54, 22 November 2006

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.

Programming for 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 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 main function

The demo program discussed in this Programmers Guide has a main 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 such "entry points", and none of them called main. 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 main function depends only of the framework used. This demo program uses simple monadic framework, therefore the main 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 main 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:

data JS a = JS a

instance Monad JS where
  (JS a) >>= fn = fn a
  (JS a) >> fn = fn
  return a = a `seq` (JS a)

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 bind will not start executing until the LHS expression is completely evaluated. The same applies to return: control will not be passed furter until the returned expression is completely evaluated.

If this monadic framework is used, the main function has return type JS ().

Calling Javascript from Haskell: unsafeJS

The unsafeJS 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:

foreign import primitive unsafeJS :: String -> a

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 unsafeJS function should be called with a string literal. Neither explicitly coded (with (:)) 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:

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;"

This is a Javascript overlay (in the sense that it overlays the default Prelude definition of the signum function) of a function that returns sign of an Int value.

The string literal unsafeJS is applied to is the Javascript code to be wrapped.

Below is the Javascript representation of this function found in the Echo page source.

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;});

Here are the rules that govern the usage of unsafeJS:

  • The unsafeJS function is contained in the UnsafeJS 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 a, b, c, 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. a, b, c, etc.
  • Haskell values are passed to Javascript functions unevaluated: use exprEval to evaluate
  • Javascript code passed to unsafeJS should not contain outermost Javascript function declaration and curly braces: ycr2js will provide those
  • Javascript code is not limited in what it may contain*; 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.
  • Javascript code must return a value

So, in the signum function above, first thing done is evaluation of the argument a. 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.


* For instance, inner function declaration may be used, as in this more complex example below (implementation of integer division via modulus):

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));"

The purpose of having an inner function declaration is to reuse evaluated arguments a and b: even though every expression is evaluated only once, extra call to exprEval 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 HSFun or HSDly. See 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 _ap method of an object representing a function. The _ap method takes an array of values as its only argument.

So, if objf is a Javascript object representing a Haskell function, and p1...pn are the arguments, application is constructed like this:

objf._ap([p1,p2,...pn])

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:

var v = exprEval(objf._ap([p1,p2,...pn]));

Then v will be assigned a value returned by the function referred to by objf.

Value for objf mey be obtained either from the Haskell code which calls a Javascript function or from the 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, Echo.main function is renamed to F_cj. 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 funIdx 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: funIdx['Echo.main'] (quotes may be double as well) for the Echo.main 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:

 <body onload="exprEval(funIdx['Echo.main'])">

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 _t property.

That is, if Javascript code expects a Boolean value as its argument a, the following expression exprEval(a)._t extracts the primitive value of true or false.

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 toString overloaded in HSCons 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 _toArray method on that reference.

An example of the latter can be seen in the runMethod function implementation. This function receives arguments of the method to be run as an array.

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())]);"

Note that the b argument is evaluated, and toString is called upon it, and c._toArray make sure that the c argument will be visible to Javascript as an Array. Note that this might be a better idea to call exprEval on c too.

Passing Objects

Javascript objects may be passed to Haskell by reference. For this purpose, an opaque type may be defined:

newtype JSObject = JSObject ()

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:

getDocument :: JS JSObject getDocument = unsafeJS "return new HSData(conIdx['Echo.JS'],[document]);"

In this case, it is only needed to get a reference to the document object itself; nothing about its internal structure is known. Further in thsi 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 visibke to Javascript code as objects of type HSData. See Structure of Javascript Objects for more details about this object's methods and properties.

In general, constructor tag index is accessible as the _t property, and data fields as the _f 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 _t property of HSData objects is in Haskell case statements translated to Javascript when pattern matching is done by constructor tag.

In the example above, a monadic value of document object reference is constructed by calling the HSData constructor function with Echo.JS tag index (obtained via the conIdx lookup object), and a singleton Array consisting of the reference to the document.

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 _f property of a HSData 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 funIdx 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 (JSObject) 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 JSObject to Int:

asInt :: JSObject -> JS Int asInt a = unsafeJS "return new HSData(conIdx['Echo.JS'],[new Number(exprEval(a))]);"

Getting/Setting Properties

Running Methods

Handling Exceptions

DOM Framework

<once we have one...>