The following sections enlist a few final tips and tricks that you might find handy while working with the REPL.
As a reminder, this feature introduced in Chapter 8, Essential Properties of Modern Applications – Asynchrony and Concurrency, makes it easy to execute a full code snippet at once in the REPL. For instance, the following lines of command illustrate how the copy and paste feature in REPL helps the easy execution of code:
scala> :paste // Entering paste mode (ctrl-D to finish) [Copy a code block from somewhere with ctrl-C/ctrl-V] case class Person(name:String) val me = Person("Thomas") val you = Person("Current Reader") val we = List(you,me).map(_.name).reduceLeft(_+" & "+_) val status = s"$we have fun with Scala" [Once you are done with pasting the code block, press ctrl-D] // Exiting paste mode, now interpreting. defined class Person me: Person = Person(Thomas) you: Person = Person(Current Reader) we: String = Current Reader & Thomas status: String = Current Reader & Thomas have fun with Scala
The REPL has been a very helpful tool throughout this book to discover and experiment with the various features of Scala. Together with the Scala worksheets introduced in Chapter 3, Understanding the Scala Ecosystem, they enhance our productivity by providing continuous feedback and make our development, therefore, more agile. Sometimes, it is convenient to measure the time it takes to execute statements or code snippets in the REPL. This is why we have given one way of achieving this.
First, we define a help
function called using
that takes two parameters, first a param
argument of the type A
and second, a function argument f
that transforms the type of an argument from A
into B
:
scala> def using[A <: {def close(): Unit},B](param: A)(f: A=>B): B = try { f(param) } finally { param.close() } using: [A <: AnyRef{def close(): Unit}, B](param: A)(f: A => B)B
What using
does is to invoke the f(param)
function, wrapping it into a try {} finally{}
block. As the idea behind this function is to apply it on an I/O resource such as FileWriter
or PrintWriter
, we want to guarantee that we can close the resource no matter what. This is why you can see a param.close
call in the finally
block. That means the
param
argument cannot just be of any type A
; it must have the additional requirement to have a close
method. This is exactly what is declared at the beginning of the definition of the generic using
method (that is, [A <: {def close(): Unit}, B]
); the param
argument should be a subtype of A
that contains a method with the given signature.
In general, dealing with generic types is out of the scope of this book, and you don't need to really understand the previous definition to benefit from the using
function. The example illustrates, however, how powerful the use of generic types in Scala can be. The type system of Scala is extremely powerful and the compiler will help you very much when writing generic code, unlike the use of generics in Java.
Let's now include the
using
function into an appendToFile
function that will be responsible for logging the evaluation of the code we write in the REPL:
scala> def appendToFile(fileName:String, textData:String) = using (new java.io.FileWriter(fileName, true)){ fileWriter => using (new java.io.PrintWriter(fileWriter)) { printWriter => printWriter.println(textData) } } appendToFile: (fileName: String, textData: String)Unit
Finally, the following timeAndLogged
function is declared to wrap a body snippet entered in the REPL with both the logging and timing functionalities:
scala> def timedAndLogged[T](body: => T): T = { val start = System.nanoTime try { val result = body appendToFile("/tmp/repl.log",result.toString) result } finally println(" "+(System.nanoTime - start) + " nanos elapsed. ") } timedAndLogged: [T](body: => T)T
Until Scala 2.10.0, you could use the :wrap
method of the REPL power mode (accessible from the REPL via the > :power
command) to be able to execute all the console statements without further involvement of the timedAndLogged
function. The :wrap
feature has recently been removed from the Scala release, so you will have to explicitly wrap the code that you want timing or logging for in the timedAndLogged
method and therefore, do not need to involve the power mode of the REPL for that.
For instance, you can execute the following command:
scala> timedAndLogged{ val input = 2014 ; println(input) ; Thread.sleep(2000) ; input } 2014 2004778000 nanos elapsed. res0: Int = 2014
The /tmp/repl.log
file we specified in the timedAndLogged
function should, of course, contain the logged result, that is, 2014
.