对于任何错误的术语表示歉意 - 我对计算机科学很陌生,而且我几乎只知道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
函数有一个标准的类似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")}
在人们可能真的希望将某些内容包含在数据中,而不是使用某些函数动态派生时,它也可能很有用。这些情况可能非常罕见,我认为当你可以使用干净的函数操作它时,不必要地纠缠数据是个坏主意。
我的主要问题:
self-ish
和this
?例如,也许this
太满了OOP意思,我不确定,我基本上只熟悉Clojure。self-ish
宏?上面,我已经包含了我目前的版本,但我不能感觉可能有一种更简单的方式。关于什么是最明智的"我有各种各样的问题。实施细节。
订购问题 :( See here for a related previous question of mine)在{}
内(即手工编写的地图),无法正确维护订单(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
的方式,因此完全有可能获得{{1来自具有"尚未"的键的值被分配了一个值。这就是为什么在我的第一个快速示例中,nil
最终映射到3 - 因为中间有一个:c
对应nil
,并且也被计算在内。你认为这有必要修理吗?:c
是微不足道的,但这可能有用吗?感谢阅读,感谢任何帮助:)
答案 0 :(得分:2)
在方案中,这是使用(letrec ...)
完成的,它允许您引用结构本身内部的数据结构的名称。因此,如果您想要定义自己的方法,那么实现它可能更有意义。你可以使用Is it possible to create circular references in Clojure?
letrec的一个优点是它具有用户指定的名称(如果您的宏嵌套,则this
被遮蔽)。
[编辑删除对类型的评论,因为我不理解您的宏。]
[更新]此外,您可能对clojure第8.5.1节中的回指的讨论感兴趣
答案 1 :(得分:2)
这种方法对我来说“感觉”有点不对,虽然我不太清楚为什么。也许我不喜欢地图构造依赖于秩序的想法....
虽然说这是一个非常容易编写的宏,但你实际上想要扩展的东西:
(let [this {}
this (assoc this :a 1)
this (assoc this :b (+ (this :a) 3))]
this)
因此,适当的宏将是(对于地图案例):
(defmacro self-ish [bindings]
`(let [~'this {}
~@(mapcat
#(do `(~'this (assoc ~'this ~@%)) )
(partition 2 bindings) )]
~'this))
(self-ish [:a 1
:b (+ (this :a) 3)])
=> {:b 4, :a 1}
请注意,由于地图绑定表单是无序的,因此我将绑定形式设为矢量。
仍然不确定我有多喜欢这个成语。我首选的方法是定义一个带有let形式的结构,并为临时计算提供有意义的名称,例如:
(let [meaningful-foo (something)
meaningful-bar (something-else)]
{:foo meaningful-foo
:bar meaningful-bar
:baz (some-calculation meaningful-foo meaningful-bar)})