The first thing we need on our journey is a compiler that translates out haskell to javascript. On this page: The JavaScript Problem you can find a list of compilers that are able to do that. I will focus on UHC and haste. GHCJS also seems promising, and I might try it later.
So we want to write a little hello world program. The equivalent in javascript would be
document.write("Hello, World!");
which replaces the contents of the document with "Hello, World!". Let us get started.
UHC
The Utrecht Haskell Compiler has a backend allowing to compile haskell to javascript. This page links all the information about the JavaScript backend: UHC-JS
Installing UHC
First we have to install UHC. You can find the code for UHC here UHC GitHub. The build instructions can be found in the EHC sub directory.
But first some dependencies are needed. On ubuntu linux I install them with apt-get:
apt-get install ghc cabal-install build-essentials libtool uuagc
We also need to install a few haskell packages via cabal:
cabal update
cabal install network uulib syb fgl
To install UHC, first clone the repository and change into the EHC directory. Then build and install UHC.
git clone git://github.com/UU-ComputerScience/uhc.git
cd uhc/EHC
./configure
make
sudo make install
In blogs it is common to suggesting getting a cup of coffee at a moment like this, because the make command may take a while. So get a cup of coffee!
If everthing worked out, UHC should be installed and you can compile to javascript via
uhc -tjs Main.hs
(Or however your haskell file is called).
Hello World with UHC
We need to do two things:
- Get the document.
- Call write on the document with "Hello, World!" as parameter.
The FFI (ForeignFunctionInterface) of the js backend of UHC is described here Improving UHC js For our purposes, it works like this:
foreign import js "jscommand" haskellName :: Type
Where "jscommand" is the command in javascript, "haskellName" the name of the haskell function we want to define and "Type" the type of the haskell function. "jscommand" may contain "%N" where N is a number refering to the N-th parameter of the haskell function.
So to get the document we first define a type for it and then import a corresponding javascript command
data Document
foreign import js "document" getDocument :: IO Document
To get the document we just have to call "document" in javascript. This returns us the document in the IO monad. The document is in the IO monad because we a calling a foreign function which might have side effects.
Now we want to call "write" on a document.
foreign import js "%1.write(%2)" write :: Document -> JSString -> IO ()
Because the "%1" is in front of the ".write", the first argument to haskell function "write" (which is the document) is the object write is called on (this can all be found in Improving UHC js.
Note that the second argument is of type JSString and not of String. This is because a string in haskell is not the same as a string in javascript. We have to convert a haskell string to a javascript string
type JSString = PackedString
foreign import prim "primStringToPackedString" toJS :: String -> JSString
Now we are ready to write our hello world:
type JSString = PackedString
foreign import prim "primStringToPackedString" toJS :: String -> JSString
data Document
foreign import js "document" getDocument :: IO Document
foreign import js "%1.write(%2)" write :: Document -> JSString -> IO ()
main = do
doc <- getDocument
write doc $ toJS "Hello, World!"
Haste
Installing haste
Haste can be found here: Haste GitHub. Instructions for installation can also be found on that page (just read the Building section). It requires installation of fursuit, which can be found here: Fursuit GitHub.
Hello World with haste
Again, we need a way to import a function to get the document and to write into the contents of the document. Information on how to import javascript functions can be found in the doc subdirectory of the haste repository.
Update: The way FFI functions have to writting with haste has changed since the blog post has original been written. At that time returning values from javascript to haskell was a little bit more cumbersome. I have updated this blog post to reflect the new way of doing it. I hope I did not forget something in the process. So if you find an error, please comment.
Haste is not as flexible as UHC when importing JavaScript functions. It does not allow placing the parameters of the haskell function in the javascript code with "%N". It also does not allow the custom type "Document" to be used as a parameter or return type. Instead "JSAny" must be used.
So we create a file "helpers.hs" with out Javascript helper functions:
function objWrite(obj, text) {
obj.write(text); // Call the method
return; // Return ()
}
function getDocument() {
return document;
}
This now allows us to write hello world in Haste:
import Haste
import Haste.Prim
type Document = JSAny
foreign import ccall "objWrite" write :: Document -> JSString -> IO ()
foreign import ccall "getDocument" getDocument :: IO Document
main = do
doc <- getDocument
write doc $ toJSStr "Hello World!"
Compile this with:
hastec Main.hs --with-js=helpers.js
Now all we need to do is embed this file in a html page:
<!DOCTYPE html><html><head><title>Main</title>
<script type="text/javascript" src="Main.js"></script>
</head>
<body>
</body>
</html>
Open this with a browser of your choice (I only tried chromium) and it should work.
Edit: Trying it out
As suggested in a comment, I have uploaded the compiled javascript file (the haste version because the UHC version has several dependencies) here: Hello, World!
Download it and this html file into the same directory. Than open the html with a browser of your choice (tested on ubuntu linux, chromium).
Edit: Rewrote with pandoc
Writing JavaScript games in Haskell by Nathan Hüsken is licensed under a Creative Commons Attribution 3.0 Germany License.
Interesting post. It would be nice if you included a link to a web page including the compiled java script, so we could test out the results without having to do all that heavy lifting :)
ReplyDeleteThat would be nice of me, would it not? :)
DeleteI edited the post and added a link to the compiled file.
That looks extremely interesting. Thank you for bringing it to my attention.
ReplyDeleteIt should be on the The Java Script Problem page, should it not?
I wondering what the limits of fay are ...
It says class and instance declarations are not supported, but they are only relevant to the type checker and on that level they are supported, am I correct?
I think I will add fay to the lists of compilers I am testing to see what the limits are.
You might be interested in http://chrisdone.com/fay/
ReplyDeleteYes, I am already checking it out. Thanks!
Delete