我正在尝试使用我继承的一些迭代Java库代码创建一个Clojure seq。基本上,Java代码所做的是使用解析器从文件中读取记录,将这些记录发送到处理器并返回结果的ArrayList。在Java中,这是通过调用parser.readData(),然后是parser.getRecord()来获取记录,然后将该记录传递给processor.processRecord()。每次调用parser.readData()都会返回一条记录,如果没有更多记录,则返回null。 Java中非常常见的模式。
所以我在Clojure中创建了这个下一个记录函数,它将从解析器中获取下一个记录。
(defn next-record
"Get the next record from the parser and process it."
[parser processor]
(let [datamap (.readData parser)
row (.getRecord parser datamap)]
(if (nil? row)
nil
(.processRecord processor row 100))))
然后想法是调用这个函数并将记录累积到Clojure seq(最好是懒惰的seq)中。所以这是我的第一次尝试,只要没有太多记录,它就会很有效:
(defn datamap-seq
"Returns a lazy seq of the records using the given parser and processor"
[parser processor]
(lazy-seq
(when-let [records (next-record parser processor)]
(cons records (datamap-seq parser processor)))))
我可以创建一个解析器和处理器,并执行类似的操作(取5(datamap-seq解析器处理器)),这给了我一个懒惰的seq。正如预期的那样,获得seq的(第一个)只能实现一个元素,count会实现所有这些,等等。这就是我期望从懒惰的seq中获得的行为。
当然,当有很多记录时,我最终会遇到StackOverflowException。所以我的下一次尝试是使用loop-recur来做同样的事情。
(defn datamap-seq
"Returns a lazy seq of the records using the given parser and processor"
[parser processor]
(lazy-seq
(loop [records (seq '())]
(if-let [record (next-record parser processor)]
(recur (cons record records))
records))))
现在以相同的方式使用它并使用(def结果(datamap-seq解析器处理器))来定义它给了我一个懒惰的seq并且没有实现任何元素。但是,只要我做了其他任何事情(第一次结果),它就会强制实现整个seq。
任何人都可以帮我理解在第二个函数中我出错的地方使用loop-recur导致它实现整个事情吗?
更新:
我看了一下异常的堆栈跟踪,并且从一个Java类抛出了堆栈溢出异常。但是只有当我拥有像这样的datamap-seq函数时才会发生这种情况(我上面发布的函数确实有效):
(defn datamap-seq
"Returns a lazy seq of the records using the given parser and processor"
[parser processor]
(lazy-seq
(when-let [records (next-record parser processor)]
(cons records (remove empty? (datamap-seq parser processor))))))
我真的不明白为什么删除会导致问题,但是当我把它从这个功能中删除时,一切正常(我现在正在其他地方删除空列表)。
答案 0 :(得分:4)
在循环表达式中循环/重复循环,直到递归用完为止。在它周围添加一个懒惰的seq不会阻止它。
你对lazy-seq / cons的第一次尝试应该已经可以正常工作了,没有堆栈溢出。我现在无法发现它的问题是什么,尽管它可能在代码的java部分。
答案 1 :(得分:2)
我会在这里发布Joost的回答。这段代码:
(defn integers [start] (lazy-seq (cons start (integers (inc start)))))如果我这样做,
将不会抛出StackOverflowExcepto:
(take 5 (drop 1000000 (integers)))
编辑:
当然,更好的方法是(iterate inc 0)
。 :)
EDIT2:
我会试着解释一下laq-seq的工作原理。 lazy-seq是一个返回类似seq的对象的宏。结合cons并且实现它的第二个参数,直到它被请求为止,你会得到懒惰。
现在看一下LazySeq class的实施方式。 LazySeq.sval
触发计算下一个值,该值返回“冻结”惰性序列的另一个实例。方法LazySeq.seq甚至更好地展示了概念背后的机制。请注意,为了完全实现序列,它使用while循环。它本身意味着堆栈跟踪使用仅限于返回LazySeq的另一个实例的短函数调用。
我希望这是有道理的。我描述了我可以从源代码中推断出什么。如果我犯了任何错误,请告诉我。