Collaborating on improving the REPL evaluator

I would like to improve the REPL experience in two ways:

  1. Currently it reevaluates all bindings in your session on every expression submitted. That is, it bundles up a new module with everything in it and runs a fresh node process. This makes it problematic if you want to support effectful bindings. Currently you cannot run effectful code and use the result in subsequent expressions. You can use unsafePerformEffect, but that will run your effects again and again. I would like to run a persistent node process for the entire repl session rather than a process to evaluate each expression.
  2. There’s no way to run async code. Once we allow effectful code to run, it would be nice to support running Aff, where the repl will block until a result is received.

For the first case, I’ve written some JavaScript to handle a persistent REPL session, where you can submit bindings or an expression to evaluate. It correctly handles recursive binding groups, which is a little tricky. It would need a basic socket server wrapper, but I don’t see any issues with that. The real work is updating the purs repl code to manage this node process, and likely changing how it invokes the compiler, so that it can grab and submit the individual bindings to the evaluator instead of a module with everything in it. Additionally it would have to know how to extract the type of an evaluated expression and retain that in the type environment.

For the second case, I would like to change the signature of the Eval code to return an Aff-like signature instead of Effect Unit, and also return the evaluated type. Something like

eval :: forall a b.
  Eval a b =>
  -- The value to evaluate
  (Unit -> a) ->
  -- The callback to invoke if an exception occurs
  (Error -> Effect Unit) ->
  -- The callback to invoke with evaluated value `b`
  (b -> Effect Unit) ->
  -- Returns a canceller, which will cleanup whatever is running when invoked
  Effect (Effect Unit)

I’ve prototyped something like this in my evaluator, and doesn’t seem to be an issue.

Would anyone like to collaborate on this? Do other PureScript maintainers or users have any opinions on these changes?

7 Likes

I’m definitely keen for this, although I don’t think I can set much time aside to collaborate on it unfortunately. I’d love to be able to support <- in the repl, for instance.

@kritzcreek worked on something called purpl a while back that I think might have had similar goals, so he may have some insight.

There’s no way to run async code. Once we allow effectful code to run, it would be nice to support running Aff , where the repl will block until a result is received.

Well, that explains the error I was unable to get past this evening. lol

Neat! Once I’m done with my semester in about a month I’ll have a lot more free time and would love to help out!

1 Like

It appears that both Aff code and <- work in the repl in a :paste block. But in this case, all three lines are printed after the delay. Maybe this is really just Effect code with the launchAff_. Is the goal of these enhancements to get this working without the :paste block, and have the print timing work as expected?

> :pa
… launchAff_ $ do
…   log "waiting"
…   toAff $ sleep 1000
…   log "done waiting"

waiting
unit
done waiting

It appears that both Aff code and <- work in the repl in a :paste block. But in this case, all three lines are printed after the delay. Maybe this is really just Effect code with the launchAff_ . Is the goal of these enhancements to get this working without the :paste block, and have the print timing work as expected?

I mean <- as a top-level repl binding, so you can do:

> a <- readFile "file.txt"

And a would be in scope for future repl calls. The repl currently does not let you evaluate effectful code, and maintain a binding to its result.

1 Like