【问题标题】:Iterator blocks in Clojure?Clojure 中的迭代器块?
【发布时间】:2011-02-14 18:12:51
【问题描述】:

我正在使用clojure.contrib.sql 从 SQLite 数据库中获取一些记录。

(defn read-all-foo []
  (with-connection *db*
    (with-query-results res ["select * from foo"]
       (into [] res))))

现在,我真的不想在从函数返回之前实现整个序列(即我想让它保持惰性),但是如果我直接返回 res 或者将它包装成某种惰性包装器(例如我想对结果序列做一定的map转换),SQL相关的绑定会在我返回后被重置并关闭连接,所以实现序列会抛出异常。

如何将整个函数封装在闭包中并返回一种迭代器块(如 C# 或 Python 中的 yield)?

或者还有其他方法可以从此函数返回惰性序列吗?

【问题讨论】:

    标签: clojure lazy-evaluation lazy-sequences


    【解决方案1】:

    with-query-results 返回的resultset-seq 可能已经像您将要得到的那样懒惰了。正如你所说,只要把手打开,懒惰只会起作用。没有办法解决这个问题。如果数据库句柄已关闭,则无法从数据库中读取。

    如果您需要在句柄关闭后进行 I/O 并保留数据,则打开句柄,快速 slurp(打败懒惰),关闭句柄,然后处理结果。如果您想遍历某些数据而不一次将其全部保存在内存中,请打开句柄,获取数据的惰性序列,doseq 覆盖它,然后关闭句柄。

    所以如果你想对每一行做一些事情(为了副作用)并丢弃结果而不将整个结果集吃到内存中,那么你可以这样做:

    (defn do-something-with-all-foo [f]
      (let [sql "select * from foo"]
        (with-connection *db*
          (with-query-results res [sql]
            (doseq [row res]
              (f row))))))
    
    user> (do-something-with-all-foo println)
    {:id 1}
    {:id 2}
    {:id 3}
    nil
    
    ;; transforming the data as you go
    user> (do-something-with-all-foo #(println (assoc % :bar :baz)))
    {:id 1, :bar :baz}
    {:id 2, :bar :baz}
    {:id 3, :bar :baz}
    

    如果您希望您的数据长期存在,那么您不妨使用上面的 read-all-foo 函数将其全部吞下(从而克服懒惰)。如果您想转换数据,那么在您获取所有数据后map 覆盖结果。那时您的数据将全部存储在内存中,但 map 调用本身和您的获取后数据转换将是惰性的。

    【讨论】:

      【解决方案2】:

      实际上可以在惰性序列中添加“终止副作用”,在第一次使用整个序列时执行一次:

      (def s (lazy-cat (range 10) (do (println :foo) nil)))
      
      (first s)
      ; => returns 0, prints out nothing
      
      (doall (take 10 s))
      ; => returns (0 1 2 3 4 5 6 7 8 9), prints nothing
      
      (last s)
      ; => returns 9, prints :foo
      
      (doall s)
      ; => returns (0 1 2 3 4 5 6 7 8 9), prints :foo
      ; or rather, prints :foo if it it's the first time s has been
      ; consumed in full; you'll have to redefine it if you called
      ; (last s) earlier
      

      不过,我不确定我是否会使用它来关闭数据库连接——我认为最好不要无限期地保持数据库连接并将关闭连接的调用放在你的懒惰结束时结果序列不仅会保持连接的时间超过严格必要的时间,而且还会导致您的程序由于不相关的原因而失败而从未关闭连接的可能性。因此,对于这种情况,我通常只会吞下所有数据。正如 Brian 所说,您可以将其全部存储在未处理的地方,而不是懒惰地执行任何转换,所以只要您不试图将一个非常庞大的数据集拉入一个块中,您应该没问题。

      但是我不知道你的确切情况,所以如果从你的角度来看它是有意义的,你绝对可以在你的结果序列的尾部调用一个连接关闭函数。正如 Michiel Borkent 指出的那样,如果您想这样做,您将无法使用 with-connection

      【讨论】:

        【解决方案3】:

        我以前从未将 SQLite 与 Clojure 一起使用,但我的猜测是 with-connection 在评估其主体时会关闭连接。因此,如果您想保持连接打开,则需要自己管理连接,并在阅读完您感兴趣的元素后将其关闭。

        【讨论】:

        • 这是我想做的,但我希望它由迭代器块闭包(或其他实现惰性序列接口的闭包形式)自动处理。
        【解决方案4】:

        没有办法在with-connectionwith-query-results 的“顶部”创建函数或宏来增加惰性。当控制流离开 lexical 范围时,两者都分别关闭它们的 Connection 和 ResultSet。

        正如 Michal 所说,创建一个惰性 seq,惰性关闭其 ResultSet 和 Connection 是没有问题的。正如他所说,这不是一个好主意,除非你能保证序列最终完成。

        一个可行的解决方案可能是:

        (def *deferred-resultsets*)
        (defmacro with-deferred-close [&body]
          (binding [*deferred-resultsets* (atom #{})]
            (let [ret# (do ~@body)]
              ;;; close resultsets
              ret# ))
        (defmacro with-deferred-results [bind-form sql & body]
          (let [resultset# (execute-query ...)]
            (swap! *deferred-resultsets* conj resultset# )
            ;;; execute body, similar to with-query-results
            ;;; but leave resultset open
          ))
        

        这将允许例如保持结果集打开,直到当前请求完成。

        【讨论】:

          猜你喜欢
          • 2013-11-14
          • 2014-07-19
          • 2012-02-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-03
          • 2020-02-06
          相关资源
          最近更新 更多