Приношу извинения за любую ошибочную терминологию --Я новичок в компьютерных науках и знаю только Clojure (, но я бы сказал, что знаю его достаточно хорошо ).
Итак, я не проводил много исследований по этому вопросу, но иногда я находил полезным при написании кода Clojure иметь возможность ссылаться на некоторую «промежуточную версию любой структуры данных, в которой я нахожусь» внутри этой структуры данных (очень похоже наlet
). Краткие примеры:
=> (self-ish {:a 10
:b (inc (this :a))
:c (count (vals this))})
=> {:a 10, :b 11, :c 3}
=> (self-ish ["a" "b" (reduce str this)])
=> ["a" "b" "ab"]
//Works in any nested bits too
=> (self-ish [1 2 3 [4 5 (first this)] 6 [7 [8 (cons (second this) (nth this 3))]]])
=> [1 2 3 [4 5 1] 6 [7 [8 (2 4 5 1)]]]
Идея состоит в том, что структура строится постепенно и на любом этапе имеет возможность ссылаться на текущую промежуточную структуру как this
. Вот код моей текущей реализации:
//Random straightforward but helpful definitions
(defn map-entry? [obj]
(instance? clojure.lang.AMapEntry obj))
(def Map clojure.lang.IPersistentMap)
(def Vector clojure.lang.IPersistentVector)
(def List clojure.lang.IPersistentList)
(def Set clojure.lang.IPersistentSet)
(defn append
[x coll]
(if-not coll x
(condp instance? coll
Map (if (empty? x) coll
(assoc coll (first x) (second x)))
Vector (conj coll x)
Set (conj coll x)
List (apply list (concat coll [x]))
(concat coll [x]))))
(defn build-this
[acc-stack acc]
(->> (cons acc acc-stack)
(drop-while list?)
(drop-while (every-pred empty? identity))
(reduce append)))
(defn self-indulge
[acc-stack acc form]
;//Un-comment the following to see it print intermediate stages of processing
#_(println "this:" (build-this acc-stack acc) "\n at:" form)
(append (cond
(coll? form) (reduce (partial self-indulge (cons acc acc-stack))
(if (map-entry? form) []
(empty form))
form)
(= (quote this) form) (build-this acc-stack acc)
:else form)
acc))
(defmacro self-ish
[form]
(self-indulge () nil form))
Функция append
добавляет элемент в коллекцию и возвращает коллекцию того же типа. Функция self-indulge
имеет стандартное сокращение -, подобное аккумулятору, которое просто создает элементы формы. У него также есть стек-аккумулятор, который становится длиннее каждый раз, когда self-indulge
повторяется сам по себе. Смысл этого в том, чтобы отслеживать другие «более высокие» аккумуляторы, так что this
будет всей структурой, а не только локальной частью. Макрос self-ish
прекрасно завершаетself-indulge
(который называет себя с помощью partial
, поэтому он не может носить макроштаны ).
Изменить :пример варианта использования Для меня этот макрос предназначен для повышения читабельности кода, а не для расширения функциональности. Я нашел это полезным в тех случаях, когда у меня есть написанные от руки -структуры с частично избыточными данными --или, может быть, лучше использовать слово «зависимый». Может быть проще читать код и видеть, что содержат разные части структуры данных,и это также может быть полезно, если я изменяю значения данных в одной части структуры и хочу, чтобы это изменение отражалось в других частях. Например:
=> (self-ish {:favorite-books (list "Crime and Punishment" "Mrs. Dalloway")
:favorite-things (list* "Ice Cream" "Hammocks" (this :favorite-books)})
=> {:favorite-things ("Ice Cream" "Hammocks" "Crime and Punishment" "Mrs. Dalloway"),
:favorite-books ("Crime and Punishment" "Mrs. Dalloway")}
Это также может быть полезно в тех случаях, когда очень хочется включить в данные что-то запеченное, а не полученное на лету с использованием какой-либо функции. Эти случаи, вероятно, намного реже, и я думаю, что было бы плохой идеей запутывать данные без необходимости, когда вы могли бы просто иметь хорошие чистые функции, манипулирующие ими.
Мои основные вопросы:
self-ish
и this
? Например, может быть, this
слишком загружен смыслом ООП, я не уверен, я в основном знаком только с Clojure.self-ish
? Выше я включил свою текущую версию, но я не могу отделаться от ощущения, что может быть более простой способ.У меня есть различные вопросы о том, что может быть «самой мудрой» деталью реализации.
Проблемы с заказом:(См. здесь для связанного предыдущего моего вопроса)В пределах{}
(т.е. карты, написанные от руки )невозможно правильно поддерживать порядок (выше 8 записей карты )без использования array-map
илиsorted-map
--другими словами, более 8 записей карты, использование {}
небезопасно. Может быть, вместо рукописного -порядка макрос мог бы творить чудеса, чтобы обрабатывать элементы в каком-то «идеальном» порядке? Или, может быть, было бы лучше обернуть все карты внутри (array-map...)
вместо -приятного для глаз {}
?
//Showing maps with 9 entries failing
=> (self-ish {:a 1
:b (inc (this :a))
:c (inc (this :b))
:d (inc (this :c))
:e (inc (this :d))
:f (inc (this :e))
:g (inc (this :f))
:h (inc (this :g))
:i (inc (this :h))})
=> NullPointerException clojure.lang.Numbers.ops (Numbers.java:942)
//8 works fine
=> (self-ish {:a 1
:b (inc (this :a))
:c (inc (this :b))
:d (inc (this :c))
:e (inc (this :d))
:f (inc (this :e))
:g (inc (this :f))
:h (inc (this :g))})
=> {:h 8, :g 7, :f 6, :e 5, :d 4, :c 3, :b 2, :a 1}
Серийный номер:Как я уже написал, макрос избегает бесконечной рекурсии, обрабатывая свои элементы последовательно, подобно let
, но это приводит к потенциально странному поведению. Например, в приведенном выше быстром примере (reduce str this)
возвращает "ab"
, потому что this
равно ["a" "b"]
на этом шаге. Может быть, иногда было бы полезно вместо этого создать какую-то бесконечную ленивую последовательность? Если да, то как это можно реализовать?
this
может вызываться на любом промежуточном этапе, вполне возможно получить значение nil
из ключа, которому «еще не» присвоено значение. Вот почему в моем первом быстром примере :c
оказалось сопоставленным с 3 --, потому что промежуточно nil
соответствовало :c
, и это тоже было учтено. Как вы думаете, это требует исправления?self-indulge
вне контекста макроса, но может ли это когда-либо быть полезным?Спасибо за внимание, любая помощь приветствуется:)