Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
从Haskell调用Clojure函数_Haskell_Clojure_Jvm_Ffi - Fatal编程技术网

从Haskell调用Clojure函数

从Haskell调用Clojure函数,haskell,clojure,jvm,ffi,Haskell,Clojure,Jvm,Ffi,是否可以使用FFI或其他技巧从Haskell(GHC上)调用Clojure函数?在这里,我感兴趣的是保持在GHC的范围内(即不使用Frege)。我还对将中心程序保存在Haskell中感兴趣(这意味着Clojure函数应该从Haskell调用,反之亦然) 如何做到这一点?一个简单的方法是使用or启动Clojure流程。 这将启用基于套接字的REPL,因此您可以使用套接字调用Clojure函数。让我从广告开始,这将使调用Clojure变得非常简单,只需编写调用函数的Java代码即可。也就是说,由于我

是否可以使用FFI或其他技巧从Haskell(GHC上)调用Clojure函数?在这里,我感兴趣的是保持在GHC的范围内(即不使用Frege)。我还对将中心程序保存在Haskell中感兴趣(这意味着Clojure函数应该从Haskell调用,反之亦然)


如何做到这一点?

一个简单的方法是使用or启动Clojure流程。
这将启用基于套接字的REPL,因此您可以使用套接字调用Clojure函数。

让我从广告开始,这将使调用Clojure变得非常简单,只需编写调用函数的Java代码即可。也就是说,由于我没有运行最先进的GHC 8.0.2(并且有许多其他安装问题),所以我无法使用它。当(如果)我运行
内联java
时,我将更新此解决方案

我下面的解决方案首先通过JNI为ClojureAPI for Java中的Java方法创建一个C接口。然后,它使用Haskell FFI支持调用该C接口。根据JDK和JRE的安装位置,您可能需要调整库并包括文件路径。如果一切正常,您应该看到打印到标准输出的
7
。这是Clojure计算的
3
加上
4

安装程序 如果您还没有jar,请下载它。我们将使用。确保已定义
LD\u LIBRARY\u路径
。在我使用的机器上,这意味着出口

export LD_LIBRARY_PATH="/usr/lib64/jvm/java/jre/lib/amd64/server/"
最后,这里是一个makefile,它使编译更容易一些。您可能需要调整某些库并包括路径

# makefile
all:
    gcc -O -c \
        -I /usr/lib64/jvm/java/include/ \
        -I /usr/lib64/jvm/java/include/linux/ \
        java.c
    ghc -O2 -Wall \
        -L/usr/lib64/jvm/java/jre/lib/amd64/server/ \
        -ljvm \
        clojure.hs \
        java.o

run:
    ./clojure

clean:
    rm -f java.o 
    rm -f clojure clojure.o clojure.hi
Clojure函数的C接口 现在,我们将为我们需要的JVM和Clojure功能创建一个C接口。为此,我们将使用。我选择公开一个非常有限的接口:

  • create\u vm
    使用类路径上的Clojure jar初始化一个新的JVM(如果将Clojure jar放在同一文件夹之外的某个地方,请确保对此进行调整)
  • load\u方法
    查找我们需要的Clojure方法。谢天谢地,这个函数非常小,所以我们可以毫不费力地包装几乎所有的函数。我们还需要有一些函数,可以将数字或字符串之类的内容转换为相应的Clojure表示形式,并将其转换为相应的Clojure表示形式。我只为
    java.lang.Long
    (这是Clojure的默认整数类型)做过这项工作。
    • readObj
      包装
      clojure.java.api.clojure.read
      (带C字符串)
    • varObj
      包装了
      clojure.java.api.clojure.var
      的一个参数版本(带有C字符串)
    • varObjQualified
      包装了两个参数版本的
      clojure.java.api.clojure.read
      (带C字符串)
    • longValue
      将Clojure-long转换为C-long
    • newLong
      将C long转换为Clojure long
    • invokeFn
      分派给正确类型的
      clojure.lang.IFn.invoke
      。在这里,我只想把它暴露给第2条,但并没有什么能阻止你们更进一步
代码如下:

// java.c
#include <stdio.h>
#include <stdbool.h>
#include <jni.h>

// Uninitialized Java natural interface
JNIEnv *env;
JavaVM *jvm;

// JClass for Clojure
jclass clojure, ifn, longClass;
jmethodID readM, varM, varQualM, // defined on 'clojure.java.api.Clojure'
          invoke[2],             // defined on 'closure.lang.IFn'
          longValueM, longC;     // defined on 'java.lang.Long'

// Initialize the JVM with the Clojure JAR on classpath. 
bool create_vm() {
  // Configuration options for the JVM
  JavaVMOption opts = {
    .optionString =  "-Djava.class.path=./clojure-1.8.0.jar",
  };
  JavaVMInitArgs args = {
    .version = JNI_VERSION_1_6,
    .nOptions = 1,
    .options = &opts,
    .ignoreUnrecognized = false,
  };

  // Make the VM
  int rv = JNI_CreateJavaVM(&jvm, (void**)&env, &args);
  if (rv < 0 || !env) {
    printf("Unable to Launch JVM %d\n",rv);
    return false;
  }
  return true;
}

// Lookup the classes and objects we need to interact with Clojure.
void load_methods() {

  clojure    = (*env)->FindClass(env, "clojure/java/api/Clojure");
  readM      = (*env)->GetStaticMethodID(env, clojure, "read", "(Ljava/lang/String;)Ljava/lang/Object;");
  varM       = (*env)->GetStaticMethodID(env, clojure, "var",  "(Ljava/lang/Object;)Lclojure/lang/IFn;");
  varQualM   = (*env)->GetStaticMethodID(env, clojure, "var",  "(Ljava/lang/Object;Ljava/lang/Object;)Lclojure/lang/IFn;");

  ifn        = (*env)->FindClass(env, "clojure/lang/IFn");
  invoke[0]  = (*env)->GetMethodID(env, ifn, "invoke", "()Ljava/lang/Object;");
  invoke[1]  = (*env)->GetMethodID(env, ifn, "invoke", "(Ljava/lang/Object;)Ljava/lang/Object;");
  invoke[2]  = (*env)->GetMethodID(env, ifn, "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
  // Obviously we could keep going here. The Clojure API has 'invoke' for up to 20 arguments...

  longClass  = (*env)->FindClass(env, "java/lang/Long");
  longValueM = (*env)->GetMethodID(env, longClass, "longValue", "()J");
  longC      = (*env)->GetMethodID(env, longClass, "<init>",    "(J)V");
}

// call the 'invoke' function of the right arity on 'IFn'.
jobject invokeFn(jobject obj, unsigned n, jobject *args) {
  return (*env)->CallObjectMethodA(env, obj, invoke[n], (jvalue*)args);
}

// 'read' static method from 'Clojure' object.
jobject readObj(const char *cStr) {
  jstring str = (*env)->NewStringUTF(env, cStr);
  return (*env)->CallStaticObjectMethod(env, clojure, readM, str);
}

// 'var' static method from 'Clojure' object.
jobject varObj(const char* fnCStr) {
  jstring fn = (*env)->NewStringUTF(env, fnCStr);
  return (*env)->CallStaticObjectMethod(env, clojure, varM, fn);
}
// qualified 'var' static method from 'Clojure' object.
jobject varObjQualified(const char* nsCStr, const char* fnCStr) {
  jstring ns = (*env)->NewStringUTF(env, nsCStr);
  jstring fn = (*env)->NewStringUTF(env, fnCStr);
  return (*env)->CallStaticObjectMethod(env, clojure, varQualM, ns, fn);
}
试试看! 编译应该是
makeall
和运行
makerun

局限性 因为这只是一个概念证明,所以有很多事情需要解决:

  • 所有Clojure原语类型的正确转换
  • 完成后请拆除JVM
  • 确保我们没有在任何地方引入内存泄漏(我们可能会使用
    newArray
  • 在Haskell中正确表示Clojure对象
  • 更多

也就是说,它有效

谷歌“haskell内联java”。这是最近的工作,我对潜在的亲密投票者一无所知:如果这是一个值得结束的推荐问题,那么我们不妨结束Stack Overflow中的每一个非概念性问题,因为他们都要求我们推荐做事的方法。我同意这是一个非常有价值的问题,不应该关闭。你最好的选择是遵循Haskell->C(jni)->Java->Clojure->Java->C->Haskell的路径。您的clojure代码是否已编译(到
.class
文件中)?@Marcs我一直在玩这个,现在我为一些clojure提供了一个C接口。我希望下一步能和Haskell对接。。。也许明天吧,很好。我认为在Haskell文档中有这样一个例子是很好的。很好的答案。这也说明了从Haskell调用clojure有多么麻烦。如果你演示如何使用内联java方法,我会再加上50英镑。@George当然,但我要等到GHC 8.0.2的正式发布(已经过期了),因为
内联java
的黑客页面说我至少需要它。:)@亚历克:看来GHC 8.0.2今天发布了。让您知道,此优惠仍然有效:)
-- clojure.hs
{-# LANGUAGE GeneralizedNewtypeDeriving, ForeignFunctionInterface #-}

import Foreign
import Foreign.C.Types
import Foreign.C.String

-- Clojure objects are just Java objects, and jsvalue is a union with size 64
-- bits. Since we are cutting corners, we might as well just derive 'Storable'
-- from something else that has the same size - 'CLong'.
newtype ClojureObject = ClojureObject CLong deriving (Storable)

foreign import ccall "load_methods" load_methods :: IO ()
foreign import ccall "create_vm" create_vm :: IO ()
foreign import ccall "invokeFn" invokeFn :: ClojureObject -> CUInt -> Ptr ClojureObject -> IO ClojureObject
-- foreign import ccall "readObj" readObj :: CString -> IO ClojureObject
-- foreign import ccall "varObj" varObj :: CString -> IO ClojureObject
foreign import ccall "varObjQualified" varObjQualified :: CString -> CString -> IO ClojureObject
foreign import ccall "newLong" newLong :: CLong -> ClojureObject
foreign import ccall "longValue" longValue :: ClojureObject -> CLong

-- | In order for anything to work, this needs to be called first.
loadClojure :: IO ()
loadClojure = create_vm *> load_methods

-- | Make a Clojure function call
invoke :: ClojureObject -> [ClojureObject] -> IO ClojureObject
invoke fn args = do
  args' <- newArray args
  let n = fromIntegral (length args)
  invokeFn fn n args'

-- | Make a Clojure number from a Haskell one
long :: Int64 -> ClojureObject
long l = newLong (CLong l)

-- | Make a Haskell number from a Clojure one
unLong :: ClojureObject -> Int64
unLong cl = let CLong l = longValue cl in l

-- | Look up a var in Clojure based on the namespace and name
varQual :: String -> String -> IO ClojureObject
varQual ns fn = withCString ns (\nsCStr ->
                withCString fn (\fnCStr -> varObjQualified nsCStr fnCStr))

main :: IO ()
main = do
  loadClojure
  putStrLn "Clojure loaded"

  plus <- varQual "clojure.core" "+"
  out <- invoke plus [long 3, long 4]
  print $ unLong out -- prints "7" on my tests