java haskell jni

Comunicación entre Java y Haskell.



jni (4)

Depende de cómo quieras que se comuniquen. Tener el código de Java y Haskell ejecutándose de forma nativa en el mismo proceso e intercambiar datos en la memoria a través de sus respectivas FFI es un gran problema, entre otras cosas porque tiene dos GC que luchan por los datos y dos compiladores que tienen sus propias ideas sobre la representación. varios tipos de datos Obtener Haskell compilado bajo la JVM también es difícil porque la JVM no tiene (en la actualidad) ningún concepto de cierre.

Por supuesto, se pueden hacer estas cosas, pero el esfuerzo de un demostrador a una herramienta industrial requiere un gran esfuerzo. Según tengo entendido, las herramientas que mencionas nunca superaron la etapa de demostración.

Una solución más simple, aunque menos elegante, es escribir su programa Haskell como un proceso de servidor que envía datos a través de sockets desde Java. Si el rendimiento y el volumen no son demasiado altos, codificarlo en JSON probablemente sería sencillo, ya que ambos lados tienen bibliotecas que lo admiten.

Busqué en Google y obtuve algunas respuestas de que la comunicación entre Java y Haskell se puede realizar mediante GCJNI (ahora el sitio está caído) y LambdaVM. ¿Dónde puedo saber más sobre ellos, ya que no encuentro muchos recursos en línea?

Quiero desarrollar una aplicación que se comunique entre Java y Haskell (donde obtendré la información de Java, pasarla a Haskell y procesarla allí y devolver el resultado a Java). Esto es lo que quiero hacer. Por favor, ayúdame...


Si opta por el enfoque del proceso del servidor Haskell, puede utilizar la biblioteca de serialización / rpc de MessagePack . Tiene enlaces tanto para Java como para Haskell, y los enlaces de Haskell parecen estar bien mantenidos. Busque msgpack y msgpack-rpc en Hackage.

Aquí hay un ejemplo de juguete de interacción Java / Haskell usando MessagePack: servidor Java , cliente Haskell (los enlaces van a GitHub). Sin embargo, la comunicación está en la dirección opuesta de lo que quieres.


Llamar a Haskell desde C parece bastante fácil, y por lo tanto también puede llamarse fácilmente desde Java con JavaCPP . Por ejemplo, para llamar a la función fibonacci_hs() desde el código de ejemplo 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

Podemos hacerlo de esta manera desde 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); } }

Bajo Linux, el procedimiento de compilación se ve así:

$ 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

Y el programa se ejecuta normalmente con el comando java habitual:

$ java -cp javacpp.jar:. Safe Fibonacci: 267914296 Edit : Me he tomado la libertad de hacer algunas marcas de microbado de la sobrecarga de llamadas. Con el siguiente archivo de cabecera C Safe.h :

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

la siguiente clase de Java:

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

genera esto con una CPU Intel Core i7-3632QM a 2.20GHz, Fedora 20 x86_64, GCC 4.8.3, GHC 7.6.3 y OpenJDK 8:

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

Para ser justos, debo mencionar que también es bastante costoso llamar a la JVM ...

Actualización : con los cambios recientes en JavaCPP , los usuarios ahora pueden acceder a la función de devolución de llamada (punteros) por nombre desde C / C ++, lo que hace posible llamar a la JVM desde Haskell fácilmente. Por ejemplo, según la información que se encuentra en una página wiki con respecto a la FFI de Haskell , con el siguiente código ubicado en 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

y una función de fibonacci definida en Java de esta manera:

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

podemos construir bajo Linux x86_64 con algo como:

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

y el programa se ejecuta correctamente dando esta salida:

$ ./Main 267914296


TL; DR: Use el patrón de paso de mensajes (es decir, cliente-servidor RPC o pares).

¿Por qué? Es más seguro, escalable, flexible y debugable. Llamar a FFI será frágil y difícil de probar.

Marcos / especificaciones RPC

  • gRPC bifurcación pública de Google de Protobufs RPC sobre HTTP / 2

  • msgpack-rpc No incluye un transporte.

  • zerorpc ZeroMQ + msgpack. Solo tiene implementaciones de Python y Node. Parece abandonado también.

  • XML-RPC Mature. Amplia interoperabilidad pero también es XML.

  • JSON-RPC Más fácil de depurar. No es un protocolo binario, aunque BSON tal vez hackee algunas bibliotecas.

Publicación por entregas

  • Protocol Buffers (protobufs) Hay muchas, muchas más herramientas para ello que otras. Es compatible con los miembros de datos versionados / opcionales que no requieren la recompilación (o ruptura) del mundo para interoperar.

  • msgpack Lindo, simple y eficiente, pero no admite cambios de esquema compatibles con versiones posteriores. Es puramente un codec binario, tonto. Probablemente demasiado simple y de bajo nivel para uso práctico.

Transportes

  • ZeroMQ Probablemente el transporte de mensajes más rápido, sin Infiniband / FC / 10 GbE, sin hilos.

  • Nanomsg Una nueva Nanomsg de la filosofía UNIX, segura para subprocesos, que Nanomsg ZeroMQ de uno de sus fundadores.

  • HTTP / 2 (usado por gRPC) La ventaja aquí es que es un estándar que funciona dentro y entre los centros de datos, y también tiene TLS (el código nativo de gRPC usa BoringSSL, la bifurcación "más segura" de Google OpenSSL).

  • MQTT Cuando necesita implementar notificaciones push a mil millones de dispositivos y usar un protocolo legible por humanos.