1

Take the following program as an example:

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

The idea is to prompt the user for input and then echo it back if it's an integer. However, in this particular program almost every second input won't be echoed immediately. Instead the program will wait for additional input before processing two inputs at once.
Presumably this a consequence of some performance tweaks happening behind the scenes. However in this instance I'd really like to have an immediate feedback loop. Is there an easy way to accomplish this, or does the logic of the program have to be significantly altered?
(The main motivation here is to pass the infinite sequence of user inputs to another function f that transforms lazy sequences to other lazy sequences. If I wrote some kind of while-loop, I wouldn't be able to use f.)

Sebastian Oberhoff
  • 1,271
  • 1
  • 10
  • 16

2 Answers2

1

It is generally not good to mix lazyness with side-effect (printing in this case), since most sequence functions have built-in optimizations that cause unintended effects while still being functionally correct.

Here's a good write up: https://stuartsierra.com/2015/08/25/clojure-donts-lazy-effects

What you are trying to do seems like a good fit for core.async channels. I would think as the problem as 'a stream of user input' instead of 'infinite sequence of user inputs', and 'f transforms lazy sequences to lazy sequences' becomes 'f transform a stream into another stream'. This will allow you to write f as transducers which you can arbitrarily compose.

Hendrik Poernama
  • 413
  • 3
  • 14
-2

I would do it like the following. Note we use spyx and spyxx from the Tupelo library to display some results.

First, write a simple version with canned test data:

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

This gives us the results:

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)

So, we see that it works and gives us the numbers we want.

Next, write a looping version. We make it terminate gracefully when our test data runs out.

(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

Next, we write an infinite loop to read the keyboard. You need to terminate this one with CRTL-C at the keyboard:

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

And we run it manually:

~/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 > 
Alan Thompson
  • 29,276
  • 6
  • 41
  • 48