2016-08-03 3 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

В реплике. '(источник fn2)'. Выкапывая пару уровней, можно увидеть копию источника в строковой версии '(println (clojure.repl/source-fn 'clojure.repl/source-fn)). На первый взгляд это кажется довольно непрозрачным, но, возможно, его можно модифицировать, чтобы получить сериализуемую версию функции. Я думаю, что это не будет работать в самом общем случае. –

ответ

1

В какой-то момент он перестает быть Clojure, поэтому ожидание того, что мы можем произвольно обойти поездку от источника к машинным инструкциям и обратно, немного не работает.

Мы должны иметь возможность сериализовать функцию в массив байтов и отправлять ее через провод. Я подозреваю, что вам нужно будет захватить объект java.lang.Class функции, а затем передать это через java.lang.instrument.ClassFileTransformer, чтобы получить байты. После того, как у вас есть, вы можете передать их до дружественного java.lang.ClassLoader на удаленном jvm.

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 Как только функция скомпилирована, у вас нет доступа к исходному коду, просто к объекту функции. Тем не менее, у vars могут быть метаданные, описывающие, где источник может быть найден. Вот как работает «источник». –

+1

Я полностью осознаю это. Это не то, что я спрашивал; Я специально сказал, что спрашиваю о случае, когда у вас нет доступа к исходному коду. –

1

Вы можете использовать clojure.repl/source.

(with-out-str (source filter)) 

, чтобы получить строку, или

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

, чтобы получить список Clojure.

2

У вас есть в основном два варианта:

  • исходный код проход (S-выражения, хранящиеся в виде данных Clojure)
  • передача банку файлы и загружать их на другой стороне.

для первого варианта вы сохранить источник в то время, когда функция является компилирует (почти всегда, когда он определен), а затем передать такое же выражение источника на другой компьютер и дайте ему собрать то же самое. поэтому сначала вы могли бы сделать вектор выражений:

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

, то вы можете хранить это в базе данных, и каждый клиент может передать его read, а затем все они будут иметь одни и те же функции.

Второй вариант зависит от того, что каждый раз, когда вы определяете функцию, он создает файл класса в каталоге/target, а затем загружает его. Затем вы можете синхронизировать этот каталог и загружать их с другой стороны. Этот подход, конечно, совершенно сумасшедший, хотя люди здесь сумасшедшие. Я рекомендую первый подход


И как лично:

Я делаю это сейчас с datomic, и я принял практику сдачи ГИТ-хэш в имени функции с помощью макроса так Я абсолютно уверен, что когда я вызываю функцию, я получаю ту же самую функцию, которую вижу в редакторе. Это приносит душевное спокойствие при запуске многих случаев, которые все тянут от одной и той же БД.

+1

Из того, что я слышал, ваш второй подход в основном состоит в том, как работает Hadoop. Но я согласен, что это звучит безумно. – DynamiteReed

1

На самом деле это не очень хороший способ, и по уважительной причине просто отправка функции на другой компьютер или хранение ее в БД может вызвать множество проблем, не в последнюю очередь из-за которых для этой функции могут потребоваться другие функции, которые не находятся на другом конце.

Гораздо лучшая идея - придерживаться данных. Вместо того, чтобы записывать функцию, напишите имя функции как событие, а затем это может быть переведено позже тем, что читает ваши данные. Придерживайтесь данных, это идиоматический путь.

+0

Но это LISP. Что случилось с обещанием «код - это данные, данные - код»? – DynamiteReed

+0

Много раз это обещание интерпретируется как означающее просто, что язык является гомоиконическим, а макросы и 'eval' поддерживаются. Реализации не часто интерпретируют это как «мы предоставляем чрезвычайно динамичный интроспективный доступ к механизмам компилятора», вероятно, потому, что предоставление этих функций обычно является убийцей производительности. – amoe

+0

Sure @ BlueJ774, но вы не можете просто взять фрагмент данных и отправить его где-нибудь и ожидать его запуска. Есть такие вещи, как локальное состояние, ревизии VM, языковые версии и т. Д., Которые могут вызвать проблемы. Код также непрозрачен после его компиляции, поэтому он создает очень плохой способ связи. Сети предназначены для передачи данных, и мы должны использовать их для передачи данных, а не кода. –

2

Flambo, обертка Clojure для Spark, использует библиотеку serializable-fn для сериализации функций (что требует Spark). Sparkling, другая оболочка для Spark, использует собственные функции Clojure через this Java abstract class, который реализует интерфейс Java Serializable.

+2

Обратите внимание, что «сериализация» Sparkling функций Clojure действительно сериализует два символа: пространство имен и имя fn. При десериализации их требуется пространство имен и возвращает ссылку на fn. То есть тело fn не сериализуется вообще. Ожидается, что код Clojure будет испечен в uberjar и запущен в кластере (поэтому весь код уже существует для каждого рабочего). Динамическое определение функций Clojure и передача их в операцию Spark не поддерживается. –

Смежные вопросы