【问题标题】:Clojure lazy-seq over Java iterative codeClojure lazy-seq over Java 迭代代码
【发布时间】:2012-09-07 19:05:50
【问题描述】:

我正在尝试从我继承的一些迭代 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)))))

我可以创建一个解析器和处理器,然后执行类似 (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)) 定义它给了我一个惰性序列并且没有实现任何元素。然而,只要我做任何其他事情,比如(第一个结果),它就会强制实现整个序列。

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

更新:

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

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

【问题讨论】:

    标签: clojure iteration


    【解决方案1】:

    我将在此处发布 Joost 的回答。这段代码:

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

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

    (take 5 (drop 1000000 (integers)))
    

    编辑:

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

    EDIT2:

    我将尝试解释一下lazy-seq 的工作原理。 lazy-seq 是一个返回类 seq 对象的宏。结合cons 直到它被请求时才意识到它的第二个参数,你会变得懒惰。

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

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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 完全同意。向 Dave 提问,您能在此处粘贴堆栈跟踪吗?我认为您的延迟序列实施没有任何问题。
      • 感谢您的回复。当我看到 StackOverflowException 时,我立即跳到递归是问题所在,而没有真正查看堆栈跟踪。该异常实际上是由 Java 代码引发的,是在 Emacs 中多次运行但未清除状态的结果。但是现在我不明白你为什么说第一个代码不会导致堆栈溢出。这很容易解释还是您可以指出我的参考?
      • 也感谢您用循环/递归表达式解释我的错误想法。现在这很有意义。
      • @DaveKincaid 我发布了对lazy-seq 背后机制的解释,作为对我答案的修改(这里太长了,不适合)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-12-21
      • 1970-01-01
      • 1970-01-01
      • 2014-05-07
      • 1970-01-01
      • 2015-12-17
      • 2011-12-10
      相关资源
      最近更新 更多