nrepl / piggieback

nREPL support for ClojureScript REPLs
480 stars 48 forks source link

Consider adding `:print-no-newline` #52

Closed mfikes closed 9 years ago

mfikes commented 9 years ago

Per https://github.com/clojure/clojurescript/wiki/Custom-REPLs#output

Note that :print-no-newline was requested by me (and in fact, I only just now retroactively added the interface spec above to attempt to have a place where it is documented).

This being relatively new, it can perhaps be eliminated if :print is revised to have semantics like a single-argument print, or if there is some better solution to the larger issue:

The reason I had asked for :print-no-newline was to properly support the distinction between when a REPL user issues

(do 
  (println "hi")
  (println "there"))

which should produce

hi
there

and

(do
   (print "hi")
   (print "there"))

which should produce

hithere

Note that the browser REPL dispenses with this problem by currently simply calling print on its own: https://github.com/clojure/clojurescript/blob/master/src/clj/cljs/repl/browser.clj#L150

(In other words, if it attempted to follow the contract and call :print it would encounter the same problem I did.)

mfikes commented 9 years ago

I looked into the behavior Ambly / Piggieback when issuing (print "foo") in the REPL.

Temporarily revising Ambly to essentilly invoke ((:print opts) "foo") fails because "foo" is not the result of an evaluation (and is squelched by Piggieback).

This is cool and raises a couple of questions for me:

  1. How should printing be handled? (The case where the browser REPL—not by intent, but simply owing to history—is directly calling print, followed by flushing *out*.)
  2. Is it possible for REPLs to do I/O outside of (a) the normal reading of forms and printing their evaluated values, and (b) printing output?

Question 2 is only of interest for Ambly because of the Bonjour discovery / configuration UI that it wants to perform during the first part of setup. (See an example of that here). For the little command-line UI that Ambly creates, it involves printing as well as printing without a newline so that it can then do a (read-line) for the Choice: prompt.

Other REPLs seem to do a little of what Ambly does with respect to this: They will print a little bit of status (Node.js REPL will indicate which port is involved, the Browser REPL will throw up an indicator that it is waiting for the browser to connect. These are command line UIs that are "preludes" to the REPL going into its loop, but they are simpler than Ambly's because they don't involve prompting (and potential re-prompting if you hit R for refresh in Ambly), prior to the normal part of the REPL going live.

(I feel like this aspect of Ambly is definitely useful, but at the same time, pushes a little on the boundary of the scope of what REPLs are expected to do.)

cemerick commented 9 years ago

It's not just the included node and browser REPLs that use the "system" print to print without a newline, it's all of them, AFAIK. Same goes for Austin and weasel.

The :print fn provided to the REPL is (to my understanding) intended to handle values resulting from evaluation, nothing more. This goes back to clojure.main and its :print arg (which defaults to prn so that results of Clojure evaluations are printed readably). @swannodette an I talked about this some here...which I see you commented in as well, but at the bottom.

I think the basic point is that REPL environments always have access to Clojure-side *out* and *err*, and they should use them to deliver the results of printing in ClojureScript, and any out-of-band information they need to communicate to a human. Anything printed to those streams will work anywhere, whether you're on a typetype, nREPL, or even using cljs.repl/repl* in an unforeseen way. cljs.repl does this already in a bunch of places, including its default error handler: https://github.com/clojure/clojurescript/blob/master/src/clj/cljs/repl.clj#L683 I see no reason why Ambly shouldn't do likewise.

So, I'd suggest that:

  1. :print-no-newline goes away
  2. Ambly can simply use print, println, and *out* and *err* however is necessary for any output beyond printing the values resulting from evaluation (this constraint is pedantic, insofar as cljs.repl/repl* already takes care of printing results via :print).
mfikes commented 9 years ago

@cemerick Your suggestion is fine with me.

The original motivation for all of this was some 19-Feb-2015 IRC discussion (taken from here):

[12:17:37] dnolen: brucehauman: tjakubow: mfikes: one thing to be aware of the coming word, you cannot just call print/println or flush anymore since you don't know under what context the REPL is being used
[12:17:51] dnolen: opts will always contain the :print and :flush fn that you should use
[12:18:16] dnolen: opts is not consistently passed around however, but you can still get at opts via the *repl-opts* dynamic var
[12:18:25] dnolen: s/word/world
[12:18:44] dnolen: whatever you are doing today will of course will still work
[12:19:08] dnolen: but if nREPL rides on this stuff people will be pretty unhappy if you're printing directly to standard out etc.
[12:19:27] brucehauman: dnolen: thats good to know, and *repl-opts* is helpful
[12:19:28] dnolen: piggieback or whatever
[12:19:44] dnolen: brucehauman: yeah it's a hack, but it's the only way to not break everythign

Subsequent IRC discussion lead to :print-no-newline.

@swannodette Given the IRC above, do you see any issue with Chas's suggestion in the previous comment (to eliminate :print-no-newline and have REPLs freely use print functions outside of the case of result printing, which must go through :print)?

cemerick commented 9 years ago

Thank you for that context. To be clear, nREPL has no problem with *out* and *err*. Those are captured via a regular dynamic binding, with content passed along to the connected session. What nREPL does differently is it keeps stdout/err separate from communicating the results of evaluation. This makes it easy to programmatically interact with a remote evaluation context w/o trying to ignore printed content, apply special presentation transformations to evaluation results, etc.

mfikes commented 9 years ago

Given the ClojureScript change I've done the same for Ambly.

mfikes commented 9 years ago

@tomjakubowski @bhauman You may be interested in this thread.

cemerick commented 9 years ago

@mfikes since you're not using :print-no-newline anymore (and you were the only user of it?), I'll close this now. If the motivating use case is gone now, presumably that option will eventually disappear from cljs.repl/repl.

If anyone has any clarifying questions re: nREPL and its handling of printed output vs. evaluation results (or anything else, really), I'd suggest posting to the clojure-tools list, just so the discussions are in a more obvious location. :-)