12

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.

Community
  • 1
  • 1
lolski
  • 16,231
  • 7
  • 34
  • 49
  • 3
    Hours? Days? At my office, they're prepared to fix a networking problem that cost me a month of headaches. When you're young, a month seems like an eternity. When you're older, a month isn't much compared to a life lived; but compared to the time left before eternity arrives, it is forever. – som-snytt Jul 11 '14 at 04:31
  • 3
    How do I apply to your office? – lolski Jul 11 '14 at 05:01
  • @lolski what is your question precisely? Don't you want to use `ILoop` at all - frankly speaking this looks like 10 lines of code? From where do you want to start your REPL, from your code compiled by sbt or from sbt task? – lpiepiora Jul 11 '14 at 20:38
  • @lpiepiora questions edited to be more precise – lolski Jul 12 '14 at 07:34

2 Answers2

5

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))
Community
  • 1
  • 1
lpiepiora
  • 13,659
  • 1
  • 35
  • 47
  • I think the solution suffers from the same issue as the one in this thread, though there is a workaround offered: http://stackoverflow.com/a/18227734/1622847. – lolski Jul 15 '14 at 04:24
  • Moreover, for some reason the console will just pause without giving you any prompt to enter expressions. This also happens when I use the solution on http://stackoverflow.com/a/18227734/1622847 I wonder if this is a Mac OS X issue. – lolski Jul 15 '14 at 04:25
  • @lolski when I tested in on Ubuntu it worked. I'll check it on Windows later, but I have no access to Mac OS X. – lpiepiora Jul 15 '14 at 07:21
  • I'll test it on those machine and report this if it is a Mac OS X specific issue. – lolski Jul 20 '14 at 05:37
  • As for your remark regarding the use of debugger -- while I understand your point completely, I find that REPL console is much more expressive. For example, in IDEA debugger, the expression that can be evaluated is quite limited. I'm not too sure how limited but I remember you can't do a pattern matching there. – lolski Jul 20 '14 at 05:37
  • @lolski sorry, I've forgotten to test it the other day on Windows. Now I did and it seems to work. Maybe you have something specific in your `build.sbt`, would you share this one?. I see your point regarding the IDEA debugger being limited, and I guess you're right. – lpiepiora Jul 20 '14 at 09:20
  • This horrible convolution actually works :) Is there a way to really REPL in the context of the break, rather than having to bind variables by hand? by the way, why not directly hardwire the class for embeddedDefaults inside the function rather than acquiring it as an argument? seems to work like that too. – matanster Sep 06 '14 at 16:43
  • @matt I dont know of other way, than binding variables. Regarding the `tag` parameter. This way it's more flexible, you can put the function to the library or whatever and the class is not hardcoded. It's an implicit so you don't have to pass it explicitly, which is more convenient than passing the class as a parameter. – lpiepiora Sep 07 '14 at 06:04
  • Thanks @lpiepiora but wouldn't you just want to use the same class every time you use this in your application? what am I missing about it? – matanster Sep 07 '14 at 08:45
  • This class is used to obtain classloader. You can package this (and perhaps other) functions in a jar, and use it as an utility in your project. When used like that you want to call it passing a class from your project and not the one from the utility jar. – lpiepiora Sep 07 '14 at 10:40
  • 1
    +1 from me, but could you provide a self-contained runnable example? I'm getting a `NullPointerException` whenever I do _anything_ whatsoever on the resulting REPL. – Erik Kaplun Jan 20 '15 at 19:05
  • @ErikAllik I ran my example (had it stashed locally), and I got also NPE, I'll take a look into it and will let you know. – lpiepiora Jan 20 '15 at 19:20
  • @ErikAllik if I remove `for in run := true` from my SBT project it actually works. Do you run it from SBT with the flag on, or as standalone (just asking, because I am pretty sure I run it with the flag on in the past)? – lpiepiora Jan 21 '15 at 07:12
  • I actually tried it both using a `.scala` script as well as using SBT (via a pack generated by sbt-pack); my SBT project has never had `for in run := true`; in fact, I don't even know what it does. – Erik Kaplun Jan 21 '15 at 10:24
  • @ErikAllik sorry I had no time to look at it earlier, I 've posted updated snipped that should work with latest Scala 2.10.4. It works better as standalone *.jar than from Sbt. Anyway it's quite unstable as the same code doesn't work against Scala 2.11.x. My personal opinion is that it's better to use a proper debugger ;) – lpiepiora Jan 25 '15 at 19:21
1

I was looking at this recently and found Ammonite to be a sufficient solution for my needs.

  1. Add Ammonite to your library dependencies: libraryDependencies += "com.lihaoyi" % "ammonite" % "1.6.0" cross CrossVersion.full
  2. Invoke Ammonite where you want to drop to the REPL shell: ammonite.Main().run()

Note that you have to pass any variables you want bound inside of run, e.g. run("var1" -> var1). Have a look at their example - Instantiating Ammonite.

borancar
  • 1,109
  • 8
  • 10