Fork me on GitHub
#clojure-dev
<
2019-05-20
>
vlaaad07:05:59

I've got a bit of a feedback regarding prepl, since it's still alpha/subject to change. If I understand it correctly, "programmable repl" purpose is to make it easy to write repl-like tools, whose output may be processed by programs first before presenting it to user. I like these semantics of continous notifications via outfn, but there are some annoyances with existing notifications and their order: - :tap results arrive in confusing order because it's async. Tap system should be synchronous, just as printing to *out* is, because it's a debug tool, so there is no need for top performance, but there is a need for understanding an order: print-based debugging is a valuable tool, and part of it is seeing in which order statements get printed. Mixing prints and tap, and a fact that :taps might arrive after :ret brings only confusion - I'd like to have another message to outfn indicating that the form was read (before evaluating it): it matches usual repl flow where you first submit a form, and then see prints and return values. Currently submitting a long-evaluating form to a prepl gives no feedback at all that something is happening. Tools should provide more immediate feedback. - :ms in :ret-messages is unnecessary. If you want to know execution time, just use (time ...). If you want to have execution time be a part of "programmable" API, I think there should be a better solution, because: - There is a lack of composability over input/output. Input is just a stream, and if you want some additional functionality, such as cancelable evaluations, or adding file/line/column annotations, or some kind of measurements over evaluated forms, it's really hard to do so by wrapping in-reader. But it can be done easily if prepl could accept a function that transforms forms that are read from it, and these transforms can be then taken into account by outfn. for example, input form (+ 1 2) might be transformed that way:

(let [start (System/nanoTime)
      ret (+ 1 2)
      ms (quot (- (System/nanoTime) start) 1000000)]
  {:ms ms
   :ret ret})
Then if you use such input transformer, your outfn can just extract :ms and :ret from returned map first. Having such form transformer will greatly empower tool developers.

john14:05:48

Isn't it expected though that some tap messages will be used across async boundaries? Can't sync those, right?

john14:05:46

I haven't used the tap system much yet though, not sure

alexmiller14:05:36

aren't tap and printing both inherently asynchronous because they can happen from multiple threads in arbitrary ways?

john14:05:52

I guess you could block threads while print and tap buffers realign

john14:05:21

Try to force some deterministic behavior... would be non-trivial though

alexmiller14:05:03

that doesn't seem like a good idea

john14:05:49

Well, it's nice to have async on the bottom and make the sync api on top anyway, so the syncing can be done just-in-time

john14:05:18

I could definitely see vlaaad's ideas implemented on top of the tap system

alexmiller14:05:23

on the points above: 1) :tap results are asynchronous, but so are the printing streams. I don't understand your assertion that it is synchronous. 2) the current behavior is the "usual" flow - send a form, get a result. there is no read-value step in a normal repl. the client knows what it is sending - it can print it if you want that, but I'm not sure I get why that's better 3) it's not necessary, but it's just a return attribute that you can consider optional. ignore if you like. 4) this is inherently a stream based api so that's the primary unit of composition. I'd say it's a non-goal to create a middleware-like system here, but there might be good use cases for plugging in some kind of server-side read or return transformation.

vlaaad14:05:47

1 & 2. Yeah, I see your point, maybe I just got used to cursive repl showing sent form and printlns before return values. 3 & 4. Sure, I can ignore that, just want to point out that to me 3 looks like symptom of 4: you wanted feature X and hardcoded it, and other users have no way to inject their own functionality like that. Though I feel I'm mistaken, how can I compose streams? can you show an example?

alexmiller14:05:41

just wrap prepl in something that affects the in and out streams

vlaaad15:05:02

I'm trying to understand how should I wrap in - it's a LineNumberingPushbackReader. Suppose I want to provide my own execution time tracker like here:

(let [start (System/nanoTime)
      ret (+ 1 2) ;; <- `(+ 1 2)` is a form I receive in `in`
      ms (quot (- (System/nanoTime) start) 1000000)]
  {:ms ms
   :ret ret})
I have to create a new reader, that will have to read forms from in, preferably preserving line numbers and spaces, so it rules out LispReader that might execute stuff and EdnReader that removes comments. And then what? Wrap a form I read, and pass it further? It will be completely different form that I don't want user to see, with different line numbers...

alexmiller15:05:45

yeah, it's a lot of work. as I said above, there may be good use cases for plugging in some kind of server-side read or return transformation

vlaaad15:05:21

oh, I found a renumbering-read and annotating forms like that: ^{:clojure.core/eval-file "a/b/foo.clj", :line 100, :column 1} (hi 1)

vlaaad15:05:05

It feels there are some pieces of puzzle here, just need to figure out how to assemble them...

andy.fingerhut16:05:54

@slipset Regarding the digging you did on comparators in early April, and I mentioned that the comparator's guide doc should probably mention NaN's, it does now. You can search for a section starting with "Be wary" in the latest version of the guide here: https://clojure.org/guides/comparators

🙏 1