Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running the Haskell compiled to JavaScript on the JVM

Tags:

haskell

ghcjs

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?
like image 891
Marimuthu Madasamy Avatar asked Mar 01 '16 05:03

Marimuthu Madasamy


1 Answers

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}
like image 63
Marimuthu Madasamy Avatar answered Nov 10 '22 00:11

Marimuthu Madasamy