Function 如何在Clojure中在运行时序列化函数?

Function 如何在Clojure中在运行时序列化函数?,function,serialization,clojure,edn,Function,Serialization,Clojure,Edn,在Clojure中是否有一种在运行时序列化函数的方法?我希望能够以序列化格式(可能是edn,但我对任何东西都持开放态度)通过线路发送无状态(但不是纯)函数 例如 如果我在一个函数上运行prn str,我就得不到我想要的 user=> (def fn1 (fn [x] (* x 2))) #'user/fn1 user=> (def data {:test 1 :key "value"}) #'user/data user=> (defn fn2 [x] (

在Clojure中是否有一种在运行时序列化函数的方法?我希望能够以序列化格式(可能是edn,但我对任何东西都持开放态度)通过线路发送无状态(但不是纯)函数


例如 如果我在一个函数上运行
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 \"user$fn1@28b9c6e2\"]\n"
user=> (prn-str data)
"{:test 1, :key \"value\"}\n"
user=> (prn-str fn2)
"#object[user$fn2 0x206c48f5 \"user$fn2@206c48f5\"]\n"
user=> 
我本想/期待这样的事情:

user=> (prn-str fn2)
"(fn [x] (* x 2))\n"
或者,也许

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

您必须使用
quote
'
来阻止评估,并使用
eval
强制评估:

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

您可以使用
clojure.repl/source

(with-out-str (source filter))
获取字符串,或

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

要获得clojure列表,您基本上有两种选择:

  • 传递源代码(存储为clojure数据的s表达式)
  • 传递jar文件并在另一端加载它们
对于第一个选项,在编译函数时保存源代码(几乎总是在定义时),然后将相同的源代码表达式传递给另一台计算机,让它编译相同的内容。首先,你可以做一个表达式向量:

(domain-functions '[(defn foo [x] x)
                    (defn bar [y] (inc y)]
然后,您可以将其存储到数据库中,每个客户端都可以将其传递给
read
,然后它们都将具有相同的功能

第二个选项取决于这样一个事实:每次定义函数时,它都会在/target目录中生成一个类文件,然后加载它。然后可以同步此目录并在另一端加载它们。这种方法当然是完全疯狂的,尽管人们在这里做着疯狂的事情。我推荐第一种方法


作为个人说明:


我现在用datomic来做这件事,我采用了使用宏将git散列放入函数名的做法,所以我绝对肯定地知道,当我调用函数时,我得到的函数与我在编辑器中看到的相同。当运行多个都来自同一个DB的实例时,这会让您安心

这真的不是一个好方法,而且出于充分的理由,简单地将一个函数发送到另一台计算机或将其存储在数据库中可能会导致很多问题,其中最重要的一点是,该函数可能需要其他不在另一端的函数


更好的办法是坚持使用数据。不要编写函数,而是将函数的名称作为事件编写,然后,无论是什么人读取数据,都可以将其转换。坚持使用数据,这是惯用的方法

Spark的Clojure包装器Flambo使用该库序列化函数(Spark需要)。Spark的另一个包装器Sparkling通过使用本机Clojure函数来实现Java接口可序列化。

在某个时候它不再是Clojure,因此我们可以从源代码到机器指令任意往返的期望有点偏离


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

但是,如果您已经有了一个函数,而不是它的源代码,并且希望序列化函数本身,该怎么办?我很肯定Datomic支持这一点;如何在Clojure中实现这一点?@SamEstep一旦编译了一个函数,就不能访问源代码,只能访问函数对象。但是,变量可以有元数据来描述在哪里可以找到源。这就是
source
的工作原理。我完全知道这一点。这不是我要问的;我明确地说,我是在询问一个您无法访问源代码的案例<代码>(源代码fn2)。在两个级别中挖掘,可以看到复制以获取字符串版本的源代码
(println(clojure.repl/source-fn'clojure.repl/source-fn))
。乍一看,它似乎相当不透明,但可能可以修改为一个func的可序列化版本。我的猜测是,在最普遍的情况下,它不会起作用。据我所知,您的第二种方法基本上是Hadoop的工作原理。但我同意这听起来很疯狂,但这是口齿不清。“代码就是数据,数据就是代码”的承诺发生了什么?很多时候,这一承诺被解释为仅仅意味着该语言是同源的,并且支持宏&
eval
。实现通常不会将其解释为“我们提供了对编译器机制的极其动态的内省访问”,可能是因为提供这些功能通常是性能杀手。当然@BlueJ774,但您不能只是将数据片段发送到某个地方并期望它运行。有些事情,如本地状态、虚拟机版本、语言版本等,都可能导致问题。代码在编译后也是不透明的,因此它是一种非常糟糕的通信方法。网络是用来传输数据的,我们应该用它们来传输数据,而不是代码。请注意,Sparkling对Clojure函数的“序列化”实际上是在序列化两个符号:名称空间和fn的名称。在反序列化它们时,它需要名称空间并返回对fn的引用。也就是说,fn的主体根本没有序列化。Clojure代码预计将被烘焙到uberjar中,并在集群上启动(因此所有代码都已经存在于每个worker上)。不支持动态定义Clojure函数并将其传递给Spark操作。