help--reading keyboard input from Clojure is surprisingly difficult

1,834 views
Skip to first unread message

Gregg Williams

unread,
Apr 8, 2011, 2:52:06 AM4/8/11
to Clojure
Having worked with Clojure for over a year, I can sympathize with
Clojure beginners (like myself) who find it extremely difficult to do
simple things with Clojure. (It took me weeks to get Clojure working
with Emacs, Swank, and the Clojure Debugging Toolkit, but I'm
persistent.)

Now this. I'm learning Spanish, and I wanted to whip up a simple
Clojure program to spit out randomly-generated vocabulary phrases,
then give me the answer when I hit ENTER from the keyboard. Simple ...
keyboard ... input. Simple, right?

After considerable research, reading all sorts of different solutions
(some of which dropped into Java) for this seemingly trivial task, I
wrote an earlier version of this (now cleaned-up) code:

----------------------------------------
(defn -main []
(println "1. generated test question goes here") ;randomized drill
item printed
(let [feedback (read-line)]
(print "2. input from keyboard is: '" feedback "'\n")
;; continues if user hits ENTER; to exit, type something
(if-let [result (not (= feedback ""))]
(print "3a. input was '" feedback "'--my signal to EXIT PROGRAM
\n")
(do
(println "3b. question answer goes here\n\n") ;answer, if given,
printed here
(recur)))))
----------------------------------------

*Couldn't* get it to work from the REPL within Emacs. More research.
Oh, Phil Hagelberg says that (read-line) is "simply not supported in
swank-clojure" (http://groups.google.com/group/clojure/browse_thread/
thread/bc49afd3986a41e3). Okay, I tried it with `lein run`. I got
something similar to its behavior within Emacs:

----------------------------------------
macscooter:drill gw$ lein run
1. generated test question goes here
2. input from keyboard is: ' nil '
3a. input was ' nil '--my signal to EXIT PROGRAM
macscooter:drill gw$
----------------------------------------

(The above output occurs without *any* keyboard input.)

Finally, I try a leiningen REPL, and I get the desired/expected
behavior:

----------------------------------------
drill.core=> (-main)
1. generated test question goes here
<--- keyboard input was ENTER key --GW
2. input from keyboard is: ' '
3b. question answer goes here


1. generated test question goes here
I give up <--- this is keyboard input --GW
2. input from keyboard is: ' I give up '
3a. input was ' I give up '--my signal to EXIT PROGRAM
nil
drill.core=>
----------------------------------------

I tried clojure-cake (briefly) but got some kind of error and haven't
pursued it just yet. I assume that running clojure completely manually
from the CLI will work, but honestly, I'd like to have some support
for interactivity and/or debugging this program, which could grow over
time.

I just want to practice Spanish! (Actually, I'd rather program
Clojure, but I don't like wasting my time trying to do elementary
things that SHOULD ... JUST ... WORK.) Can anybody suggest anything
that will enable me to write this simple program that any middle-
school student would find, well, basic if written in BASIC? Thanks.

Ken Wesson

unread,
Apr 8, 2011, 3:35:08 AM4/8/11
to clo...@googlegroups.com
On Fri, Apr 8, 2011 at 2:52 AM, Gregg Williams <gre...@innerpaths.net> wrote:
> Having worked with Clojure for over a year, I can sympathize with
> Clojure beginners (like myself) who find it extremely difficult to do
> simple things with Clojure. (It took me weeks to get Clojure working
> with Emacs, Swank, and the Clojure Debugging Toolkit, but I'm
> persistent.)
>
> Now this. I'm learning Spanish, and I wanted to whip up a simple
> Clojure program to spit out randomly-generated vocabulary phrases,
> then give me the answer when I hit ENTER from the keyboard. Simple ...
> keyboard ... input. Simple, right?
>
> After considerable research, reading all sorts of different solutions
> (some of which dropped into Java) for this seemingly trivial task, I
> wrote an earlier version of this (now cleaned-up) code:

...

> I just want to practice Spanish! (Actually, I'd rather program
> Clojure, but I don't like wasting my time trying to do elementary
> things that SHOULD ... JUST ... WORK.) Can anybody suggest anything
> that will enable me to write this simple program that any middle-
> school student would find, well, basic if written in BASIC? Thanks.

The whole JVM ecosystem seems built around client-server
communications, noninteractive jobs, and client-side GUI; not
client-side console interaction. It's easier to do something like this
using a Swing GUI with a read-only JTextArea holding the interaction
history above a one-line JTextField used to submit new input; stick an
ActionListener on the JTextField and it will get called when enter is
pressed in the field. Or you can use something like this to sugar it
up:

(defmacro text-line [input-var & body]
`(let [f# (javax.swing.JTextField.)]
(doto f
(setEditable true)
(setColumns 60)
(.addActionListener
(proxy [ActionListener] []
(actionPerformed [_]
(let ~(conj input-var `(.getText f#))
(.setText f# "")
~@body)))))))

Sample usage:

(let [field (text-line [line] (println line))]
...
(.add some-panel field java.awt.layout.BorderLayout/SOUTH))

Untested.

Meikel Brandmeyer

unread,
Apr 8, 2011, 5:51:43 AM4/8/11
to Clojure
Hi,

I put that into greet.clj.
--8<---8<---8<--
(def state (atom :running))

(println "Where to send the greetings?")

(while (= @state :running)
(let [guy (read-line)]
(if (pos? (count guy))
(println (str "Hello, " guy "!"))
(reset! state :stop))))
--8<---8<---8<--

And then start the session:
--8<---8<---8<--
PS C:\Documents and Settings\tfelad> java -jar clojure-1.2.0.jar
greet.clj
Where to send the greetings?
Gregg
Hello, Gregg!
Ken
Hello, Ken!
Meikel
Hello, Meikel!

--8<---8<---8<--

Just hitting enter after the last greeting shuts down the loop. Is
that what you intent?

Sincerely
Meikel

PS: BTW, the above “just worked.”

Meikel Brandmeyer

unread,
Apr 8, 2011, 6:01:44 AM4/8/11
to Clojure
Hi,

On 8 Apr., 08:52, Gregg Williams <greg...@innerpaths.net> wrote:

> I assume that running clojure completely manually
> from the CLI will work, but honestly, I'd like to have some support
> for interactivity and/or debugging this program, which could grow over
> time.

Ah. Ok. It's a tooling issue. What you can do (at least with
VimClojure, I suspect something similar should work with emacs) is:

1. Put your main loop into a function. And don't make it exit the
program at the end, but just return from the function.
2. Start a Repl manually.
3. Start a nailgun server on its own thread (VimClojure has some
convenience function for this, swank probably allows something
similar)
4. run your function
5. in parallel work in your editor, send stuff for evaluation, etc.
6. maybe return from the function, if you needed to redef the function
itself and not just some helper function
7. go to 4 as you wish

So you have both: script development plus interactive features of the
editor. Just don't use the built-in repl, because it simply doesn't
have a stdin, so there is no sense in reading from it.

Hope this helps.

Sincerely
Meikel

Armando Blancas

unread,
Apr 8, 2011, 10:52:05 AM4/8/11
to Clojure
> Can anybody suggest anything
> that will enable me to write this simple program that any middle-
> school student would find, well, basic if written in BASIC? Thanks.

Write your own read function to delegate to (read-line) or, in debug
mode, read the next line from some file; then keep various files for
specific runs of your program. Debug mode and the file to read can be
setup with dynamic binding to keep your code clean of extra args.

Rasmus Svensson

unread,
Apr 8, 2011, 2:29:57 PM4/8/11
to clo...@googlegroups.com
See this tread for why stdin is not directly available with lein:

http://groups.google.com/group/leiningen/browse_thread/thread/f9f9ed97eb8a2928/ccab95588ef50d05?lnk=gst&q=stdin

"This is currently impossible due to a bug in ant; it just swallows
stdin completely, and they seem to have no intention of fixing it."

// raek

Gregg Williams

unread,
Apr 8, 2011, 11:33:59 PM4/8/11
to Clojure
Thanks to all for your helpful replies. Mikel's greet.clj is something
that "just works," but it requires invoking Java through the command
line. If I go that route, the code that I wrote will work...won't it?
(I'll try.)

But look at the proposed solutions--for example, "It's easier to do
something like this using a Swing GUI with a read-only JTextArea
holding the interaction history above a one-line JTextField used to
submit new input; stick an ActionListener on the JTextField and it
will get called when enter is pressed in the field"! *How* does the
word "easier" belong in that sentence, when I all I want to do is read
input from the keyboard?!

My modest proposal:

I invite the Clojure community to deem this input-from-keyboard
problem to be a Clojure "Adoption Assassin" that is stifling Clojure
adoption by the larger programming community. There are other such
pain points, and they need to be solved.

WOULDN'T IT BE GREAT if we could solve them with a
"Clojure Starter Kit" that newcomers could just load
with their programs to get A MUCH BETTER STARTING
EXPERIENCE with the language?

If anybody wants to nominate a certain problem as a Clojure Adoption
Assassin, I'll be glad to keep track of them and keep the community
aware of them. You can email me at the address below.

Again, thanks for the help. If you think you have a solution to the
problem in my original post, please post it here. Don't consider this
reply to mean that I feel the thread is finished!

-----------------------
Gregg Williams, gregg AT-SIGN GettingClojure DOT-SIGN com
http://www.GettingClojure.com: because we're *all* still learning
Clojure!

Armando Blancas

unread,
Apr 9, 2011, 12:18:18 AM4/9/11
to Clojure
> My modest proposal: [snip]

Have you considered a grant from the National Science Foundation?
Dennis Ritchie is still around in what remains of Bell Labs; maybe he
could help us read from standard input.

Lee Spector

unread,
Apr 9, 2011, 9:46:18 AM4/9/11
to clo...@googlegroups.com

On Apr 8, 2011, at 11:33 PM, Gregg Williams wrote:

> ... *How* does the


> word "easier" belong in that sentence, when I all I want to do is read
> input from the keyboard?!
>
> My modest proposal:

> ...

I'm writing to support the sentiment that Gregg expressed here with regard to this issue, and also his broader proposal.

List members offered several clever and helpful workarounds in response to Gregg's post, along with pointers to the reasons for the current situation. Great!

But still, I will humbly submit that it's totally freakin' nutso that it should be so hard to do basic user interaction.

FWIW I looked back at materials I used to teach clojure last semester and saw that I presented some stuff based on read-line in the REPL and it must have worked in the environment that I was using at the time, which was Eclipse/CCW on a mac. But now I do remember that some students who were using this for simple text adventure games had problems, although I don't remember the details. They may have been using other platforms. In any event I will include the code that I shared with the students at that time below... yes, I know that some of you will think that I shouldn't have used eval, etc., but for the purpose I think it was fine.... and my view that stuff like this should work without clever workarounds in any Clojure environment that isn't labeled as broken.

-Lee

(ns commandline)

(defn read-and-do-line
"Get from the user and execute a command line without parentheses,
where the first thing on the line is the name of a function and the
rest of the line is arguments (which will not be evaluated)."
[]
(print "command: ")
(flush)
(let [line (read-line)
list-of-things (read-string (str "(" line ")"))]
(apply (eval (first list-of-things)) (rest list-of-things))))


(defn inc-and-print [number] (println (inc number))) ;; just an example command to call
(defn list-and-print [& args] (println (apply list args))) ;; another

(def in-loop (atom false)) ;; just to control the command loop below

(defn quit [] (reset! in-loop false)) ;; a function to get out of the loop

(defn command-loop []
(reset! in-loop true)
(loop []
(when @in-loop
(read-and-do-line)
(recur))))

;; 1:3 commandline=> (command-loop)
;; command: inc-and-print 3
;; 4
;; command: list-and-print :a :b :c 1 2 3
;; (:a :b :c 1 2 3)
;; command: quit
;; nil
;; 1:7 commandline=>

Sean Corfield

unread,
Apr 9, 2011, 6:18:49 PM4/9/11
to clo...@googlegroups.com
On Sat, Apr 9, 2011 at 6:46 AM, Lee Spector <lspe...@hampshire.edu> wrote:
> But still, I will humbly submit that it's totally freakin' nutso that it should be so hard to do basic user interaction.

I'm curious as to what percentage of developers are writing
console-based applications (in any language)?

What do the processes look like in other languages? How are those
processes different to what happens with Clojure?

With the following test program, t.clj...

(println "Testing...")
(let [a (read-line)]
(println "> " a))

...I can open up a REPL, do (load-file "t.clj") and it runs as
expected. I can also type clj run t.clj and it works just fine (can't
remember where I got that script from - it just wraps the java command
to start Clojure).

Now, I will concede that lein run t.clj does not work - and that does
seem a bit odd given that lein repl then (load-file "t.clj") works but
if it's a technical limitation with the run task, fair enough.

However, given the clj run command, I can add a #! line to my Clojure
scripts, make them execute and just run them:

#!/usr/bin/env clj run
(println "Testing...")
(let [a (read-line)]
(println "> " a))

Now I can just type ./t.clj and it works as expected...
--
Sean A Corfield -- (904) 302-SEAN
An Architect's View -- http://corfield.org/
World Singles, LLC. -- http://worldsingles.com/
Railo Technologies, Inc. -- http://www.getrailo.com/

"Perfection is the enemy of the good."
-- Gustave Flaubert, French realist novelist (1821-1880)

Lee Spector

unread,
Apr 9, 2011, 7:27:13 PM4/9/11
to clo...@googlegroups.com

On Apr 9, 2011, at 6:18 PM, Sean Corfield wrote:

> On Sat, Apr 9, 2011 at 6:46 AM, Lee Spector <lspe...@hampshire.edu> wrote:
>> But still, I will humbly submit that it's totally freakin' nutso that it should be so hard to do basic user interaction.
>
> I'm curious as to what percentage of developers are writing
> console-based applications (in any language)?
>

I don't know -- probably quite low -- but I would predict that the percentage of students learning Lisp-like languages who would be writing console-based interactive programs is quite high. Lots of curricula are based on this sort of thing, and even people who aren't students may do some of this first since it seems like it shouldn't rely on understanding libraries or the java ecosystem or whatever. So funky behavior related to simple text I/O may be confusing people who are least likely to know how to work around it.

> What do the processes look like in other languages? How are those
> processes different to what happens with Clojure?

Code for doing this sort of thing in Common Lisp or Scheme looks almost identical to the simple Clojure code we've seen on this thread, with the most significant difference being that the Clojure version apparently doesn't work if you launch your REPL and run your code in some of the commonly recommended ways. (Note: I haven't tried all of the ways that have been discussed, but the consensus seems to be that it does indeed fail.)

-Lee

Mike Meyer

unread,
Apr 9, 2011, 8:14:18 PM4/9/11
to clo...@googlegroups.com
On Sat, 9 Apr 2011 19:27:13 -0400
Lee Spector <lspe...@hampshire.edu> wrote:
> On Apr 9, 2011, at 6:18 PM, Sean Corfield wrote:
> > On Sat, Apr 9, 2011 at 6:46 AM, Lee Spector <lspe...@hampshire.edu> wrote:
> >> But still, I will humbly submit that it's totally freakin' nutso that it should be so hard to do basic user interaction.
> > I'm curious as to what percentage of developers are writing
> > console-based applications (in any language)?
> I don't know -- probably quite low -- but I would predict that the
> percentage of students learning Lisp-like languages who would be
> writing console-based interactive programs is quite high.

I wouldn't expect it to be "quite low", but I build them regularly,
and so do most of the people I work with. On unix systems, the
predominate way to plug to programs together is still via the standard
IO pipes, which the programs - even if normally invoked from some
visual tool - can be used from the command line. And many of us work
develop on one system, then deploy on something else via SSH, so being
able to deal with things on the command line - even if it also
provides some kind of GUI - is a useful feature.

Admittedly, JVM languages don't lend themselves to such things, what
with having to either start it on every command or have some kind of
server running to avoid that.

> Code for doing this sort of thing in Common Lisp or Scheme looks
> almost identical to the simple Clojure code we've seen on this
> thread, with the most significant difference being that the Clojure
> version apparently doesn't work if you launch your REPL and run your
> code in some of the commonly recommended ways.

This sounds more like an issue with those "commonly recommended ways"
than with clojure or it's REPL, since it works fine running from a
shell.

<mike
--
Mike Meyer <m...@mired.org> http://www.mired.org/consulting.html
Independent Software developer/SCM consultant, email for more information.

O< ascii ribbon campaign - stop html mail - www.asciiribbon.org

Albert Cardona

unread,
Jun 14, 2011, 11:55:57 PM6/14/11
to clo...@googlegroups.com, Meikel Brandmeyer
Hi Meikel,

it surprised me that you used an atom to run the event loop. A simple
loop/recur also works, at least in clojure 1.2.1:

(loop []
(let [input (read-line)]
(when (pos? (count input))
(println (pick phrases))
(recur))))


Just for the record.

Best,

Albert


--
http://albert.rierol.net
http://www.ini.uzh.ch/~acardona/

Meikel Brandmeyer

unread,
Jun 15, 2011, 2:01:53 AM6/15/11
to clo...@googlegroups.com, Meikel Brandmeyer
Hi,

indeed. On the other hand the atom has the advantage of making the loop easily interuptible from the outside. YMMV. :)

Sincerely
Meikel

Reply all
Reply to author
Forward
0 new messages