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 enMain.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.