Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clean solution for dropping into REPL console in the middle of program execution

Is there any working solution for dropping into REPL console with for Scala 2.10?

This is mainly for debugging purpose - I want to pause in the middle of execution, and have a REPL console where I can inspect values and test the program's logic using complex expressions within my program at the current state of execution. Those who have programmed in Ruby might know similar function: the binding.pry.

AFAIK, Scala 2.9 and under used to have breakIf but it has been removed from the later versions. Using ILoop seems to be the new way but introduced issues due to sbt not adding scala-library to the classpath.

Several solutions such as this and this seem to offer a good workaround but my point is there must be a solution where I don't have to spend hours or even days just to make the REPL working.

In short, there's a lot more boilerplate steps involved - this is in contrast with binding.pry which is just a line of code with no additional boilerplate.

I am not aware if there's an issue introduced in executing the program as an sbt task as opposed to if running the program executable directly, but for development purpose I am currently running and testing my program using sbt task.

like image 991
lolski Avatar asked Jul 10 '14 10:07

lolski


1 Answers

You could easily reimplement the breakIf method in your code. I don't think there is much cleaner way of doing that.

First you have to add a scala compiler library to your build.sbt

libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value

Once that's done you can implement breakIf

import scala.reflect.ClassTag
import scala.tools.nsc.Settings
import scala.tools.nsc.interpreter.{ILoop, NamedParam}

def breakIf[T](assertion: => Boolean, args: NamedParam*)(implicit tag: ClassTag[T]) = {
    val repl = new ILoop()

    repl.settings = new Settings()
    repl.settings.embeddedDefaults[T]
    repl.settings.Yreplsync.value = true
    repl.in = repl.chooseReader(repl.settings)

    repl.createInterpreter()

    args.foreach(p => repl.bind(p.name, p.tpe, p.value))

    repl.loop()
    repl.closeInterpreter()
  }

I think it's pretty straight forward, the only tricky part is that you have to set-up the classpath properly. You need to call embeddedDefaults with a class from your project (see my answer to another question).

You can use the new breakIf as follows:

val x = 10
breakIf[X](assertion = true, NamedParam("x", "Int", x))

Where X is just some of your classes.

I don't know if this answers your question, because it's hard to measure what is easy and what is hard.

Additionally just as a side note - if you want to use it for debugging purposes, why not use a debugger. I guess most of the debuggers can connect to a program, stop at a breakpoint and evaluate expressions in that context.

Edit

Seems like it doesn't work on current release of Scala 2.10, the working code seems to be:

import scala.reflect.ClassTag
import scala.tools.nsc.Settings
import scala.tools.nsc.interpreter.{ILoop, NamedParam}

def breakIf[T](assertion: => Boolean, args: NamedParam*)(implicit tag: ClassTag[T]) = {

  val repl = new ILoop() {
    override protected def postInitialization(): Unit = {
      addThunk(args.foreach(p => intp.bind(p)))
      super.postInitialization()
    }
  }

  val settings = new Settings()

  settings.Yreplsync.value = true
  settings.usejavacp.value = true
  settings.embeddedDefaults[T]

  args.foreach(repl.intp.rebind)

  repl.process(settings)

}

and usage is like

  val x = 10
  breakIf[X](assertion = true, NamedParam("x", x))
like image 179
lpiepiora Avatar answered Sep 20 '22 14:09

lpiepiora