Java 8 has an inbuilt JavaScript engine called Nashorn so it is actually possible to run Haskell compiled to JavaScript on the JVM.
The following program works:
{-# LANGUAGE JavaScriptFFI #-}
module Main where
foreign import javascript unsafe "console={log: function(s) { java.lang.System.out.print(s); }}"
setupConsole :: IO ()
foreign import javascript unsafe "java.lang.System.exit($1)"
sysexit :: Int -> IO ()
main = do
setupConsole
putStrLn "Hello from Haskell!"
sysexit 0
We can run it with: (Side note: It is possible to run this as a normal Java program.jjs
is just a convenient way to run pure JavaScript code on the JVM)
$ ghcjs -o Main Main.hs
[1 of 1] Compiling Main ( Main.hs, Main.js_o )
Linking Main.jsexe (Main)
$ which jjs
~/bin/jdk/bin/jjs
$ jjs Main.jsexe/all.js
Hello from Haskell!
In the above code, console.log
needs to be defined using java.lang.System.print
as Nashorn doesn't provide the default global console
object and Haskell's putStrLn
otherwise doesn't seem to be printing anything.
The other thing is that the JVM needs to be exited with sysexit
FFI function implemented with java.lang.System.exit
.
I have 2 questions:
console.log
, what other host dependencies are assumed in ghcjs that have to be defined?With the help from luite, I have finally got it working with a little bit of shims for the JVM:
Java's Nashorn provides the global Java
variable which can be used to detect if we are running under JVM. If this variable is defined, a global variable h$isJvm
is set similar to h$isNode
for the ghcjs runtime. This variable will then be used to provide JVM specific code in other places. We can also define console.log
here so that writing to console works out of the box on the JVM without having to define it in the user program:
if(typeof Java !== 'undefined') {
h$isJvm = true;
this.console = {
log: function(s) {
java.lang.System.out.print(s);
}
};
}
GHCJS has a method called h$exitProcess
which is used to exit the process. With the variable we defined in the previous step, h$isJvm
, we can add the following code for the JVM to exit:
if (h$isJvm) {
java.lang.System.exit(code);
}
Nashorn provides a global arguments
variable that contains the command line parameter values passed to jjs
. We can add a shim using this variable:
if(h$isJvm) {
h$programArgs = h$getGlobal(this).arguments;
}
With these shims, we can run most Haskell out of the box on the JVM. Here is the original program in the question with the above shims added in GHCJS:
module Main where
main = putStrLn "Hello from Haskell!"
This regular Haskell code now runs out of the box in the JVM. Even the little non-trivial ones run directly on the JVM. For example, the following code taken from here:
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
import Options.Generic
data Example = Example { foo :: Int, bar :: Double }
deriving (Generic, Show)
instance ParseRecord Example
main = do
x <- getRecord "Test program"
print (x :: Example)
We can build it with stack
and run with jjs
passing command line arguments:
haskell-jvm-hello$ stack build
haskell-jvm-hello$ jjs ./.stack-work/dist/x86_64-linux/Cabal-1.22.4.0_ghcjs/build/haskell-jvm-hello-exe/haskell-jvm-hello-exe.jsexe/all.js -- --help
Test program
Usage: a.js --foo INT --bar DOUBLE
Available options:
-h,--help Show this help text
haskell-jvm-hello$ jjs ./.stack-work/dist/x86_64-linux/Cabal-1.22.4.0_ghcjs/build/haskell-jvm-hello-exe/haskell-jvm-hello-exe.jsexe/all.js -- --foo 1 --bar 2.5
Example {foo = 1, bar = 2.5}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With