This week I learnt of Haste, a Haskell to Javascript compiler intended to be used to build web applications. I have been trying to learn Haskell as a side project for a while and have previously looked into options for compiling it to Javascript.

I was very happy that after a little playing around I was able to get an incomplete Haskell chess implementation compiling to Javascript and playing itself in a browser.


Getting it running was just a matter of including the modules, generating some moves and writing the state to an element. However as it does some computation calculating moves it also blocked the page. This can be fixed by running the code in a web worker, I'd used web workers with ClojureScript last week to do the calculations for my Swipe Keyboard and figured I could probably use them for this too.

There does not appear to be any support for web workers in Haste, but it does support a foreign function interface which allows support to be added.

To support creating and using Worker objects I created three functions in Javascript. One to create a Worker object, one to bind an onmessage function and one to call the workers postMessage function. The A function is provided by Haste to call callbacks and provide arguments.

function make_worker(url) {
    return new Worker(url);
}

function on_message(worker, callback) {
    worker.onmessage = function (e) {
        A(callback, [[0, e.data], 0]);
    }
}

function send_message(worker, message) {
    worker.postMessage(message);
}

In Haskell I create a worker, send it a message, wait for messages from the worker and write them to an element on the page.

{-# LANGUAGE ForeignFunctionInterface #-}

module Main where
import Haste

newtype Worker = Worker JSAny

foreign import ccall make_worker :: JSString -> IO Worker
foreign import ccall on_message :: Worker -> JSFun (JSString -> IO ()) -> IO ()
foreign import ccall send_message :: Worker -> JSString -> IO ()

message :: Worker -> (JSString -> IO ()) -> IO ()
message worker f = on_message worker $ mkCallback f

handle_message :: Elem -> JSString -> IO ()
handle_message el s = setProp el "innerHTML" s' 
    where Just s' = fromJSString s

main = do
    Just el <- elemById "output"
    worker <- make_worker (toJSString "worker.js")
    message worker (\s -> handle_message el s) 
    send_message worker $ toJSString "Hello worker!"

Worker scripts run in an environment where an onmessage event can be bound and a postMessage function is provided. I created some more wrapper functions to use these in Haskell.

function on_message(callback) {
    self.onmessage = function (e) {
        A(callback, [[0, e.data], 0]);
    }
}

function send_message(message) {
    postMessage(message);
}

The Haskell web worker script sends a message once it has loaded and adds a prefix to every message it receives and sends the message back.

{-# LANGUAGE ForeignFunctionInterface #-}

module Main where
import Haste

foreign import ccall on_message :: JSFun (JSString -> IO ()) -> IO ()
foreign import ccall send_message :: JSString -> IO ()

message :: (JSString -> IO ()) -> IO ()
message f = on_message $ mkCallback f

response :: JSString ->  JSString 
response s = toJSString $ "Worker recieved, " ++ s'
    where Just s' = fromJSString s

main = do
    send_message $ toJSString "Worker: Hello"
    message (\x -> send_message (response x))

I didn't need all this for the Chess worker as it just posts updated game stages and doesn't receive any messages.

I still don't know Haskell well enough to really know what I'm doing, but hopefully Haste will encourage me to engage with more Haskell code.

I've pushed all the code for this post to Github.

Comments

Comment