【问题标题】:How can I avoid evaluation of a lazy-seq inside a collection如何避免评估集合中的惰性序列
【发布时间】:2013-06-13 15:16:45
【问题描述】:

我正在尝试编写一个调试宏来打印出表达式及其值。如果我发送一个惰性序列,这会导致问题,因为如果我把它变成一个字符串(带 str),程序就会挂起。如果惰性序列位于顶层,则很容易检测到它:

(def foo (cycle [1 2]))
(= (type foo) clojure.lang.LazySeq) ;=> true

当然,如果它嵌套在另一个集合中,这将不起作用

(def bar (list (cycle [1 2])))
(= (type bar) clojure.lang.LazySeq) ;=> false

要解决这个问题,我需要以下两件事之一:

1:一个检查集合的函数,看看它是否包含嵌套在某处的惰性序列。

2:将集合转换为字符串而不评估嵌套惰性序列的函数,如下所示:

(str2 {:inf (cycle [1 2])}) => "{:inf #clojure.lang.LazySeq@e9383}"

使用 Michał Marczyk 的回答我想出了这个宏:

(defmacro dbg-print [& rest]
  "Print out values or expressions in context"
  `(let [lazy-take# 5 ;when printing lazy-seq, how many elements to print
         symb-str# (map str '~rest)
         symb-evl# (reverse
                    (binding [*print-length* 10] 
                      (loop [coll# (list ~@rest) retur# '()]
                        (if (not (empty? coll#))
                          (recur (rest coll#) (cons (pr-str (first coll#)) retur#))
                          retur#))))
         pairs# (map #(str %1 %2 %3 %4) symb-str# (repeat ":") symb-evl# (repeat " "))
         str# (reduce str pairs#)]
     (println (format "%s\n" str#))))

它是这样工作的:

(dbg-print (+ 1 3) (cycle [1 2])) ;=> (+ 1 3):4 (cycle [1 2]):(1 2 1 2 1 2 1 2 1 2 ...) 

并且可以处理嵌套的惰性序列:

(dbg-print (list (cycle [1 2]))) ;=> (list (cycle [1 2])):((1 2 1 2 1 2 1 2 1 2 ...)) 

【问题讨论】:

  • 一个实用的反问:你为什么要调试打印一个无限的惰性序列?如果您不知道无穷大,那可能是错误。如果您确实知道无穷大,那么调试的更有趣的部分是您从中获取一些东西的地方(例如 (dbg-macro (first (partition-by (comp = second) inf-seq)))) 。只是说像您的lazy-take#5这样的限制会让我更担心在我进行调试时会在我的思维中产生错误,因为调试输出不会代表程序中处理的数据。
  • 嗯,您可以通过从中获取一些内容来了解​​它包含的内容,但最好能立即了解结构。例如,我有一个惰性序列,它意外地包含在一个列表中。如果我采用该列表中的第一个,程序将崩溃。总体而言,最好不要让宏崩溃,因为您不小心给它喂了一个惰性序列。

标签: macros clojure lazy-evaluation


【解决方案1】:

您可以将内置 Vars *print-length* 和 *print-level* 与 pr / print 系列函数一起使用(包括 pr-str,如果您想将字符串表示形式作为返回值,而不是打印出来):

(binding [*print-length* 3
          *print-level*  3]
  (prn ((fn explode []
          (repeatedly #(repeatedly explode))))))

打印出来

(((# # # ...) (# # # ...) (# # # ...) ...) ((# # # ...) (# # # ...) (# # # ...) ...) ((# # # ...) (# # # ...) (# # # ...) ...) ...)

其中#s 表示数据结构的那些部分由于下降超过*print-level* 而被省略,...s 表示数据结构的那些部分由于延伸超过*print-length* 而被省略。

另外两个例子,打印了一些实际数据:

user> (binding [*print-length* 10]
        (prn (cycle [1 2 3])))
(1 2 3 1 2 3 1 2 3 1 ...)

user> (binding [*print-level* 10]
        (prn ((fn step [i]
                (lazy-seq (list i (step (inc i)))))
              0)))
(0 (1 (2 (3 (4 (5 (6 (7 (8 (9 #))))))))))

最后,一个返回字符串的例子:

user> (binding [*print-length* 2
                *print-level*  2]
        (prn-str ((fn explode []
                    (repeatedly #(repeatedly explode))))))
"((# # ...) (# # ...) ...)\n"

此功能记录在相关变量的文档字符串中,请参阅 (doc *print-length*) 和 (doc *print-level*)。

【讨论】:

    【解决方案2】:

    我想到了几种方法:

    • 使用 prewalk 遍历结构,并用占位符替换惰性序列。
    • 使用 prewalk 遍历序列,并递归地将 take 10 应用于每个集合。
    • 扩展 Clojure.lang.LazySeq 的 print-method multi 方法以根据您的选择打印它们。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-29
      • 1970-01-01
      • 2014-02-08
      相关资源
      最近更新 更多