Clojure中的迭代器块?

Clojure中的迭代器块?,clojure,lazy-evaluation,lazy-sequences,Clojure,Lazy Evaluation,Lazy Sequences,我正在使用clojure.contrib.sql从SQLite数据库中获取一些记录 (defn read-all-foo [] (with-connection *db* (with-query-results res ["select * from foo"] (into [] res)))) 现在,我真的不想在从函数返回之前实现整个序列(即,我想让它保持惰性),但是如果我直接返回res,或者将其包装成某种惰性包装(例如,我想对结果序列进行某种map转换),在我返回

我正在使用
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


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

我以前从未将SQLite与Clojure一起使用过,但我猜with connection会在对连接体求值后关闭连接。因此,如果您想保持连接的打开状态,您需要自己管理连接,并在您阅读完感兴趣的元素后关闭连接。

带有查询结果的
返回的
结果集seq
可能已经像您将要得到的那样懒惰了。正如你所说,只有把手打开,懒惰才会起作用。这是没办法的。如果数据库句柄已关闭,则无法从数据库中读取

如果您需要在句柄关闭后执行I/O并保留数据,那么请打开句柄,快速将其读入(克服惰性),关闭句柄,然后处理结果。如果您想迭代某些数据而不同时将其全部保存在内存中,则打开句柄,获取数据上的惰性seq,
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
函数将其全部删除(从而克服懒惰)。如果要转换数据,则在获取所有数据后,将其映射到结果上。此时,您的数据将全部存储在内存中,但
映射调用本身和后获取数据转换将是延迟的。

事实上,可以向延迟序列添加“终止副作用”,在第一次使用整个序列时执行一次:

(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
我不确定我是否会用它来关闭一个DB连接,但是--我认为最好的做法是不要无限期地保持DB连接,并且将连接关闭调用放在延迟结果序列的末尾不仅会保持连接的时间超过严格必要的时间,但同时也打开了这样一种可能性,即您的程序在不关闭连接的情况下,会因不相关的原因而失败。因此,对于这种情况,我通常只会在所有数据中发出咕噜声。正如Brian所说,您可以将其全部存储在未经处理的地方,而不是懒洋洋地执行任何转换,因此只要您不想在一个块中拉入一个真正巨大的数据集,您就应该没事


但是我不知道你的具体情况,所以如果从你的角度来看有意义的话,你可以在结果序列的末尾调用一个连接关闭函数。正如Michiel Borkent所指出的,如果你想这样做,你就不能将
与连接一起使用

没有办法在带有连接的
和带有查询结果的
上创建一个函数或宏来增加懒散性。当控制流离开词法范围时,两者都分别关闭连接和结果集

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

可行的解决办法可能是:

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

例如,这将允许在当前请求完成之前保持结果集的打开状态。

这就是我想要做的,但我希望它由迭代器块闭包(或实现惰性seq接口的某种其他形式的闭包)自动处理。