在JVM上运行编译为JavaScript的Haskell

时间:2016-03-01 05:23:20

标签: haskell ghcjs

Java 8有一个内置的JavaScript引擎,名为Nashorn,因此实际上可以在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需要退出sysexitjava.lang.System.exit实现的FFI功能。

我有两个问题:

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

2 个答案:

答案 0 :(得分:2)

luite的帮助下,我终于得到了shims for the JVM的一点点工作:

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

    Java的Nashorn提供全局Java变量,可用于检测我们是否在JVM下运行。如果定义了此变量,则为ghcjs运行时设置类似于h$isJvm的全局变量h$isNode。然后,此变量将用于在其他位置提供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;
    }
    
  4. 使用这些填充程序,我们可以在JVM上运行大多数Haskell。这是问题中的原始程序,GHCJS中添加了上述填充程序:

    module Main where
    
    main = putStrLn "Hello from Haskell!"
    

    这个常规的Haskell代码现在可以在JVM中运行。即使是非常重要的小部分也直接在JVM上运行。例如,以下代码取自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)
    

    我们可以使用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}
    

答案 1 :(得分:0)

仅供记录,这也是github

的问题

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