Запуск Haskell, скомпилированного в JavaScript на JVM

Java 8 имеет встроенный механизм JavaScript под названием Nashorn, поэтому на самом деле можно запустить Haskell, скомпилированный в JavaScript на JVM.

Работает следующая программа:

{-# 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

Мы можем запустить его с помощью: (Боковое примечание: это можно запустить как обычную программу Java. jjs - это просто удобный способ запускать чистый код JavaScript на 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!

В приведенном выше коде console.log необходимо определить с помощью java.lang.System.print, поскольку Nashorn не предоставляет глобальный объект console по умолчанию, а Haskell putStrLn в противном случае ничего не печатает.

Другое дело, что JVM необходимо выйти с помощью функции sysexit FFI, реализованной с помощью java.lang.System.exit.

У меня есть 2 вопроса:

  • Как и в случае с console.log, какие другие зависимости хоста предполагается в ghcjs, которые должны быть определены?
  • Является ли JVM нормально не закрываться из-за того, что ghcjs создает цикл событий в фоновом режиме или по какой-либо другой причине? Есть ли способ избежать этого и заставить программу нормально работать?

Ответ 1

С помощью luite я наконец-то начал работать с немного прокладки для JVM:

  • Определение платформы (планки/src/platform.js)

    Java Nashorn предоставляет глобальную переменную Java, которая может использоваться для определения того, работаем ли мы под JVM. Если эта переменная определена, глобальная переменная h$isJvm устанавливается аналогично h$isNode для времени выполнения ghcjs. Затем эта переменная будет использоваться для предоставления JVM-кода в других местах. Мы также можем определить console.log здесь, чтобы запись в консоль работала из коробки в JVM без необходимости определять ее в пользовательской программе:

    if(typeof Java !== 'undefined') {
        h$isJvm = true;
        this.console = {
          log: function(s) {
            java.lang.System.out.print(s);
          }
        };
    }
    
  • Выход из JVM обычно (прокладки/src/thread.js)

    GHCJS имеет метод под названием h$exitProcess, который используется для выхода из процесса. С переменной, определенной на предыдущем шаге, h$isJvm, мы можем добавить следующий код для выхода JVM:

    if (h$isJvm) {
       java.lang.System.exit(code);
    }
    
  • Аргументы командной строки (прокладки/src/environment.js)

    Nashorn предоставляет глобальную переменную arguments, которая содержит значения параметров командной строки, переданные в jjs. Мы можем добавить прокладку, используя эту переменную:

    if(h$isJvm) {
        h$programArgs = h$getGlobal(this).arguments;
    }
    

С этими прокладками мы можем запустить большую часть Haskell из коробки на JVM. Вот исходная программа в вопросе с вышеуказанными прокладками, добавленными в GHCJS:

module Main where

main = putStrLn "Hello from Haskell!"

Этот обычный код Haskell теперь запускается из коробки в JVM. Даже небольшие нетривиальные функции запускаются непосредственно на JVM. Например, следующий код, взятый из здесь:

{-# 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)

Мы можем построить его с помощью stack и запустить с помощью jjs прохождение аргументов командной строки:

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}

Ответ 2

Только для записи это также было задано на github

Ответ там указал на существующий код обнаружения платформы, а также выход из процесса функциональность. Эти и связанные с ними области предоставят точки, в которых ghcjs могут быть расширены, чтобы поддерживать jvm как определенную платформу.