11

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:

  1. Similar to console.log, what other host dependencies are assumed in ghcjs that have to be defined?
  2. Is the JVM not shutting down normally because of ghcjs creating an event loop in the background or some other reason? Is there any way to avoid that and make the program exit normally?
Marimuthu Madasamy
  • 13,126
  • 4
  • 30
  • 52
  • 1
    Interesting question. Note, however, that you may well be better off using Frege if you want a Haskell-like language targeting the JVM. – dfeuer Mar 01 '16 at 05:34
  • @dfeuer yes, Frege is my language of choice on the JVM currently. I also think this is an interesting option as so I am just exploring how far it goes :) Here is another example I tried that converts between Haskell and Java list: https://gist.github.com/mmhelloworld/240ec2c13310eef14a51 – Marimuthu Madasamy Mar 01 '16 at 05:40
  • Maybe you'll want to check [Trireme](https://github.com/apigee/trireme). It provides a nodejs-compatible environment on top of the JVM. – ForNeVeR Mar 02 '16 at 06:38
  • 1
    @ForNeVeR Thanks! That looks interesting but it is unfortunately still using Rhino, the old JS engine for the JVM: https://github.com/apigee/trireme#rhino – Marimuthu Madasamy Mar 02 '16 at 15:21
  • @MarimuthuMadasamy, I had a feeling that you want the code to be running in principle, and not necessarily with good performance. I would *love* to see Trireme implementation with Nashorn, but unfortunately this is not something actively worked on. Check this: https://github.com/apigee/rowboat – ForNeVeR Mar 03 '16 at 04:04
  • @ForNeVeR I actually did try out `trireme` but got this error from ghcjs: `setErrno not yet implemented: Error: EBADF`. I also tried out `rowboat` now. I tried to build it from sources but the tests are failing. At the moment, plain Nashorn is the best option as with Nashorn, the program actually works. – Marimuthu Madasamy Mar 03 '16 at 04:46
  • @MarimuthuMadasamy, I had no intention to switch you to some another solution or tell you that you're doing something wrong, I was just trying to show you another options. I actually like your idea of running ghcjs code on JVM, it's very fun. I hope you'll find a reliable Nashorn-powered solution. – ForNeVeR Mar 03 '16 at 06:51
  • @ForNeVeR Nashorn works in the end with few shims for the JVM as shown in my answer below! – Marimuthu Madasamy Mar 05 '16 at 05:42

2 Answers2

2

With the help from luite, I have finally got it working with a little bit of shims for the JVM:

  1. Platform detection (shims/src/platform.js)

    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);
          }
        };
    }
    
  2. Exiting JVM normally (shims/src/thread.js)

    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);
    }
    
  3. Command line arguments (shims/src/environment.js)

    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}
Marimuthu Madasamy
  • 13,126
  • 4
  • 30
  • 52
0

Just for the record, this was also asked on github

The answer there pointed to existing platform detection code, as well as process exit functionality. These and related areas would provide the points where ghcjs could be extended to support the jvm as a particular platform.

sclv
  • 38,665
  • 7
  • 99
  • 204
  • 1
    Yes, I asked it there. I will post an answer here later with my findings once I have a fully working example after adding the necessary support for JVM in ghcjs. – Marimuthu Madasamy Mar 04 '16 at 08:00