Совет/обсуждение анонимных «самореферентных» структур данных

Приношу извинения за любую ошибочную терминологию --Я новичок в компьютерных науках и знаю только 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")}

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

Мои основные вопросы:

  1. Действительно ли это полезно, или возникшая двусмысленность/сложность будут слишком велики? Я полагаю, что я не одинок в желании/использовании макроса такого типа. При чем тут чужой опыт? Вы используете что-то подобное? Вы нашли лучшие обходные пути? Есть ли причины, по которым чего-то подобного нет ни в одной библиотеке Clojure? Или есть что-то, чего я еще не видел?
  2. Существуют ли лучшие соглашения об именах, которые я мог бы использовать --в отличие от self-ishи this? Например, может быть, thisслишком загружен смыслом ООП, я не уверен, я в основном знаком только с Clojure.
  3. Я новичок в компьютерных науках, есть ли доступные и информативные ресурсы, связанные с такими вещами --Думаю, я бы назвал это анонимным самореферентным (может быть, лучше сказать рефлексивным? )структуры данных? Я пока не нашел ничего доступного и информативного.
  4. Есть ли лучший способ написать макрос self-ish? Выше я включил свою текущую версию, но я не могу отделаться от ощущения, что может быть более простой способ.
  5. У меня есть различные вопросы о том, что может быть «самой мудрой» деталью реализации.

    • Обход:Должен ли он быть сначала в ширину или в глубину? Если сначала в глубину, то предзаказ, постзаказ или по порядку? Прямо сейчас я считаю, что это предзаказ в глубину, что для меня имеет смысл, но, возможно, у него есть некоторые недостатки, которых я не заметил.
    • Проблемы с заказом:(См. здесь для связанного предыдущего моего вопроса)В пределах{}(т.е. карты, написанные от руки )невозможно правильно поддерживать порядок (выше 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вне контекста макроса, но может ли это когда-либо быть полезным?

Спасибо за внимание, любая помощь приветствуется:)

8
задан Community 23 May 2017 в 12:29
поделиться