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

suspend, i.e. the Secret Master Plan for Fixing All The Things #1087

Closed
byorgey opened this issue Feb 5, 2023 · 6 comments · Fixed by #1928
Closed

suspend, i.e. the Secret Master Plan for Fixing All The Things #1087

byorgey opened this issue Feb 5, 2023 · 6 comments · Fixed by #1928
Assignees
Labels
C-Project A larger project, more suitable for experienced contributors. G-CESK machine This issue has to do with the CESK machine (the Swarm language interpreter). G-REPL An issue having to do with the REPL. S-Moderate The fix or feature would substantially improve user experience. Z-Feature A new feature to be added to the game. Z-Refactoring This issue is about restructuring the code without changing the behaviour to improve code quality.

Comments

@byorgey
Copy link
Member

byorgey commented Feb 5, 2023

The big idea

What happens, theoretically and practically, when you type stuff at the REPL? The way I always think about it is that it's as if you're writing one big long block of commands chained by semicolons; it just so happens that you get to enter them one at a time. This explains why names defined at the REPL should be in scope for later things entered at the REPL. (Well, you can also enter expressions to be evaluated in addition to commands, but this can easily be explained by saying that anything without a command type gets implicitly wrapped in return.)

So what is the base doing in between REPL entries? Currently, it is idle, just like any robot that has finished running its program. But this is wrong! We should have a new CESK machine state to represent what the base is doing while waiting for another REPL input.

  • Fundamentally, the base is doing something different than an idle robot. An idle robot will never do anything more unless another robot comes to salvage or reprogram it. The base will resume executing as soon as something else is entered at the REPL. If only for conceptual reasons it makes sense to distinguish between these states.

  • If the base is idle, its CESK machine no longer remembers any context or store. So we have to save them into the robotContext just so we can load them back into the new CESK machine state on the next REPL input. I now realize how silly this is! Why not just have the CESK machine remember it all along?

Details

I propose adding a new CESK machine state Suspended and a new primitive constant suspend.

  • The new machine state could be called Suspended, and will contain a result Value, a current Env, and a current Store. I do not think it needs to have a continuation (since I cannot think when we would ever want anything besides the empty continuation), but we could easily add a continuation if it turns out to be useful later.

  • The new primitive is suspend : a -> cmd a.

    • When suspend v is executed, it has the effect of putting the machine immediately into the Suspended state, with v as the value, and the current environment and store.
    • suspend should not be available for players to use, but will only be used internally.
    • Whenever a command like x; y; z is entered at the REPL, it will be automatically rewritten to x; y; v <- z; suspend v.
      • Note that the suspend comes last, so there will in fact be an empty continuation at the point when it is executed, which is why the Suspended state does not need to store one.
      • On the other hand if we later decided we wanted to allow things like x; suspend v; y then we would need to save a continuation.
  • Instead of checking to see when the base is idle, the REPL will now check to see when it becomes Suspended

  • When something is entered at the REPL, the environment stored in the current Suspended machine state will be used to typecheck it, and then the Suspended state will be replaced with a state appropriate to evaluate/execute the given input (with the environment and store carried over), just as if the original suspend v call had been replaced with the new input. In other words, if you type a; b; c at the REPL and then later enter x; y; z, the net effect of the suspend is to make it as if you had literally written a; b; c; x; y; z.

  • We will need to put some thought into what happens with uncaught exceptions that bubble up to the top level. The current environment and store will have to be captured somehow and put into a Suspended state with a unit value. In other words, if x; y; z is being executed and y throws an exception, it should be as if y; z is replaced by suspend ().

  • This will actually be better than the status quo, which one could argue has a bug: if you execute something like def m = move end; move in Classic mode the move throws an exception (since the base does not have tank treads), and m is not added to the context. In other words the entire command fails if any part of it fails; but one could argue that the def m should succeed and add m to the context before going on to try the move.

Implications

Definitions and import

Now that there is no longer any such thing as a global context, we should seriously reconsider #997. In fact we could simply get rid of def and only allow let ... in. Or we could make def x : ty = e end; c2 syntax sugar for let x : ty = e in c2. Only having let ... in would be awkward for typing definitions at the REPL since you would have to explicitly say let x = e in suspend () or something like that. But then again having both let and also def which is syntax sugar for let would be confusing. Maybe we could have ... as syntax sugar for suspend () so you could write let x = e in ... . Or we could just replace the def keyword with let, that is, let x = e end; c2 would be syntax sugar for let x = e in c2 (and let x = e end by itself would be let x = e in suspend ()).

One difficulty is how to handle imports (#495). All of this makes run even more problematic than before. A few ideas:

  • If def is replaced with (or syntax sugar for) let ... in, the question arises what we should do at the end of a sequence of definitions (say, in a .sw file). One idea would be to make a sequence of defs syntax sugar for a chain of lets with a call to suspend () at the very end.
  • The simplest idea would then be to replace run with a syntactic construct import <text_literal> in e, which would load the contents of the file with the given name and run it, replacing the final suspend with e. The downside of this idea is there would be a distinction between importing a file of definitions (using import ... in ...) and importing a file containing an expression.
  • A much more sophisticated idea is to add record types (Records #1093) to the language, and make a module just a record, like Agda does. I used to think this was a nice idea, but after implementing records (Records #1148) I realize there are a number of impediments to having this work well:
    • To make it work well we would want some kind of open ... in ... construct to dump all the names in a record into the current scope, so we wouldn't be tied to always having qualified imports. However, checking such a construct seems problematic (see the discussion at Records #1148).
    • We would also need to have records with polymorphic fields, which seems like it could open a big can of worms with higher-rank types etc; I also thought about it a bit and I have no idea how to go about implementing it.
@byorgey byorgey added the Z-Feature A new feature to be added to the game. label Feb 5, 2023
@byorgey byorgey changed the title suspend, *i.e.* the Secret Master Plan for Fixing All The Things suspend, i.e. the Secret Master Plan for Fixing All The Things Feb 5, 2023
@kostmo
Copy link
Member

kostmo commented Feb 6, 2023

Very exciting! Not to derail the topic, but is there any overlap with the concept of suspend and the watch/wait proposal (i.e. supporting interrupts) from #687?

@byorgey
Copy link
Member Author

byorgey commented Feb 6, 2023

I don't think they are related, but I could be wrong; maybe there is some more fundamental underlying mechanism.

@byorgey byorgey added Z-Refactoring This issue is about restructuring the code without changing the behaviour to improve code quality. C-Project A larger project, more suitable for experienced contributors. S-Moderate The fix or feature would substantially improve user experience. G-CESK machine This issue has to do with the CESK machine (the Swarm language interpreter). G-REPL An issue having to do with the REPL. labels Feb 6, 2023
@byorgey byorgey self-assigned this Feb 6, 2023
@xsebek
Copy link
Member

xsebek commented Feb 6, 2023

Thanks for the detailed write-up @byorgey, I will study it on my 2-hour train journey tomorrow. 😄

On a more serious note, the simplifications sound very promising and I am hopeful this will end our CESK bugs.

Afterwards, we should add a way to inspect the CESK machine environment in UI (tweezers) and outside with Web API.

@xsebek
Copy link
Member

xsebek commented Feb 6, 2023

Instead of getting rid of def, I would argue for an indentation-sensitive version like in Python:

def m2 =
  move;
  move;

// no end

m2;

Or a similar version of let, just to get around the odd trailing in which I would like to avoid.


EDIT: a fun idea is to keep def for mutually recursive definitions - we could consider all definitions in a chain at once.

@byorgey
Copy link
Member Author

byorgey commented Jan 27, 2024

Just adding a note to say that we (and by we I mean "I") shouldn't let the issues with run/import and #997 derail this idea. We can still implement the stuff with suspend but leave run in for now, leave def as it is, and so on.

@byorgey
Copy link
Member Author

byorgey commented Jun 9, 2024

I have started working on this. So far I have deleted a LOT of code (mostly weird special cases), and it makes me so happy. I still have some new code to write as well, but I think overall this will end up being a big net decrease in the number of lines of code.

mergify bot pushed a commit that referenced this issue Jun 19, 2024
…tate (#1928)

The big idea of this PR is to add a new type of CESK machine state, `Suspended`, which is a state the base robot automatically goes into after completing execution of a program entered at the REPL.  It is as if the base robot is still within the local context of any definitions etc. entered previously, just printed out an "intermediate result", and is now waiting to find out what the next term to be executed will be.  This allows us to treat `def` as syntax sugar for `let` and results in a much cleaner way to manage the context of definitions, which in turn allows us to remove a whole lot of special cases and fixes several annoying bugs.  See #1087 for more explanation and discussion.

Things that are **deleted** (!) by this PR:
- `TDef` (`def` is now just syntax sugar for `let`) (#997)
- `VResult` (we no longer need to return anything other than plain values)
- `FUnionEnv`, `FDiscardEnv`, `FLoadEnv` (we no longer need machinery to collect up definitions while evaluating terms)
- `ProcessedTerm` (just a plain `TSyntax` (i.e. `Syntax' Polytype`) is now enough)
- `Module` (it only existed to package up some contexts with typechecked terms, but we don't need them anymore)
- `RobotContext` and `topContext` (we don't need to store those things separately any more, they are just stored in the `CESK` machine where they belong)

Additions/changes:
- The `Requirement` module is split into `Requirements.Type` and `Requirements.Analysis` to avoid circular module imports
- New `Suspended` state for the CESK machine
- `def` and `tydef` are now allowed anywhere (even nested inside other definitions etc.) since `def x = y end; z` is just syntax sugar for `let x = y in z` (see #349)
- Code size decreased slightly for many programs using `def`, since `def` is now a synonym for `let`, and consecutive `def`s therefore do not require a `bind`.

Closes #1087.  Fixes #681 (hence also #736, #1796). Fixes #1032. Closes #1047. Closes #997. Fixes #1900. Fixes #1466. Fixes #1424.

See also #636.
@mergify mergify bot closed this as completed in #1928 Jun 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-Project A larger project, more suitable for experienced contributors. G-CESK machine This issue has to do with the CESK machine (the Swarm language interpreter). G-REPL An issue having to do with the REPL. S-Moderate The fix or feature would substantially improve user experience. Z-Feature A new feature to be added to the game. Z-Refactoring This issue is about restructuring the code without changing the behaviour to improve code quality.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants