A Haskell FFI calling convention for Javascript

2010/10/29

Haskell’s Foreign Function Interface (FFI) predefined calling conventions do not match well with Javascript’s object oriented features. In particular selecting a field of an object using dot notation (like o.f ) and using an object as an array using brackets (like o[i] ) do not have a natural counterpart in Haskell or the default calling conventions supported by the FFI interface. So, here are some examples of how Javascript is accessed in UHC via its jscript calling convention:

data Document foreign import jscript "document" document :: IO Document foreign import jscript "%1.write()" documentWrite :: Document -> JSString -> IO () foreign import jscript alert :: JSString -> IO ()

document

"document"

write

"%1.write()"

write

()

%<nr>

<nr> >= 1

<nr>

alert

"<functionname>()"

<functionname>

From within a browser the document representation can be accessed via the global variable, the foreign entitytranslates to a reference to this variable. The type of the document is defined as an opaque type, it can thus only manipulated via Javascript. Writing a string to the document is done by invoking the methodon a document. The foreign entityspecifies that from all arguments the first one is used as the receiver of the method. The parenthesisspecify that a call has to be made, passing all arguments except those referred to explicitly by means of, whererefers to argument. If an entity is omitted as init defaults towhereis the name of the foreign function being defined.

documentWrite

String

JSString

String

Functiondoes not accept abut ainstead, defined to be the platform dependent representation of Strings, converted to and fromwith corresponding conversion functions.

type JSString = PackedString stringToJSString :: String -> JSString jsStringToString :: JSString -> String

stringToJSString

forces its argument to be fully evaluated and then converts it to a Javascript String.

document

IO

IO

There is choice whether to putin themonad or not, depending whether this global object itself will ever be assigned a new value or not. Not being a Javascript DOM wizard wrapping inseems to be the safest bet.

Given these functions a minimal Hello World web program thus is:

main = alert $ stringToJSString "Hi there!"

As this would pop up an alert box, an alternative Hi is the following program which writes to the document instead:

main = do d <- document documentWrite d $ stringToJSString "Hi there!"

Actually, the usual Hello would have worked as well because it is implemented as writing to the document:

main = putStr "Hi there!"

To show the usefulness of array like access as part of we do a bit of rudimentary DOM programming:

foreign import jscript "%1.getElementsByName()" documentGetElementsByName :: Document -> JSString -> IO (NodeList Node) data NodeList x foreign import jscript "%1.length" nodeListLength :: NodeList Node -> Int foreign import jscript "%1[%2]" nodeListItem :: NodeList Node -> Int -> IO Node data Node foreign import jscript "%1.innerHTML" elementInnerHTML :: Node -> JSString foreign import jscript "%1.tagName" elementTagName :: Node -> JSString

NodeList

is not an array, but behaves like an array: we can ask for its length and retrieve an element by index. It is not an array itself, so modelling it as such in Haskell would be incorrect. However, by allowing import entities to use Javascript array notation we circumvent this limitation and the Javascript array interface can still be used easily.

Finally, this minimal interface to DOM can be used to retrieve and print info about an element in an html document:

main = do d <- document nl <- documentGetElementsByName d (stringToJSString "myHeader") print (nodeListLength nl) n <- nodeListItem nl 0 print $ jsStringToString $ elementTagName n print $ jsStringToString $ elementInnerHTML n

Given the presence of

Head says hello!

"myHeader"

with the namein the document where the program is run, it will produce the following as part of the document:

1 "H1" "Head says hello!"