lumo

:lumo: Standalone ClojureScript environment. Currently at version 1.9.0
2017-12-19T10:16:16.000302Z

@cgrand I wish that I would understand better the mecahnics of upgradeable repl. I'm wondering if these are related, for example in inf-clojure which I use with lumo, I've been constantly trying to make some loops I have running use process.nextTick() to be appended in front of the js eventqueue. Long story short, every evaluation of get-arglists and get-completions, needs to be read and cycle trough many functions before evaluated, causing the event-queue of the single thread to fill up (and seems that setImmediate and process.nextTick() become meaningless in those cases). An alternative, which if I understand you with the concept of upgradeable repl, would be that functions like get-completions and get-arglists, could potentially not be read, but just become a function call from the socket repl, where the socket repl could have logic from a front end to call a compiled function or send it trough the reader (evaluate-text etc)?

2017-12-19T10:21:59.000308Z

(unrelated), you mention a callback before printing, how would the user not specify a special callback, how would a front-end "take over" a repl. Via runtime variable?

cgrand 2017-12-19T10:29:52.000182Z

@hlolli thanks for your interest, first let me define what I mean by REPL: “read eval print loop” and nothing else. get-completion for example don’t belong to the REPL but to the client (for lumo since the client and the REPL share the same process it’s blurry). Try the socket repl of lumo to get a “pure” repl.

2017-12-19T10:32:17.000182Z

@cgrand Yes I agree, it shulould not belong to the repl therefore I think this part here is not very cool https://github.com/clojure-emacs/inf-clojure/blob/master/inf-clojure.el#L828-L831

2017-12-19T10:32:40.000083Z

Fault of the front end inf-clojure more than lumo I guess.

cgrand 2017-12-19T10:33:31.000212Z

If you start a plain clojure repl (eg with clojure), you can do

cgrand 2017-12-19T10:35:27.000266Z

~$ clojure
Clojure 1.9.0
user=> (loop [] (let [x (read)] (when-not (= :bye x) (println “you said” x) (recur))))
hello
you said hello
world
you said world
help I’m trapped!
you said help
you said I’m
you said trapped!
:bye
nil
user=>

cgrand 2017-12-19T10:36:15.000240Z

here the code being evaluated takes control of the repl streams/process

2017-12-19T10:37:33.000165Z

ahh ok! suddenly I understand your point much better

cgrand 2017-12-19T10:38:51.000147Z

It’s the same meaning of upgrade as in “HTTP 101”: before it was http now it’s something else

cgrand 2017-12-19T10:39:40.000242Z

and this is what unrepl exploits to get you a better repl experience starting from a plain repl

cgrand 2017-12-19T10:39:57.000272Z

In clojure this works because everything blocks

2017-12-19T10:40:26.000108Z

ok

2017-12-19T10:40:41.000568Z

have you considered async/await, would it fit here?

cgrand 2017-12-19T10:43:54.000322Z

async/await is just js sugar, no?

2017-12-19T10:44:57.000410Z

it's a sugar for promises, but really blocks the reset of the body until returned, given the whole function is noted with async.

cgrand 2017-12-19T10:46:10.000294Z

it doesn’t really block, it transforms in CPS (or similar) the body of async fns

cgrand 2017-12-19T10:47:08.000355Z

in cljsjs my best proposal so far is to have the expression evaluates to a SuspensionRequest value (which is just a special delay)

2017-12-19T10:47:12.000039Z

yes I guess so, it would be more akin to CPS. If you need real block, then I guess it wont cut the butter.

cgrand 2017-12-19T10:48:32.000209Z

I don’t want real block, because JS is async, I just want to get the same feature at the end: the possibility for an expression to take over the repl. I don’t care if the expression is different than in Clojure.

2017-12-19T10:49:27.000348Z

I could see a new function for this in lumo, a callback is provided and starts a read-eval-print-loop

2017-12-19T10:49:38.000470Z

lunchcall, afk

cgrand 2017-12-19T10:50:17.000103Z

ok I’ll let you some things to read then 🙂

cgrand 2017-12-19T10:51:41.000217Z

(again it’s doable I’ve made three or four protototypes) what I want to find now is how to minimally modify lumo built-in repl to give it this power.

cgrand 2017-12-19T10:58:35.000313Z

For a long time the design was variations over “set a callback using a public fn or global object”. I was never happy with that because it’s a callback setting that has an additional side-effect: suspend the main loop and then you get a lot of special cases: - what should I do of the return value (or the exception thrown) of the whole expression? - what if the user sets the callback twice? can’t start two loops, so: first wins? last wins? exception? - what if the user sets the callback from a really async callback? Should not stop the loop obviously, throw an exception and hope the exception will get properly reported at some point?

cgrand 2017-12-19T10:59:31.000313Z

So it makes the contract “not great” and the implementation is more complex (because more checks).

cgrand 2017-12-19T10:59:54.000005Z

That’s how I ended with the idea of a suspension request

cgrand 2017-12-19T11:01:26.000211Z

The expression must evaluate to a suspension request so it provides easy answers to all the points above because it’s just a value (containing a function).

cgrand 2017-12-19T11:04:10.000121Z

When the lumo repl gets a suspension request out of evaluation then instead of printing it and “looping”, it should call the function in the suspension request, and pass it the input stream and a resume-cb.

cgrand 2017-12-19T11:04:39.000019Z

the resume-cb will resumes the lumo REPL.

cgrand 2017-12-19T11:05:45.000048Z

to avoid adherence to node.js I introduce a small AsyncReader protocol for the input stream passed to the suspension request.

cgrand 2017-12-19T11:06:20.000004Z

EOM

cgrand 2017-12-19T11:07:35.000264Z

note: lumo R-E-P-L is more R.on(EP) (with E and P fused together)

2017-12-19T12:08:27.000190Z

I wonder if a "user" would ever work with upgradeable repl vs front-end dev tools developers. But I wouldn't know. Sounds cool this contract of suspension request, going to check it out.

2017-12-19T12:13:19.000022Z

As a typical user, when I wanted to take over the repl, I found myself mixing core.async and the module readline https://github.com/hlolli/lumo-quiz-game/blob/master/src/lumo_quiz_game/main.cljs#L75-L78

2017-12-19T12:16:42.000210Z

and I misunderstood you probably for cljsjs as the cljsjs js for cljs libraries.

cgrand 2017-12-19T12:23:57.000365Z

sorry I should add a keyboard shortcut to replace cljsjs by “self-host cljs”

cgrand 2017-12-19T12:27:12.000235Z

unless I’m ocnfused about you ran it you didn’t take over the repl, you wrote a term app

2017-12-19T12:48:44.000374Z

That's right, I wonder what would happen if I add eval into this loop 🙂

2017-12-19T12:49:20.000258Z

but yeah, it doesn't avoid lumo's print and eval combo

2017-12-19T12:49:25.000259Z

nevermind

2017-12-19T13:00:43.000146Z

Not how I ment it, the callback function that is to be passed to a repl, is only set once and that callback function is written by front-end devtools developers. The users would then just start codeing without worrying about the underlying structure. So if for example unrepl has different printer than default repl, then that printer and all boilerplate will be passed to that function? ex. (lumo.repl/upgdrage-repl (fn [line] ..boilerplate with out-writer..)) where upgrade repl starts a loop.

2017-12-19T13:01:17.000254Z

So my question is, would a user ever have to worry about setting two callbacks or give a repl async function?

2017-12-19T13:07:46.000567Z

or rather, the callback-fn would have to be responsible for looping...

cgrand 2017-12-19T13:07:54.000385Z

so the question is upgdrage-repl! vs suspension-request?

2017-12-19T13:12:32.000272Z

allow me to take a step back again, if the repl receives expression before the old return value is returned, do we wan't to add it into a queue, or print the return value asynchronously, possibly in wrong order (if that's even possible, I doubt actually)

cgrand 2017-12-19T13:12:45.000157Z

In both case its’ a 1-fn API which takes the same callback as argument. I prefer suspension-request because (being value-based) it has clearer semantics and less room for error or abuse.

2017-12-19T13:13:19.000291Z

ok, I guess the suspension-request method would require a little tweak on the current execute-text function?

2017-12-19T13:14:50.000297Z

Maybe the best thing, is to do a PR and then it would probably spark a conversation on github and we could try it out etc. I think there are many that would like to see this feature.

cgrand 2017-12-19T13:15:05.000332Z

both approaches require tweaks of this function (and/or its callers).

cgrand 2017-12-19T13:15:48.000073Z

You are giving me an idea but I don’t know if it’s fully baked yet and, if it is, if it’s unholly

cgrand 2017-12-19T13:17:56.000154Z

the idea would be to use both approaches: suspension-request as the api and upgrade-repl! as the implementation. This would require to let most of the caller chain unchanged at the expense of yet another piece of mutable state...

👍 1
2017-12-19T13:18:01.000193Z

I was overthinking based on your question "what if the user sets the callback twice? can’t start two loops, so: first wins? last wins? exception?", but js is built on event-queue, so first in first out. Probably we are misunderstanding each other 🙂

2017-12-19T13:18:38.000353Z

(given that a callback is not sent to webworker)

cgrand 2017-12-19T13:24:25.000606Z

> I was overthinking based on your question “what if the user sets the callback twice? can’t start two loops, so: first wins? last wins? exception?“, My pondering was mostly that a side-effectful api open room for undefined behaviors (from the contract point of view) which the user (even it’s a frontend dev) may have a hard time to spot as bugs at first.

cgrand 2017-12-19T13:25:08.000130Z

Q: Your :thumbsup: is about using state or not using it?

2017-12-19T13:33:45.000363Z

I was reading again your messages, didn't find where you mentioned where a mutateable state is needed. I'm therefore neutral, I can see a way of one function api where the old loop is only suspended and new loop is takeing over. My thumbs up ment, you have theory on how to solve it and I'd like to see it implemented and try it out. If someone is really the final call here is antonio.

cgrand 2017-12-19T13:35:58.000255Z

mutable state: if I don’t want to change the signature of the call-chain to execute-text then I need to set some mutable flag — pretty sure it would incur technical debt

2017-12-19T13:54:43.000648Z

How about adding callback to

(defn- execute-text
  [source {:keys [expression? print-nil-result? filename session-id] :as opts}]
result-writer-cb just thinking out loud like before. That could be passed into eval part of repl to control the return value logic?

cgrand 2017-12-19T14:31:49.000040Z

that’s my plan

2017-12-19T14:59:57.000314Z

good plan, won't change the signature in a breaking way since it's destructured keys.

cgrand 2017-12-19T15:56:56.000294Z

Is there a way to test a change to lumo without rebuilding?

anmonteiro 2017-12-19T15:57:41.000008Z

@cgrand you can use the development workflow

anmonteiro 2017-12-19T15:57:51.000296Z

boot dev to watch & build the JS + CLJS

anmonteiro 2017-12-19T15:58:01.000924Z

yarn dev to launch it in Node.js without having to build the C++

cgrand 2017-12-19T15:58:49.000852Z

Thanks!