首页 > Clojure with-open 以及lazy-seq 读取 问题

Clojure with-open 以及lazy-seq 读取 问题

下面是一个github 读取一段base64加密的文件,并将解码的文件写入到一个随机的临时文件 。
入口函数是:(read-tran-file)

(defn tran-b64 [f s]
  (when-let [b (String. (b64/decode (.getBytes s)))]
    (with-open [w (io/writer f :append true)]
      (.write w b)
      )
    )
  )

(defn gen-tmp-file []
  "gen temp file"
  (str
    (System/getProperty "java.io.tmpdir")
    (System/getProperty "file.separator")
    (java.util.UUID/randomUUID) ".tmp")
  )



(defn tran [f x]
    (with-open [rdr (io/reader x)]
      (map (partial tran-b64 f) (doall (line-seq rdr)))
      )
  )

(defn read-tran-file []
  (let [f (gen-tmp-file)]
    (tran f "https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt")
    )
  )

请忽略(tran-b64)中反复打开流的动作。

那么问题来了:

(defn tran [f x]
    (with-open [rdr (io/reader x)]
      (map (partial tran-b64 f) (doall (line-seq rdr)))
      )
  )

这个函数的with-open中,(map (partial tran-b64 f) (doall (line-seq rdr))) 后面还加上其他函数执行,如:

(map (partial tran-b64 f) (doall (line-seq rdr)))
(println f)

都会导致(map (partial tran-b64 f) (doall (line-seq rdr))) 这一行代码不执行。
即使使用(do (map *) (println *)) 也不行。

到底这个懒序列(lazy-seq)是使用doall进行读取吗?还是使用dorun?
我看了doall函数调用的是dorun,而dorun使用的next方法。
那么这里又有一个问题<<clojure编程乐趣>>书中说的lazy-seq需要使用rest进行遍历,而不建议使用next。

问题类表:
1.with-open 为什么不能执行第2个表达式?
2.lazy-seq 使用什么方式读取比较好?


懒序列是 Clojure 的关键特征之一。它的使用原则是:尽可能让序列保持为懒序列,直到真正需要结果的时候。

不鼓励用懒序列来包含副作用的操作。良好的代码风格,最好用 doseq 来代替 map, for 等序列操作符。

clojure(doseq [line (line-seq rdr)]
  (tran-b64 f))

让代码可读性更高,将副作用明确出来,也可以在 doseq 中增加新的副作用操作,例如 (println f)

如果非要用 map 操作来执行,应该是将 dorun 放在整个懒序列外面,而不是强制将line-seq实化:

clojure(dorun (map (partial ran-b64 f) (line-seq rdr)))

懒序列实质上是延迟执行的程序,以数据的外表存在,是“程序即数据”的极好例子。对它的最佳使用方法,是将操作(最好是无副作用的代码)用序列函数处理后重新成为数据。仅仅在输入输出时引入副作用。因此:

clojure(defn tran-b64 [s]
  (String. (b64/decode (.getBytes s))))

(defn gen-tmp-file []
  "gen temp file"
  (str
    (System/getProperty "java.io.tmpdir")
    (System/getProperty "file.separator")
    (java.util.UUID/randomUUID) ".tmp"))

(defn tran [filename x]
  (with-open [rdr (io/reader x)
              wtr (io/writer filename :append true)]
      (doseq [line (map tran-b64 (line-seq rdr))]
         (.write wtr line))))

(defn read-tran-file []
  (let [filename (gen-tmp-file)]
    (tran filename "https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt")))

PS: f 在 clojure 程序中习惯表示为一个函数,因此我都改成了 filenameread-tran-file, gen-tmp-file 函数名都不恰当,不能反映其真正工作,read-tran-file函数实际上写了文件,gen-tmp-file 实际上是 tmp-filename

【热门文章】
【热门文章】