【问题标题】:Can an echo program in Clojure be built with lazy infinite sequences?Clojure 中的 echo 程序可以用惰性无限序列构建吗?
【发布时间】:2017-12-09 00:19:08
【问题描述】:

以下面的程序为例:

(defn echo-ints []
  (doseq [i (->> (BufferedReader. *in*)
                 (line-seq)
                 (map read-string)
                 (take-while integer?))]
    (println i)))

这个想法是提示用户输入,然后如果它是一个整数则回显它。然而,在这个特定的程序中,几乎每一秒的输入都不会立即被回显。相反,程序将在一次处理两个输入之前等待额外的输入。
大概这是幕后发生的一些性能调整的结果。但是在这种情况下,我真的很想有一个即时反馈循环。有没有一种简单的方法可以做到这一点,或者程序的逻辑是否必须进行重大改变?
(这里的主要动机是将无限的用户输入序列传递给另一个函数f,该函数将惰性序列转换为其他惰性序列。如果我编写了某种while循环,我将无法使用f .)

【问题讨论】:

  • (read-line) 也会发生这种情况吗?

标签: clojure echo lazy-sequences


【解决方案1】:

将惰性与副作用(在这种情况下为打印)混合通常并不好,因为大多数序列函数都有内置优化,会导致意想不到的效果,同时在功能上仍然正确。

这是一篇好文章:https://stuartsierra.com/2015/08/25/clojure-donts-lazy-effects

您尝试做的似乎很适合 core.async 频道。我认为问题是“用户输入流”而不是“用户输入的无限序列”,并且“f 将惰性序列转换为惰性序列”变成了“f 将流转换为另一个流”。这将允许您将 f 编写为可以任意组合的转换器。

【讨论】:

    【解决方案2】:

    我会这样做。请注意,我们使用spyxspyxx from the Tupelo library 来显示一些结果。

    首先,用罐头测试数据写一个简单的版本:

    (ns tst.demo.core
      (:use tupelo.test)
      (:require
        [tupelo.core :as t] )
      (:import [java.io BufferedReader StringReader]))
    (t/refer-tupelo)
    
    (def user-input
     "hello
      there
      and
      a
      1
      and-a
      2
      and
      a
      3.14159
      and-a
      4
      bye" )
    
    (defn echo-ints
      [str]
      (let [lines (line-seq (BufferedReader. (StringReader. str)))
            data  (map read-string lines)
            nums  (filter integer? data) ]
        (doseq [it data]
          (spyxx it))
        (spyx nums)))
    (newline)
    (echo-ints user-input)
    

    这给了我们结果:

    it => <#clojure.lang.Symbol hello>
    it => <#clojure.lang.Symbol there>
    it => <#clojure.lang.Symbol and>
    it => <#clojure.lang.Symbol a>
    it => <#java.lang.Long 1>
    it => <#clojure.lang.Symbol and-a>
    it => <#java.lang.Long 2>
    it => <#clojure.lang.Symbol and>
    it => <#clojure.lang.Symbol a>
    it => <#java.lang.Double 3.14159>
    it => <#clojure.lang.Symbol and-a>
    it => <#java.lang.Long 4>
    it => <#clojure.lang.Symbol bye>
    
    nums => (1 2 4)
    

    所以,我们看到它有效并为我们提供了我们想要的数字。

    接下来,编写一个循环版本。当我们的测试数据用完时,我们让它优雅地终止。

    (defn echo-ints-loop
      [str]
      (loop [lines (line-seq (BufferedReader. (StringReader. str)))]
        (let [line      (first lines)
              remaining (rest lines)
              data      (read-string line)]
          (when (integer? data)
            (println "found:" data))
          (when (not-empty? remaining)
            (recur remaining)))))
    (newline)
    (echo-ints-loop user-input)
    
    found: 1
    found: 2
    found: 4
    

    接下来,我们编写一个无限循环来读取键盘。你需要用键盘上的 CRTL-C 来终止这个:

    (ns demo.core
      (:require [tupelo.core :as t])
      (:import [java.io BufferedReader StringReader]))
    (t/refer-tupelo)
    
    
    (defn echo-ints-inf
      []
      (loop [lines (line-seq (BufferedReader. *in*))]
        (let [line      (first lines)
              remaining (rest lines)
              data      (read-string line)]
          (when (integer? data)
            (println "found:" data))
          (when (not-empty? remaining)
            (recur remaining)))))
    
    (defn -main []
      (println "main - enter")
      (newline)
      (echo-ints-inf))
    

    我们手动运行它:

    ~/clj > lein run
    main - enter
    hello
    there
    1
    
    found: 1
    and
    a
    2
    found: 2
    and-a
    3
    found: 3
    further more
    4
    found: 4
    ^C
                                                                                                                                                                          ~/clj > 
    ~/clj > 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-09-23
      • 1970-01-01
      • 2011-06-26
      • 2013-11-28
      • 2010-12-08
      • 2014-06-17
      • 1970-01-01
      相关资源
      最近更新 更多