masak / bel

An interpreter for Bel, Paul Graham's Lisp language
GNU General Public License v3.0
27 stars 1 forks source link

Should `thread` block the REPL while it finishes? #349

Closed masak closed 3 years ago

masak commented 3 years ago

As I was experimenting with #348 a bit, I realized for the first time that running something like this:

> (thread (let n 0 (while (< (++ n) 5)) (set aborted t) (pr 'aborted \lf)))
aborted
(aborted \lf)
>

...actually holds up the execution and doesn't give a new prompt until the thread has finished/"joined". Whether this behavior is correct or not is, I guess, a bit of a philosophical question. But I was slightly surprised, which suggests that maybe it could be incorrect. Even though it makes sense from the point of view of the current architecture.

Positing a slightly different architecture, one in which the same Bel evaluator both presents the REPL prompt and evaluates the user's code, feels like it might make it possible to spawn threads which then don't block the "main" REPL thread from continuing. But even as I write this, it does feel mildly contrived and not at all obvious.

So, hm.

masak commented 3 years ago

Positing a slightly different architecture, one in which the same Bel evaluator both presents the REPL prompt and evaluates the user's code, feels like it might make it possible to spawn threads which then don't block the "main" REPL thread from continuing.

I'm here to offer the contrarian view.

Picture a universe in which we have what #204 talks about, a repl function defined inside of the Bel environment itself:

(def repl ()
  (print:bel:read:prompt "> ")
  (repl))

This is the best-case scenario in which the REPL itself is defined using only built-ins. Specifically, the bel call is how we say "and now, evaluate the code". But that would still place that evaluation in a "sub-evaluator" of the current one, by necessity. And that means that all the threads would need to run to completion before control was returned to repl — the threads on the "current level" are distinct from the threads created inside the bel call.

I find it difficult to imagine an alternative where this is not so. There is no other action besides bel that's reasonable to do there in order to evaluate the code.

Closing.

masak commented 3 years ago

I came back here now to bash OP, but found that my previous comment already said everything I wanted to.

As I discover more and more (realistic) limitations of bel (eval), I get more and more disappointed. It does give the power to evaluate arbitrary data as code, but it does it at a slight remove from the "current" evaluator. I'm starting to suspect that this is part of the whole "metacircular" deal... But I'm curious if "run this in a nested evaluator" is really the only option here — is there something fundamentally impossible about "run this in the current evaluator"?

This neatly ties into #395, which I think should not merge, because it would turn bel into "run this in the current evaluator". Sad but true. On the other hand, I still feel like I haven't fully mapped out the contours of the relationship between the "ambient" evaluator running the Bel code most of the time, and the bel function in the globals. Only that they cannot possibly be the same thing, because then the metacircularity collapses too hard and turns into actual circularity, looping forever. (How do you define applyf? By performing all these function calls! Oh, wait.)