Communication between Java and Haskell

前端 未结 4 1684
没有蜡笔的小新
没有蜡笔的小新 2020-12-02 23:25

I googled and got some answers that communication between Java and Haskell can be done by GCJNI(Now the site is down) and LambdaVM.. To use the LambdaVM/GCJNI, whether I nee

相关标签:
4条回答
  • 2020-12-03 00:01

    Calling Haskell from C appears quite easy, and thus can also be easily called from Java with JavaCPP. For example, to call the fibonacci_hs() function from the sample code Safe.hs:

    {-# LANGUAGE ForeignFunctionInterface #-}
    
    module Safe where
    
    import Foreign.C.Types
    
    fibonacci :: Int -> Int
    fibonacci n = fibs !! n
        where fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
    
    fibonacci_hs :: CInt -> CInt
    fibonacci_hs = fromIntegral . fibonacci . fromIntegral
    
    foreign export ccall fibonacci_hs :: CInt -> CInt
    

    we can do it this way from Java:

    import org.bytedeco.javacpp.*;
    import org.bytedeco.javacpp.annotation.*;
    
    @Platform(include={"<HsFFI.h>","Safe_stub.h"})
    public class Safe {
        static { Loader.load(); }
        public static native void hs_init(int[] argc, @Cast("char***") @ByPtrPtr PointerPointer argv);
        public static native int fibonacci_hs(int i);
        public static void main(String[] args) {
            hs_init(null, null);
            int i = fibonacci_hs(42);
            System.out.println("Fibonacci: " + i);
        }
    }
    

    Under Linux, the compilation procedure looks like this:

    $ ghc -fPIC -dynamic -c -O Safe.hs
    $ javac -cp javacpp.jar Safe.java
    $ java -jar javacpp.jar -Dplatform.compiler=ghc -Dplatform.compiler.output="-optc-O3 -Wall Safe.o -dynamic -fPIC -shared -lstdc++ -lHSrts-ghc7.6.3 -o " -Dplatform.linkpath.prefix2="-optl -Wl,-rpath," Safe
    

    And the program runs normally with the usual java command:

    $ java -cp javacpp.jar:. Safe
    Fibonacci: 267914296
    


    Edit: I have taken the liberty to do some microbenchmarking of the calling overhead. With the following C header file Safe.h:

    inline int fibonacci_c(int n) {
        return n < 2 ? n : fibonacci_c(n - 1) + fibonacci_c(n - 2);
    }
    

    the following Java class:

    import org.bytedeco.javacpp.*;
    import org.bytedeco.javacpp.annotation.*;
    
    @Platform(include={"<HsFFI.h>","Safe_stub.h", "Safe.h"})
    public class Safe {
        static { Loader.load(); }
        public static native void hs_init(int[] argc, @Cast("char***") @ByPtrPtr PointerPointer argv);
        public static native int fibonacci_hs(int i);
        public static native int fibonacci_c(int n);
        public static int fibonacci(int n) {
            return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
        }
        public static void main(String[] args) {
            hs_init(null, null);
    
            for (int i = 0; i < 1000000; i++) {
                fibonacci_hs(0);
                fibonacci_c(0);
                fibonacci(0);
            }
            long t1 = System.nanoTime();
            for (int i = 0; i < 1000000; i++) {
                fibonacci_hs(0);
            }
            long t2 = System.nanoTime();
            for (int i = 0; i < 1000000; i++) {
                fibonacci_c(0);
            }
            long t3 = System.nanoTime();
            for (int i = 0; i < 1000000; i++) {
                fibonacci(0);
            }
            long t4 = System.nanoTime();
            System.out.println("fibonacci_hs(0): " + (t2 - t1)/1000000 + " ns");
            System.out.println("fibonacci_c(0): "  + (t3 - t2)/1000000 + " ns");
            System.out.println("fibonacci(0): "    + (t4 - t3)/1000000 + " ns");
        }
    }
    

    outputs this with an Intel Core i7-3632QM CPU @ 2.20GHz, Fedora 20 x86_64, GCC 4.8.3, GHC 7.6.3, and OpenJDK 8:

    fibonacci_hs(0): 200 ns
    fibonacci_c(0): 9 ns
    fibonacci(0): 2 ns
    

    In all fairness, I should mention that it is also pretty expensive to call into the JVM as well...


    Update: With recent changes to JavaCPP, users can now access callback function (pointers) by name from C/C++, making it possible to call into the JVM from Haskell easily. For example, based on information found on a wiki page regarding Haskell's FFI, with the following code placed in Main.hs:

    {-# LANGUAGE ForeignFunctionInterface #-}
    module Main where
    
    import Foreign.C -- get the C types
    import Foreign.Ptr (Ptr,nullPtr)
    
    -- impure function
    foreign import ccall "JavaCPP_init" c_javacpp_init :: CInt -> Ptr (Ptr CString) -> IO ()
    javacpp_init :: IO ()
    javacpp_init = c_javacpp_init 0 nullPtr
    
    -- pure function
    foreign import ccall "fibonacci" c_fibonacci :: CInt -> CInt
    fibonacci :: Int -> Int
    fibonacci i = fromIntegral (c_fibonacci (fromIntegral i))
    
    main = do
      javacpp_init
      print $ fibonacci 42
    

    and a fibonacci function defined in Java this way:

    import org.bytedeco.javacpp.*;
    import org.bytedeco.javacpp.annotation.*;
    
    @Platform
    public class Main {
        public static class Fibonacci extends FunctionPointer {
            public @Name("fibonacci") int call(int n) {
                return n < 2 ? n : call(n - 1) + call(n - 2);
            }
        }
    }
    

    we may build under Linux x86_64 with something like:

    $ javac -cp javacpp.jar Main.java
    $ java -jar javacpp.jar Main -header
    $ ghc --make Main.hs linux-x86_64/libjniMain.so
    

    and the program executes correctly giving this output:

    $ ./Main
    267914296
    
    0 讨论(0)
  • 2020-12-03 00:10

    TL;DR: Use message passing (ie RPC client-server or peers) pattern.

    Why? It's safer, scalable, flexible and debuggable. Calling into FFI is going to be fragile and hard to test.


    RPC frameworks/specifications

    • gRPC Google's public fork of Protobufs RPC over HTTP/2

      • unofficial Haskell gRPC bindings
    • msgpack-rpc Doesn't include a transport.

    • zerorpc ZeroMQ + msgpack. Only has Python and Node implementations. Seems abandoned too.

    • XML-RPC Mature. Wide interoperability but it's also XML.

    • JSON-RPC Easier to debug. Not a binary protocol, although BSON maybe hack into some libraries.


    Serialization

    • Protocol Buffers (protobufs) There are many, many more tools for it than others. It supports versioned/optional data members that don't require recompiling (or breaking) the world to interoperate.

    • msgpack Cute, simple and efficient, but it doesn't support forward-compatible schema changes. It's purely a dumb, binary codec. Probably too simple and low-level for practical use.


    Transports

    • ZeroMQ Probably the fastest, non-Infiniband/FC/10 GbE, non-thread-safe message transport.

    • Nanomsg A newer, thread-safe, UNIX-philosophy reimagining of ZeroMQ from one of its founders.

    • HTTP/2 (used by gRPC) The advantage here is it's a standard that works within and between datacenters, and also has TLS (gRPC native code uses BoringSSL, Google's "more secure" OpenSSL fork).

    • MQTT When you need to implement push notifications to a billion devices and use a human-readable protocol.

    0 讨论(0)
  • 2020-12-03 00:20

    If you opt for the Haskell server process approach, you could use the MessagePack serialization/rpc library. It has bindings for both Java and Haskell , and the Haskell bindings seem to be well maintained. Look for msgpack and msgpack-rpc on Hackage.

    Here's a toy example of Java/Haskell interaction using MessagePack: Java server, Haskell client (links go to GitHub). The communication is in the opposite direction of what you want, though.

    0 讨论(0)
  • 2020-12-03 00:21

    It depends on how you want them to communicate. To have Java and Haskell code running natively in the same process and exchanging data in memory via their respective FFIs is a huge problem, not least because you have two GCs fighting over the data, and two compilers both of which have their own ideas about representing various data types. Getting Haskell compiled under the JVM is likewise difficult because the JVM does not (at present) have any concept of closures.

    Of course these things can be done, but getting from demonstrator to industrial tool takes huge effort. My understanding is that the tools you mention never made it past the demonstrator stage.

    A simpler, if less elegant, solution, is to write your Haskell program as a server process that is sent data over sockets from the Java. If performance and volume is not too high then coding it up in JSON would probably be simple, as both sides have libraries to support it.

    0 讨论(0)
提交回复
热议问题