Чистое решение для перехода на консоль REPL в середине выполнения программы

Есть ли какое-либо рабочее решение для перехода в консоль REPL с помощью Scala 2.10?

Это в основном для цели отладки - я хочу сделать паузу в середине выполнения и иметь консоль REPL, где я могу проверять значения и проверять логику программы, используя сложные выражения в моей программе в текущем состоянии выполнения. Те, кто запрограммирован в Ruby, могут знать аналогичную функцию: binding.pry.

AFAIK, Scala 2.9, а при использовании breakIf, но он был удален из более поздних версий. Использование ILoop кажется новым, но ввел проблемы из-за sbt не добавляя scala -library к пути к классам.

Несколько решений, таких как this и this, как представляется, предлагают хорошее обходное решение, но я считаю, что быть решением, где мне не нужно тратить часы или даже дни, чтобы заставить REPL работать.

Короче говоря, здесь задействовано гораздо больше шагов шаблона - это контрастирует с binding.pry, который является просто строкой кода без дополнительного шаблона.

Я не знаю, возникла ли проблема при выполнении программы как задачи sbt, а не при запуске исполняемого файла напрямую, но для целей разработки я в настоящее время запускаю и тестирую свою программу с помощью задачи sbt.

Ответ 1

Вы можете легко переопределить метод breakIf в своем коде. Я не думаю, что есть намного более чистый способ сделать это.

Сначала вам нужно добавить библиотеку компилятора scala к build.sbt

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

После этого вы можете реализовать 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()
  }

Я думаю, что это довольно прямолинейно, единственная сложная часть состоит в том, что вам нужно правильно настроить путь к классам. Вам нужно позвонить embeddedDefaults с классом из вашего проекта (см. Мой ответ на еще один вопрос).

Вы можете использовать новый breakIf следующим образом:

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

Где X - это только некоторые из ваших классов.

Я не знаю, отвечает ли это на ваш вопрос, потому что трудно измерить, что легко и что сложно.

Кроме того, в качестве побочного примечания - если вы хотите использовать его для целей отладки, почему бы не использовать отладчик. Я думаю, что большинство отладчиков могут подключаться к программе, останавливаться в точке останова и оценивать выражения в этом контексте.

Изменить

Похоже, что он не работает с текущей версией scala 2.10, рабочий код выглядит следующим образом:

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)

}

и использование похоже на

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

Ответ 2

Я смотрел на это недавно и обнаружил, что аммонит является достаточным решением для моих нужд.

  1. Добавьте аммонит в зависимости вашей библиотеки: libraryDependencies += "com.lihaoyi" % "ammonite" % "1.6.0" cross CrossVersion.full
  2. Вызвать Ammonite, куда вы хотите перейти в оболочку REPL: ammonite.Main().run()

Обратите внимание, что вам нужно передать любые переменные, которые вы хотите связать внутри run, например, run("var1" → var1). Взгляните на их пример - применение аммонита.