Monday, October 1, 2012

Reading user input from STDIN in Clojure

Recently I was working on a simple Clojure program where I need to read input from STDIN. I hadn't actually done this before, so I searched online and found others had similar questions, but had to cobble an answer together due to two issues:

  1. In most languages you use a while loop, check for some condition (or input) to be false in order to stop. Clojure actually has a while loop construct, but making it work seems tricky and likely involves mutable state. Paying the overhead of STM (Software Transactional Memory) in order to do a simple input loop, seems like excessive overkill.

  2. When you try to read from STDIN using lein run, it doesn't work. The input is ignored.

So, here's what I have working that is reasonably idiomatic and concise.

In my code example, I want to read a user's input until they type in a sentinel value, which I chose to be :done. Here I just echo the output back:

The trick to getting it work with with Leiningen (I'm using lein-2.0.0-preview10) is to use lein trampoline run, rather than lein run. The trampoline feature allows you to run your app in a separate JVM process rather than within the Leiningen process (as a child process). Running it as a child process somehow blocks STDIN input from being captured.

Example usage:

$ lein trampoline run -m example.stdin
Enter text:
First line
You entered: >>First line<<
Second line
You entered: >>Second line<<
Foo
You entered: >>Foo<<
:done
End

In all likelihood, you would want to capture and retain the input lines in a collection. To do that use an accumulator in your loop:

Ideas on more idiomatic ways to do this in Clojure are welcome.