Sambo
Sambo

Reputation: 199

Scala REPL: avoid terminating batch job

I am running the Scala REPL, version 2.12.4, on windows 10. When I press CTRL-C, I am prompted: "Terminate batch job (Y/N)?". However, no matter what I type, the REPL always stops and I have to restart it.

How can I avoid exiting the REPL?

Upvotes: 1

Views: 142

Answers (2)

Datanova Scientific
Datanova Scientific

Reputation: 59

If you use REPL a lot, you may want to consider Ammonite. It handles Ctrl-C gracefully out of the box.

It also provides some other nice features like syntax highlighting & scripting for REPL-heavy applications.

Upvotes: 0

user unknown
user unknown

Reputation: 36229

If you know upfront, that you run into such issues, you can prepare for it.

Here the steps which didn't worked, until I found a solution:

a) The naive apporach, catch Exception and exit gracefully:

 object InterruptTest {

    // this method takes some seconds, longer and longer, a few seconds 
    // until it reaches 45 on my some years old machine, enough time 
    // to interrupt it. For larger values, you soon have to increase 
    // startparams, like: 
    //           scala -J-Xms512m -J-Xmx4G InterruptTest 80
    @annotation.tailrec
    def partition (count: Int, ziel: Int, sofar: Vector [Vector [Int]]) : Vector [Vector [Int]] = {
        println (s"i: $count\tlen: ${sofar.size}")
        if (count == ziel) sofar else {
            val ll = Vector (sofar.view.map (li => (li.head + 1) +: li.tail), sofar.view.map (li => 1 +: li)).flatten.map(_.sorted).distinct
            partition (count+1, ziel, ll)
        }
    }

    def main (args: Array[String]) : Unit = try {
            args.map (s => partition (1, s.toInt, Vector (Vector (1))))
        } catch {
            case ie: InterruptedException => println ("ie: exit gracefully")
            case re: RuntimeException => println ("re: exit gracefully")
    }
}

val test = InterruptTest
test.main (Array ("45"))

Well - it doesn't work. Ctrl-C is still immediate exit. Worth a try, if you happen to not knowing the specification details.

b) Let's use the Java Scanner, to eat keystrokes:

object InterruptTest {

    val sc = new java.util.Scanner (System.in)

    @annotation.tailrec
    def partition (count: Int, ziel: Int, sofar: Vector [Vector [Int]]) : Vector [Vector [Int]] = {
        println (s"i: $count\tlen: ${sofar.size}")
        // if there is a token, test whether 'q', then quit
        if (sc.hasNext) {
            val token = sc.next()
            if (token.trim () == "q") {
                println ("User request: quit, incomplete result!")
                return sofar
            }
            else println ("unknown user request: " + token + " will proceed, use 'q' to quit.")
        }
        if (count == ziel) sofar else {
            val ll = Vector (sofar.view.map (li => (li.head + 1) +: li.tail), sofar.view.map (li => 1 +: li)).flatten.map(_.sorted).distinct
            partition (count+1, ziel, ll)
        }
    }

    // time scala -J-Xms512m -J-Xmx4G Partition 80
    def main (args: Array[String]) : Unit =
            args.map (s => partition (1, s.toInt, Vector (Vector (1))))
}

val test = InterruptTest
test.main (Array ("45"))

A brilliant idea, except: Scanner.hasNext is blocking. So we need a separate Thread. I'm sure there are more elegant solutions than this, with Akka, with Futures and so on. Take it as proof of concept:

c) The Meanwhiler:

object InterruptTest {

    // still around: The Scanner-guy:
    val sc = new java.util.Scanner (System.in)
    // ... but now with volatile:
    @volatile
    var interrupted = false

    // ... and Meanwhiler:
    object Meanwhiler extends Thread {
        override def run () : Unit = {
            if (sc.hasNext) {
                val token = sc.next ()
                if (token.trim () == "q") {
                    println ("User request: quit, incomplete result!")
                    interrupted = true
                }
                else println ("unknown user request: " + token + " will proceed, use 'q' to quit.")
            }
        }
    }

    @annotation.tailrec
    def partition (count: Int, ziel: Int, sofar: Vector [Vector [Int]]) : Vector [Vector [Int]] = {
        println (s"i: $count\tlen: ${sofar.size}")
        if (count == ziel || interrupted ) sofar else {
            val ll = Vector (sofar.view.map (li => (li.head + 1) +: li.tail), sofar.view.map (li => 1 +: li)).flatten.map(_.sorted).distinct
            partition (count+1, ziel, ll)
        }
    }

    // time scala -J-Xms512m -J-Xmx4G Partition 80
    def main (args: Array[String]) : Unit = {
        val mw = Meanwhiler
        mw.start ()
        args.map (s => partition (1, s.toInt, Vector (Vector (1))))
        mw.join ()
    }
}

val test = InterruptTest
test.main (Array ("45"))

Scanner is still blocking, but not blocking us! Session output:

scala InterruptTest.scala 
Loading /home/stefan/lib/predef.scala...
import scala.annotation.tailrec
import scala.io._
import scala.util.Random

Loading InterruptTest.scala...
defined object InterruptTest
test: InterruptTest.type = InterruptTest$@65ae095c
i: 1    len: 1
i: 2    len: 2
i: 3    len: 3
i: 4    len: 5
i: 5    len: 7

// shortened

i: 33   len: 10143
i: 34   len: 12310
i: 35   len: 14883
i: 36   len: 17977
User request: quit, incomplete result!
i: 37   len: 21637

Welcome to Scala version 2.11.6 (OpenJDK 64-Bit Server VM, Java 1.8.0_151).
Type in expressions to have them evaluated.
Type :help for more information.

scala> 

caveats: You have to know before, that your code might loop endlessly. If you do so, another quick-and-dirty method would be an threshold and a counter, probably easier to integrate and faster to write. Don't mind the dirty, little var, which gets eliminated, when the bugs are eliminated.

The design might be improved, if you prefer the manual control over a counter. For example, Scanner can be moved into the Meanwhile object. A better name should be searched for. Maybe a more scala like Solution, maybe an Actor, I'm open for suggestions.

Upvotes: 2

Related Questions