Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for using *1, *2, *3, and *e #229

Closed
hovsater opened this issue Dec 29, 2020 · 12 comments
Closed

Add support for using *1, *2, *3, and *e #229

hovsater opened this issue Dec 29, 2020 · 12 comments

Comments

@hovsater
Copy link

hovsater commented Dec 29, 2020

When you evaluate a block, it would be nice to be able to use *1, *2 and *3 and *e respectively to reference return values and the last exception.

Taken from the Clojure docs:

Several special vars are available when using the REPL:

*1, *2, *3 - hold the result of the last three expressions that were evaluated
*e - holds the result of the last exception.

It seems to be supported by most REPLs.

(+ 1 1) ; When evaluated returns 2
(println *1) ; When evaluated prints 2 and returns nil (given the prior form was evaluated first).

I'd be happy to contribute a small amount to make this happen if you're interested in pursuing it. 🙂

@mauricioszabo
Copy link
Owner

No, it's not really possible. A single "eval top block" can issue one to three commands to the REPL, so the value of these vars are not bound to last evaluation. Also, there's no *e on most REPLs - most exceptions are captured and serialized for Chlorine to capture...

I'll probably add an entry on the FAQ regarding these vars :)

@hovsater
Copy link
Author

hovsater commented Dec 29, 2020

@mauricioszabo I see, that makes sense. 🙂 So am I right in assuming Chlorine does one-off evaluations to the Socket REPL instead of having an ongoing connection to it and that's why it's really not technically possible to achieve that?

For instance, doing echo '(+ 1 1) (println "The value is" *1)' | nc 0 5555 seems to print the correct output:

$ echo '(+ 1 1) (println "The value is" *1)' | nc 0 5555
user=> 2
The value is 2
nil

but I'm not familiar with how Chlorine actually send the blocks when you evaluate them, so perhaps, this isn't feasible.

@mauricioszabo
Copy link
Owner

No, it does have an ongoing connection. The problem is that when you evaluate anything, Chlorine tries to detect:

  1. Does this editor have a filename associated to it?
  2. Does it have a ns form?

If 1 is true, it'll send a command to set the filename (binding *1 to it);
If 2 is true, it'll send a command to set the namespace (again, binding *1)
Then, it'll send the eval command (once again, binding *1).

Now, if 1 and 2 are true (which they are, most of the time), everytime you send an evaluation you'll have *1 and *2 bound to nil and the current namespace (that's what the REPL returns when you set a filename and namespace). This means that "original *1" (the one that contains the evaluation result) is now *3.

You can check this yourself by trying to eval something, then evaluating *3. You'll see that it indeed contains the result of last eval; and that *1 probably contains nil, and *2 contains an instance to clojure.lang.Namespace

image

@mauricioszabo
Copy link
Owner

BTW, a simple correction: Chlorine will try to detect the ns FIRST then the filename. The reason is that when it detects the filename it also sets the right row, so if we send the in-ns command first it'll mess the current row

@hovsater
Copy link
Author

hovsater commented Dec 29, 2020

I see. That makes sense. So, it "works" but not in the way you would expect, making it less useful? I tried out the example and it worked just like you said it would. However, the example below didn't set *1, *2 and *3 to anything, which made me a bit confused again.

Screenshot 2020-12-29 at 13 20 49

I assume *1, *2 and *3 are set to nil due to what you described earlier, but given I have no filename and no namespace, what commands are being run before the actual block I'm trying to evaluate?

EDIT:

Nvm, I just realised that evaluating the actual vars will obviously change the value of them again 🤦 My bad! This makes sense. Using the vars will be highly indeterministic, which make them less useful.

@mauricioszabo
Copy link
Owner

Do you have a ns form on this editor? This could explain why *2 is bound to nothing.

As for the nil in *3, it's probably because you eval'ed *1 first (then making it nil). Also, you don't have a filename but Chlorine will still try to set the current row, binding it to nil

The current code that does handle evals in Chlorine starts here: https://github.com/mauricioszabo/repl-tooling/blob/master/src/repl_tooling/repl_client/clojure.cljs#L95-L96

and goes to here: https://github.com/mauricioszabo/repl-tooling/blob/master/src/repl_tooling/repl_client/clojure.cljs#L40-L48

@hovsater
Copy link
Author

Thanks for taking the time to explain this so throughly. It's really appreciated. 🙂

@hovsater
Copy link
Author

hovsater commented Dec 30, 2020

Sorry for bringing this up again but when I woke up this morning I realised I didn't fully understand what you meant yesterday.

Given that you said

If 1 is true, it'll send a command to set the filename (binding *1 to it);
If 2 is true, it'll send a command to set the namespace (again, binding *1)
Then, it'll send the eval command (once again, binding *1).

So *1 will always point to last expression evaluated.

Now, if 1 and 2 are true (which they are, most of the time), everytime you send an evaluation you'll have *1 and *2 bound to nil and the current namespace (that's what the REPL returns when you set a filename and namespace). This means that "original *1" (the one that contains the evaluation result) is now *3.

So how come *1 isn't the value of whatever was being evaluated? Are there expressions being evaluated after our own code has been evaluated? My understanding was that our own expression was the last thing being evaluated.

@mauricioszabo
Copy link
Owner

So how come *1 isn't the value of whatever was being evaluated

It is - but the problem is that when you evaluate something in Chlorine, it'll send other commands before your eval. So it'll be re-bind to the result of previous command - for example, the "set filename and/or row"

@hovsater
Copy link
Author

hovsater commented Dec 30, 2020

@mauricioszabo perhaps I misunderstand how the REPL bind the dynamic variables, but anything before my eval shouldn't be a problem since *1 is bound to the last result. I'm sorry if I'm asking a lot of questions, just trying to wrap my head around this.

@mauricioszabo
Copy link
Owner

mauricioszabo commented Dec 30, 2020

Ok, so the thing is: if you don't have a ns form on your editor, Chlorine will try to run a code to set the current row. Let's suppose this code is called (set-row <some-number>). So, if you're on the line 20 of the editor, this is what will be run:

; Evaling (+ 1 2)
(set-row 19) ; Binds *1 to nil

(do
(+ 1 2)); Binds *2 to nil, and *1 to 3

Now, on the second evaluation, if you eval *1:

(set-row 19) ; Binds *1 to nil, *2 to 3, and *3 to nil

(do
*1) ; *1 is nil, so it'll bind *1 to nil, *2 to nil, and *3 to 3

set-row is called with 19 because of the do form

@hovsater
Copy link
Author

hovsater commented Dec 30, 2020

@mauricioszabo thanks a lot, I finally get it! 🎉 Really appreciate you taking the time to explain this in detail. 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants