Tomas Mikula
Tomas Mikula

Reputation: 6537

Nested environments in Scala REPL

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> 

Upvotes: 2

Views: 117

Answers (1)

Michael Zajac
Michael Zajac

Reputation: 55569

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.

Upvotes: 1

Related Questions