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对象,而 HaskellputStrLn似乎没有打印任何东西。

另一件事是 JVM 需要sysexit使用java.lang.System.exit.

我有两个问题:

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

2 回答 2

2

在luite的帮助下,我终于让它在 JVM 上使用了一些垫片

  1. 平台检测(shims/src/platform.js)

    Java 的 Nashorn 提供了全局Java变量,可以用来检测我们是否在 JVM 下运行。如果定义了此变量,h$isJvm则会设置一个类似于h$isNodeghcjs 运行时的全局变量。然后,此变量将用于在其他地方提供 JVM 特定代码。我们也可以console.log在这里定义,这样写入控制台就可以在 JVM 上开箱即用,而无需在用户程序中定义:

    if(typeof Java !== 'undefined') {
        h$isJvm = true;
        this.console = {
          log: function(s) {
            java.lang.System.out.print(s);
          }
        };
    }
    
  2. 正常退出 JVM (shims/src/thread.js)

    GHCJS 有一个方法被调用h$exitProcess,用于退出进程。使用我们在上一步中定义的变量h$isJvm,我们可以添加以下代码让 JVM 退出:

    if (h$isJvm) {
       java.lang.System.exit(code);
    }
    
  3. 命令行参数 (shims/src/environment.js)

    Nashorn 提供了一个全局arguments变量,其中包含传递给jjs. 我们可以使用这个变量添加一个垫片:

    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}
于 2016-03-05T05:28:22.060 回答
0

只是为了记录,这也是在github上问的

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

于 2016-03-04T07:50:31.117 回答