1

我正在尝试从我继承的一些迭代 Java 库代码中创建一个 Clojure seq。基本上,Java 代码所做的是使用解析器从文件中读取记录,将这些记录发送到处理器并返回结果的 ArrayList。在 Java 中,这是通过调用 parser.readData(),然后调用 parser.getRecord() 来获取记录,然后将该记录传递给 processor.processRecord() 来完成的。对 parser.readData() 的每次调用都返回一条记录,如果没有更多记录,则返回 null。Java中很常见的模式。

所以我在 Clojure 中创建了这个 next-record 函数,它将从解析器中获取下一条记录。

(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)))))

我可以创建一个解析器和处理器,并执行类似 (take 5 (datamap-seq parser processor)) 之类的操作,这给了我一个惰性序列。正如预期的那样,获得该 seq 的(第一个)只实现一个元素,执行 count 实现所有元素,等等。这正是我对惰性 seq 所期望的行为。

当然,当有很多记录时,我最终会遇到 StackOverflowException。所以我的下一个尝试是使用循环递归来做同样的事情。

(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 results (datamap-seq parser processor)) 定义它给了我一个惰性序列并且没有实现任何元素。但是,一旦我做任何其他事情(例如(第一个结果)),它就会强制实现整个 seq。

谁能帮助我理解我在第二个函数中出错的地方,使用循环递归导致它实现整个事情?

更新:

我已经从异常中仔细查看了堆栈跟踪,并且堆栈溢出异常是从 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))))))

我真的不明白为什么删除会导致问题,但是当我把它从这个功能中取出时,它一切正常(我现在正在其他地方删除空列表)。

4

2 回答 2

4

loop/recur在循环表达式中循环,直到递归用完。在它周围添加一个惰性序列不会阻止这种情况。

您第一次尝试使用lazy-seq / cons 应该已经按照您的意愿工作,没有堆栈溢出。我现在无法发现它的问题是什么,尽管它可能在代码的 java 部分。

于 2012-09-14T16:06:43.113 回答
2

除了 Joost 的回答,我将在这里发布。这段代码:

(定义整数 [开始]
  (惰性序列
    (缺点
      开始
      (整数(inc start)))))

如果我这样做,将不会抛出 StackOverflowExceptoin:

(take 5 (drop 1000000 (integers)))

编辑:

当然,更好的方法是(iterate inc 0). :)

编辑2:

我将尝试解释一下lazy-seq是如何工作的。lazy-seq是一个返回类似 seq 的对象的宏。再加上在被要求之前没有意识到它的第二个参数的缺点,你会变得懒惰。

现在看看LazySeq 类是如何实现的。LazySeq.sval触发下一个值的计算,该值返回另一个“冻结”惰性序列实例。方法LazySeq.seq甚至更好地展示了这个概念背后的机制。请注意,要完全实现序列,它使用 while 循环。它本身意味着堆栈跟踪的使用仅限于返回另一个 LazySeq 实例的短函数调用。

我希望这有任何意义。我描述了我可以从源代码中推断出的内容。如果我犯了任何错误,请告诉我。

于 2012-09-14T16:14:40.053 回答