在JVM上运行编译为JavaScript的Haskell代码

11

Java 8内置了一个名为Nashorn的JavaScript引擎,因此实际上可以在JVM上运行编译成JavaScript的Haskell。

以下程序有效:

{-# 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只是在JVM上运行纯JavaScript代码的方便方式)

$ 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退出。

我有两个问题:

  1. console.log类似,ghcjs中假定哪些其他主机依赖项必须定义?
  2. JVM是否无法正常关闭是因为ghcjs在后台创建了事件循环还是其他原因?有没有办法避免这种情况,使程序正常退出?

1
有趣的问题。然而请注意,如果您想使用类似 Haskell 的语言针对 JVM,那么使用 Frege 可能会更好。 - dfeuer
也许你想要检查一下Trireme。它在JVM之上提供了一个与Node.js兼容的环境。 - ForNeVeR
1
@ForNeVeR 谢谢!看起来很有趣,但不幸的是它仍在使用Rhino,这是JVM的旧JS引擎:https://github.com/apigee/trireme#rhino - Marimuthu Madasamy
@MarimuthuMadasamy,我有一种感觉,你想让代码原则上运行,而不一定要有良好的性能。我非常希望看到Trireme实现与Nashorn一起使用,但不幸的是这并不是当前正在积极开发的项目。请查看此链接:https://github.com/apigee/rowboat - ForNeVeR
@ForNeVeR Nashorn最终可以通过JVM中的一些shim来实现,如下面我的答案所示! - Marimuthu Madasamy
显示剩余3条评论
2个回答

2

luite 的帮助下,我终于通过为 JVM 添加一些shims使其正常运行:

  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;
    }
    

使用这些“垫片”,我们可以在JVM上直接运行大多数Haskell代码。以下是在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}

0

仅供记录,这个问题也在github上被提出了。

那里的答案指向了现有平台检测代码,以及进程退出功能。这些和相关领域将提供ghcjs扩展以支持jvm作为特定平台的要点。


1
是的,我在那里提出了这个问题。一旦我在ghcjs中添加了对JVM的必要支持并获得了完全工作的示例,我将在此处发布我的发现答案。 - Marimuthu Madasamy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接