Самореференция функции Clojure для получения собственных метаданных - PullRequest
1 голос
/ 30 марта 2019

Когда я присоединяю некоторые метаданные к функции и затем вызываю ее, я не могу получить доступ к этим метаданным в этой функции

(let [I (fn I [x] (println I) (println (meta I)))]
  (let [f (with-meta I {:rr 5})]
    (println I)
    (println f)
    (f I)))

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

Ответы [ 3 ]

0 голосов
/ 02 апреля 2019

Скорее случайно, я обнаружил уловку, которая позволяет функциям читать собственные метаданные. Оказывается, компилятор Clojure генерирует код поддержки метаданных по-разному, когда в исходном определении функции есть пользовательские метаданные. Если он присутствует, (meta fn-name) работает внутри тела функции, в противном случае - нет. Например, следующее производит результат, требуемый OP:

*clojure-version*
;;=> {:major 1, :minor 10, :incremental 0, :qualifier nil}

(let [f1 ^{:foo true} (fn f [] (meta f))
      f2 (with-meta f1 {:bar true})]
  (prn (f1))
  (prn (f2)))
;;=> {:foo true}
;;=> {:bar true}
;;=> nil

Мы можем исследовать код, сгенерированный для функции без метаданных в исходном определении - есть только метод invoke

(require '[clojure.pprint :as p])

(let [ff (fn f [] (meta f))]
  (p/pprint (seq (.getDeclaredMethods (class ff)))))
;;=> (#object[java.lang.reflect.Method 0x2b56b137 "public java.lang.Object user$eval2171$f__2172.invoke()"])
;;=> nil

И когда метаданные присутствуют, генерируются дополнительные методы (meta и withMeta) для работы с метаданными.

(let [ff ^:foo (fn f [] (meta f))]
  (p/pprint (seq (.getDeclaredMethods (class ff)))))
;;=> (#object[java.lang.reflect.Method 0x3983bd83 "public clojure.lang.IObj user$eval2175$f__2176.withMeta(clojure.lang.IPersistentMap)"]
;;=> #object[java.lang.reflect.Method 0x547d182d "public clojure.lang.IPersistentMap user$eval2175$f__2176.meta()"]
;;=> #object[java.lang.reflect.Method 0x62c3d0fe "public java.lang.Object user$eval2175$f__2176.invoke()"])
;;=> nil
0 голосов
/ 03 апреля 2019

Добро пожаловать в Clojure, @xstreamer!

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

(defn f
  "Boring doc"
  [])

(meta #'f)
;; => {:arglists ([]),
;;     :doc      "Boring doc",
;;     :line     32,
;;     :column   1,
;;     :file     "C:/Users/teodorlu/IdeaProjects/th-scratch/src/th/play/core.clj",
;;     :name     f,
;;     :ns       #object[clojure.lang.Namespace 0x3b402f0c "th.play.core"]}

Теперь переопределите его!

(alter-meta! #'f assoc :rr 5)

(meta #'f)
;; => {:arglists ([]),
;;     :doc      "Boring doc",
;;     :line     32,
;;     :column   1,
;;     :file     "C:/Users/teodorlu/IdeaProjects/th-scratch/src/th/play/core.clj",
;;     :name     f,
;;     :ns       #object[clojure.lang.Namespace 0x3b402f0c "th.play.core"],
;;     :rr       5}

Где assoc устанавливает значение на карте.

(assoc {} :rr 5)
;; {:rr 5}

(assoc {:some :stuff} :more :stuff)
;; {:some :stuff, :more :stuff}

Ссылки

Если вас смущает #'f, вы получите var, представляющий привязку f , а не только значение, к которому оно относится. Для получения дополнительной информации о vars и о том, как их использовать, обратитесь к официальному справочнику по vars и менее краткому справочнику от 8th light .

0 голосов
/ 01 апреля 2019

Я думаю, что проблема в том, что вы объединяете значение функции и идентичность функции вместе.Это то, что делают многие другие языки, поэтому это естественно, когда вы изучаете Clojure.В вашем примере I имеет ссылку на себя и ищет метаданные из этой ссылки, которая возвращает nil.Затем вы создаете f, что совпадает с I, но с некоторыми метаданными.Поэтому, когда вы запускаете f, он ищет метаданные на I и возвращает nil.Определение f совсем не меняет I, оно просто создает новую вещь в терминах старой.Если вы хотите что-то изменить, вам нужно ввести тип ссылки, который вы можете изменить.Их несколько, но обычно для хранения функций вы бы использовали Var (см. Здесь для справки)

(defn i [] (meta i))
(i)     ;;=> nil
(alter-var-root #'i with-meta {:rr 5})
(i)     ;;=> {:rr 5}

Здесь мы определяем функцию в текущем пространстве имен, которая называетсяi, который просто возвращает свои собственные метаданные.Мы называем это, чтобы получить nil.Затем мы изменяем глобальную ссылку с некоторыми новыми метаданными и вызываем ее снова.

Если вам нужен пример с более лексической областью, вы можете использовать atom, как показано ниже:

(let [i (atom nil)
      f (fn [] (meta @i))]
  (reset! i f)
  (prn 'before '>> (@i))
  (swap! i with-meta {:rr 5})
  (prn 'after '>> (@i)))

Однако, кроме изучения того, как эти вещи сочетаются друг с другом, я не уверен, какова цель.Вероятно, плохая идея использовать эти структуры в реальной программе, которую вы планируете поддерживать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...