2016-08-03 22 views
2

有没有办法在Clojure的运行时序列化函数?我希望能够通过串行格式发送无状态(但不是纯粹的)函数(可能是edn,但我对任何东西都是开放的)。如何在Clojure中运行时序列化函数?


例如...

如果我在一个功能运行prn-str,我没有得到我预期/希望。

user=> (def fn1 (fn [x] (* x 2))) 
#'user/fn1 
user=> (def data {:test 1 :key "value"}) 
#'user/data 
user=> (defn fn2 [x] (* x 2)) 
#'user/fn2 
user=> (prn-str fn1) 
"#object[user$fn1 0x28b9c6e2 \"[email protected]\"]\n" 
user=> (prn-str data) 
"{:test 1, :key \"value\"}\n" 
user=> (prn-str fn2) 
"#object[user$fn2 0x206c48f5 \"[email protected]\"]\n" 
user=> 

我本来想把/预期的是这样的:

user=> (prn-str fn2) 
"(fn [x] (* x 2))\n" 

或者,也许,

user=> (prn-str fn2) 
"(defn fn2 [x] (* x 2))\n" 
+0

在repl。 '(来源fn2)'。挖掘几个级别,复制以获取字符串版本的源代码可以看到'(println(clojure.repl/source-fn'clojure.repl/source-fn))''。乍一看,它看起来很不透明,但也许可以修改为给出一个可串行化版本的func。我的猜测是不会在最一般的情况下工作。 –

回答

1

在某些时候它不再是Clojure,所以期望我们可以任意地从源代码到机器指令的往返行程返回一点点。

我们应该能够将一个函数序列化为一个字节数组,然后通过电线发送。我怀疑你需要抓取函数的java.lang.Class对象,然后通过java.lang.instrument.ClassFileTransformer来获取字节。一旦你有了这些,你可以将它们传递给远程jvm上友好的java.lang.ClassLoader

2

你将不得不使用quote'防止评估和eval给力评价:

(def fn1 '(fn [x] (* x 2))) 
(prn-str fn1) ;;=> "(fn [x] (* x 2))\n" 
((eval fn1) 1) ;;=> 2 
+1

但是如果你已经有一个函数,而不是它的源代码,并且你想要序列化函数本身呢?我很确定Datomic支持这一点;你怎么能在Clojure中做到这一点? –

+1

@SamEstep一旦函数编译完成,你就无法访问源代码,只能访问函数对象。然而,变量可以有元数据来描述可以找到源的位置。这是'源'的工作原理。 –

+1

我完全意识到这一点。这不是我所问的;我特别说过,我在询问你有没有*可以访问源代码的情况。 –

1

您可以使用clojure.repl/source

(with-out-str (source filter)) 

得到一个字符串,或

(read-string (with-out-str (source filter))) 

获得Clojure的列表。

2

您有基本上两个选择:

  • 通源代码(存储为数据的Clojure s表达式)
  • 通jar文件并加载它们在另一侧上。

对于第一种选择,你保存当时的功能是:编译源(几乎总是当它被定义),然后在同一源表达传递到另一台计算机,并让它编译同样的事情。所以首先你可能使表达的载体:

(domain-functions '[(defn foo [x] x) 
        (defn bar [y] (inc y)] 

,那么你可以在此存储到数据库中,并且每个客户端可以将它传递给read,然后他们都将有相同的功能。

第二个选项取决于这样一个事实,即每次定义函数时,它都会在/ target目录中生成一个类文件,然后加载它。然后,您可以同步该目录并在另一端加载它们。这种方法当然是完全疯狂的,虽然人们在这里做疯狂的东西。我建议第一种方法


而作为一个个人注释:

我与datomic现在这样做,我已经通过了把混帐散列到函数名中使用宏这样的做法我绝对确信,当我调用函数时,我得到的是我在编辑器中看到的相同函数。当运行多个从同一个数据库中提取的实例时,这会带来安心。

+1

从我听说的,你的第二种方法基本上是Hadoop的工作原理。但我同意这听起来很疯狂。 – DynamiteReed

1

确实没有一个好方法,出于好的理由,简单地将函数传送到另一台计算机或将其存储在数据库中会导致很多问题,其中最重要的是该函数可能需要其他函数不在另一端。

更好的主意是坚持数据。不用编写函数,而是将函数的名称作为事件写入,然后甚至可以在以后读取数据时进行翻译。坚持数据,这是惯用的方式。

+0

但是这是LISP。 “代码是数据,数据是代码”的承诺发生了什么? – DynamiteReed

+0

许多时候,这个承诺被解释为仅仅意味着语言是homoiconic和宏''eval'是支持的。实现并不经常将其解释为“我们提供对编译器机制的非常动态的内省访问”,可能是因为提供这些功能通常是性能杀手。 – amoe

+0

Sure @ BlueJ774,但是你不能只取一段数据并将它发送到某个地方,并期望它运行。有些事情可能会导致问题,例如本地状态,VM修订版本,语言版本等。代码在编译后也是不透明的,所以它是一个非常差的通信方法。网络旨在传输数据,我们应该使用它们来传输数据,而不是代码。 –

2

Flambo是Spark的Clojure包装器,它使用serializable-fn库来序列化函数(Spark需要)。 sparkling是Spark的另一个包装,通过this Java abstract class使用本地Clojure函数,实现Java接口Serializable。

+2

请注意,Sparkling的Clojure函数的“序列化”实际上是序列化两个符号:命名空间和fn的名称。在反序列化它们时,它需要命名空间并返回对fn的引用。也就是说,fn的主体完全没有序列化。预计Clojure代码将被烘焙成一个uberjar并在集群上启动(因此所有的代码已经存在于每个工作人员身上)。不支持动态定义Clojure函数并将它们传递给Spark操作。 –