2

Is it possible to create (enter) nested environments in Scala REPL, such that after exiting the nested environment, all variable bindings created within the exited environment will be lost?

Here is what I wish a session could look like:

scala> val x = 1
x: Int = 1

scala> enter // How to implement this?
// Entering nested context (type exit to exit)

scala> val x = 2
x: Int = 2

scala> val y = 3
y: Int = 3

scala> exit // How to implement this?
// Exiting nested context

scala> assert(x == 1)

scala> y
<console>:12: error: not found: value y
       y
       ^

scala> 
Tomas Mikula
  • 6,537
  • 25
  • 39

1 Answers1

1

This isn't possible with the current Scala REPL, but you can achieve something similar using the Ammonite REPL:

Welcome to the Ammonite Repl 0.8.2
(Scala 2.12.1 Java 1.8.0_121)
@ val x = 1 
x: Int = 1
@ repl.sess.save("first")
res1_1: ammonite.repl.SessionChanged = 
@ val x = 2 
x: Int = 2
@ val y = 3 
y: Int = 3
@ repl.sess.save("second") ; repl.sess.load("first") 
res4_1: ammonite.repl.SessionChanged = 
Removed Imports: Set('y, 'res1_1, 'res1_0)
@ y 
cmd5.sc:1: not found: value y
val res5 = y
           ^
Compilation Failed
@ x 
res5: Int = 1

These sessions aren't nested exactly the way you describe, but are easy to track by name, and can overlap. That is after repl.sess.save("first"), you still have access to the original x if you don't override it.


After playing around with it some more, I was able to concoct a simple object that uses a stack to track the sessions and load/save them. It can be placed in ~/.ammonite/predef.sc to load automatically with the Ammonite REPL:

object SessionStack {

    case class AmmSession(id: Int = 1) {
        def name = s"session_${id}"
        def next = AmmSession(id + 1)
    }

    private var sessions = collection.mutable.Stack.empty[AmmSession]

    private var current = AmmSession()

    def enter: Unit = {
        sessions.push(current.copy())
        repl.sess.save(current.name)
        current = current.next
    }

    def exit: Unit = if(sessions.nonEmpty) {
        current = sessions.pop()
        repl.sess.load(current.name)
    } else {
        println("Nothing to exit.")
    }

}
import SessionStack._

I haven't tested this rigorously, so there may be an edge-case that isn't covered, but I was able to go a few levels deep easily and then peel back the layers.

Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
  • Thanks, that's interesting. Though I really want nested scopes, i.e. bindings from the outer scope to be available in the inner scope. – Tomas Mikula Mar 07 '17 at 03:36
  • @TomasMikula After calling `repl.sess.save("first")` you still have access to `x`, so you can still achieve a similar effect, just not as conveniently. It essentially allows you to "roll back" to the save point when you re-load it. – Michael Zajac Mar 07 '17 at 03:46
  • Oh, in that case, can I implement `enter` and `exit` using a global variable—a stack of session names? – Tomas Mikula Mar 07 '17 at 04:20
  • @TomasMikula See my edit. I think that will do what you're looking for using the Ammonite REPL. – Michael Zajac Mar 07 '17 at 17:44