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.