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?
8
  • 1
    Interesting question. Note, however, that you may well be better off using Frege if you want a Haskell-like language targeting the JVM. Commented Mar 1, 2016 at 5: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: gist.github.com/mmhelloworld/240ec2c13310eef14a51 Commented Mar 1, 2016 at 5:40
  • Maybe you'll want to check Trireme. It provides a nodejs-compatible environment on top of the JVM. Commented Mar 2, 2016 at 6:38
  • 1
    @ForNeVeR Thanks! That looks interesting but it is unfortunately still using Rhino, the old JS engine for the JVM: github.com/apigee/trireme#rhino Commented Mar 2, 2016 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: github.com/apigee/rowboat Commented Mar 3, 2016 at 4:04

2 Answers 2

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}
Sign up to request clarification or add additional context in comments.

Comments

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.

1 Comment

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.