对任何错误的术语表示歉意——我对计算机科学很陌生,而且我几乎只知道 Clojure(但我想我会说我非常了解)。
所以,我还没有对此进行大量研究,但我有时发现它在编写 Clojure 代码时很有用,以便能够从该数据结构中引用一些“我所在的任何数据结构的中间版本” (很像 a 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
函数有一个标准的类似 reduce 的累加器,它只是构建表单元素。它还有一个累加器堆栈,每次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")}
在人们可能真的想在数据中包含一些烘焙的东西时,它也可能很有用,而不是使用某些函数即时导出。这些情况可能要少得多,而且我认为当您可以使用漂亮的干净函数来操作数据时,不必要地纠缠数据是一个坏主意。
我的主要问题:
- 这真的有用吗,还是会产生太多的歧义/复杂性?我想我并不孤单想要/使用这种类型的宏。其他人在这里有什么经验?你用这样的东西吗?你找到更好的解决方法了吗?是否有任何 Clojure 库中没有类似内容的原因?或者有什么我还没有看到的?
- 有没有更好的命名约定我可以使用——而不是
self-ish
andthis
?比如可能this
是太加载了OOP的意思,我不确定,我基本上只熟悉Clojure。 - 我对计算机科学很陌生,是否有与此类事物相关的可访问且信息丰富的资源——我想我会称之为匿名自我引用(也许反身是一个更好的词?)数据结构?我还没有发现任何既平易近人又信息丰富的东西。
- 有没有更好的方法来编写
self-ish
宏?上面,我已经包含了我当前的版本,但我无法摆脱可能有更简单方法的感觉。 我对什么可能是“最明智的”实施细节有各种疑问。
- 遍历:是广度优先还是深度优先?如果深度优先,是前序、后序还是有序?现在,我相信它是深度优先预购,这对我来说很有意义,但也许它有一些我没有注意到的缺点。
订单问题:(有关我之前的相关问题,请参见此处)在内部
{}
(即手写地图)中,如果不使用array-map
或sorted-map
- 换句话说,在 8 个地图条目以上,{}
用法是不可能正确维护订单(超过 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}
Serial:正如我所写,宏通过串行处理其元素来避免无限递归,类似于
let
,但这确实会产生潜在的奇怪行为。例如,在我上面的快速示例中,(reduce str this)
返回"ab"
因为this
在["a" "b"]
该步骤。也许有时创建某种无限的惰性序列会很有用?如果是这样,那将如何实施?- 映射条目:现在,映射条目被视为向量,但由于如何
this
在任何中间步骤调用,完全有可能nil
从“尚未”分配值的键中获取值。这就是为什么在我的第一个快速示例中,:c
最终映射到 3——因为中间有一个nil
对应于:c
,并且也被计算在内。你认为这值得修复吗? - 非宏实用程序:仅
self-indulge
在宏上下文之外使用是微不足道的,但这可能有用吗?
感谢阅读,任何帮助表示赞赏:)