Chapter 1. Command-Line Tasks
Most likely, one of the first steps on your Scala 3 journey will involve working at the command line. For instance, after you install Scala as shown in “Installing Scala”, you might want to start the REPL—Scala’s Read/Eval/Print/Loop—by typing scala
at your operating system command line. Or you may want to create a little one-file “Hello, world” project and then compile and run it. Because these command-line tasks are where many people will start working with Scala, they’re covered here first.
The REPL is a command-line shell. It’s a playground area where you can run small tests to see how Scala and its third-party libraries work. If you’re familiar with Java’s JShell, Ruby’s irb
, the Python shell or IPython, or Haskell’s ghci
, Scala’s REPL is similar to all of these. As shown in Figure 1-1, just start the REPL by typing scala
at your operating system command line, then type in your Scala expressions, and they’ll be evaluated in the shell.
Any time you want to test some Scala code, the REPL is a terrific playground environment. There’s no need to create a full-blown project—just put your test code in the REPL and experiment with it until you know it works. Because the REPL is such an important tool, its most important features are demonstrated in the first two recipes of this chapter.
While the REPL is terrific, it’s not the only game in town. The Ammonite REPL was originally created for Scala 2, and it had many more features than the Scala 2 REPL, including:
-
The ability to import code from GitHub and Maven repositories
-
The ability to save and restore sessions
-
Pretty-printed output
-
Multiline editing
At the time of this writing Ammonite is still being ported to Scala 3, but many important features already work. See Recipe 1.3 for examples of how to use those features.
Finally, when you need to build Scala projects, you’ll typically use a build tool like sbt, which is demonstrated in Chapter 17. But if you ever want to compile and run a small Scala application, such as one that has just one or two files, you can compile your code with the scalac
command and run it with scala
, just like you do in Java with the javac
and java
commands. This process is demonstrated in Recipe 1.4, and after that, Recipe 1.6 shows how you can run applications that you package as a JAR file with the java
or scala
commands.
1.1 Getting Started with the Scala REPL
Solution
If you’ve used REPL environments in languages like Java, Python, Ruby, and Haskell, you’ll find the Scala REPL to be very familiar. To start the REPL, type scala
at your operating system command line. When the REPL starts up you may see an initial message, followed by a scala>
prompt:
$ scala Welcome to Scala 3.0 Type in expressions for evaluation. Or try :help. scala> _
The prompt indicates that you’re now using the Scala REPL. Inside the REPL environment you can try all sorts of different experiments and expressions:
scala> val x = 1 x: Int = 1 scala> val y = 2 y: Int = 2 scala> x + y res0: Int = 3 scala> val x = List(1, 2, 3) x: List[Int] = List(1, 2, 3) scala> x.sum res1: Int = 6
As shown in these examples:
-
After you enter your command, the REPL output shows the result of your expression, including data type information.
-
If you don’t assign a variable name, as in the third example, the REPL creates its own variable, beginning with
res0
, thenres1
, etc. You can use these variable names just as though you had created them yourself:
scala> res1.getClass res2: Class[Int] = int scala> res1 + 2 res3: Int = 8
Both beginning and experienced developers write code in the REPL every day to quickly see how Scala features and their own algorithms work.
Tab completion
There are a few simple tricks that can make using the REPL more effective. One trick is to use tab completion to see the methods that are available on an object. To see how tab completion works, type the number 1
, then a decimal, and then press the Tab key. The REPL responds by showing the dozens of methods that are available on an Int
instance:
scala> 1. != finalize round ## floatValue self % floor shortValue & formatted sign * getClass signum many more here ...
You can also limit the list of methods that are displayed by typing the first part of a method name and then pressing the Tab key. For instance, if you want to see all the methods available on a List
, type List(1).
followed by the Tab key, and you’ll see over two hundred methods. But if you’re only interested in methods on a List
that begin with the characters to
, type List(1).to
and then press Tab, and that output will be reduced to these methods:
scala> List(1).to to toIndexedSeq toList toSet toTraversable toArray toIterable toMap toStream toVector toBuffer toIterator toSeq toString
Discussion
I use the REPL to create many small experiments, and it also helps me understand some type conversions that Scala performs automatically. For instance, when I first started working with Scala and typed the following code into the REPL, I didn’t know what type the variable x
was:
scala> val x = (3, "Three", 3.0) val x: (Int, String, Double) = (3,Three,3.0)
With the REPL, it’s easy to run tests like this and then call getClass
on a variable to see its type:
scala> x.getClass val res0: Class[? <: (Int, String, Double)] = class scala.Tuple3
Although some of that result line is hard to read when you first start working with Scala, the text on the right side of the =
lets you know that the type is a Tuple3
class.
You can also use the REPL’s :type
command to see similar information, though it currently doesn’t show the Tuple3
name:
scala> :type x (Int, String, Double)
However, it’s generally helpful in many other instances:
scala> :type 1 + 1.1 Double scala> :type List(1,2,3).map(_ * 2.5) List[Double]
Though these are simple examples, you’ll find that the REPL is extremely helpful when you’re working with more complicated code and libraries you’re not familiar with.
Starting the REPL Inside sbt
You can also start a Scala REPL session from inside the sbt shell. As shown in Recipe 17.5, “Understanding Other sbt Commands”, just start the sbt shell inside an sbt project:
$ sbt MyProject> _
Then use either the console
or the consoleQuick
command from there:
MyProject> console scala> _
The console
command compiles the source code files in the project, puts them on the classpath, and starts the REPL. The consoleQuick
command starts the REPL with the project dependencies on the classpath, but without compiling project source code files. This second option is useful for times when your code isn’t compiling, or when you want to try some test code with your dependencies (libraries).
See Also
If you like the idea of a REPL environment but want to try alternatives to the default REPL, there are several great free alternatives:
-
The Ammonite REPL has more features than the REPL, and it’s demonstrated in Recipe 1.3.
-
Scastie is a web-based alternative to the REPL that supports sbt options and lets you add external libraries into your environment.
-
ScalaFiddle is also a web-based alternative.
-
The IntelliJ IDEA and Visual Studio Code (VS Code) IDEs both have worksheets, which are similar to the REPL.
1.2 Loading Source Code and JAR Files into the REPL
Solution
Use the :load
command to read source code files into the REPL environment. For example, given this code in a file named Person.scala, in a subdirectory named
models:
class
Person
(
val
name
:
String
):
override
def
toString
=
name
you can load that source code into the running REPL environment like this:
scala> :load models/Person.scala // defined class Person
After the code is loaded into the REPL, you can create a new Person
instance:
scala> val p = Person("Kenny") val p: Person = Kenny
Note, however, that if your source code has a package
declaration:
// Dog.scala file
package
animals
class
Dog
(
val
name
:
String
)
the :load
command will fail:
scala> :load Dog.scala 1 |package foo |^^^ |Illegal start of statement
Source code files can’t use packages in the REPL, so for situations like this you’ll need to compile them into a JAR file, and then include them in the classpath when you start the REPL. For instance, this is how I use version 0.2.0 of my Simple Test library with the Scala 3 REPL:
// start the repl like this $ scala -cp simpletest_3.0.0-0.2.0.jar scala> import com.alvinalexander.simpletest.SimpleTest.* scala> isTrue(1 == 1) true
At the time of this writing you can’t add a JAR to an already running REPL session, but that feature may be added in the future.
Discussion
Another good thing to know is that compiled class files in the current directory are automatically loaded into the REPL. For example, if you put this code in a file named Cat.scala and then compile it with scalac
, that creates a Cat.class file:
case
class
Cat
(
name
:
String
)
If you start the REPL in the same directory as that class file, you can create a new Cat
:
scala> Cat("Morris") val res0: Cat = Cat(Morris)
On Unix systems you can use this technique to customize your REPL environment. To do so, follow these steps:
-
Create a subdirectory in your home directory named repl. In my case, I create this directory as /Users/al/repl. (Use any name for this directory that you prefer.)
-
Put any *.class files you want in that directory.
-
Create an alias or shell script you can use to start the REPL in that directory.
On my system I put a file named Repl.scala in my ~/repl directory, with these contents:
import
sys
.
process
.
*
def
clear
=
"clear"
.
!
def
cmd
(
cmd
:
String
)
=
cmd
.
!!
def
ls
(
dir
:
String
)
=
println
(
cmd
(
s"ls -al
$
dir
"
))
def
help
=
println
(
"\n=== MY CONFIG ==="
)
"cat /Users/Al/repl/Repl.scala"
.
!
case
class
Person
(
name
:
String
)
val
nums
=
List
(
1
,
2
,
3
)
I then compile that code with scalac
to create its class files in that directory. Then I create and use this alias to start the REPL:
alias
repl
=
"cd ~/repl; scala; cd -"
That alias moves me to the ~/repl directory, starts the REPL, and then returns me to my current directory when I exit the REPL.
As another approach, you can create a shell script named repl
, make it executable, and place it in your ~/bin directory (or anywhere else on your PATH
):
#!/bin/sh
cd
~/
repl
scala
Because a shell script is run in a subprocess, you’ll be returned to your original directory when you exit the REPL.
By using this approach, your custom methods will be loaded into the REPL when it starts up, so you can use them inside the scala
shell:
clear
// clear the screen
cmd
(
"ps"
)
// run the 'ps' command
ls
(
"."
)
// run 'ls' in the current directory
help
// displays my Repl.scala file as a form of help
Use this technique to preload any other custom definitions you’d like to use in the REPL.
1.3 Getting Started with the Ammonite REPL
Solution
The Ammonite REPL works just like the Scala REPL: just download and install it, then start it with its amm
command. As with the default Scala REPL, it evaluates Scala expressions and assigns a variable name if you don’t provide one:
@ val x = 1 + 1 x: Int = 2 @ 2 + 2 res0: Int = 4
But Ammonite has many additional features. You can change the shell prompt with this command:
@ repl.prompt() = "yo: " yo: _
Next, if you have these Scala expressions in a file named Repl.scala, in a subdirectory named foo:
import
sys
.
process
.
*
def
clear
=
"clear"
.
!
def
cmd
(
cmd
:
String
)
=
cmd
.
!!
def
ls
(
dir
:
String
)
=
println
(
cmd
(
s"ls -al
$
dir
"
))
you can import them into your Ammonite REPL with this command:
@ import $file.foo.Repl, Repl.*
Then you can use those methods inside Ammonite:
clear
// clear the screen
cmd
(
"ps"
)
// run the 'ps' command
ls
(
"/tmp"
)
// use 'ls' to list files in /tmp
Similarly, you can import a JAR file named simpletest_3.0.0-0.2.0.jar in a subdirectory named foo into your amm
REPL session using the Ammonite $cp
variable:
// import the jar file
import
$cp
.
foo
.
`simpletest_3.0.0-0.2.0.jar`
// use the library you imported
import
com
.
alvinalexander
.
simpletest
.
SimpleTest
.
*
isTrue
(
1
==
1
)
The import ivy
command lets you import dependencies from Maven Central (and other repositories) and use them in your current shell:
yo: import $ivy.`org.jsoup:jsoup:1.13.1` import $ivy.$ yo: import org.jsoup.Jsoup, org.jsoup.nodes.{Document, Element} import org.jsoup.Jsoup yo: val html = "<p>Hi!</p>" html: String = "<p>Hi!</p>" yo: val doc: Document = Jsoup.parse(html) doc: Document = <html> ... yo: doc.body.text res2: String = "Hi!"
Ammonite’s built-in time
command lets you time how long it takes to run your code:
@ time(Thread.sleep(1_000)) res2: (Unit, FiniteDuration) = ((), 1003788992 nanoseconds)
Ammonite’s auto-complete ability is impressive. Just type an expression like this, then press Tab after the decimal:
@ Seq("a").map(x => x.
When you do so, Ammonite displays a long list of methods that are available on x
—which is a String
—beginning with these methods:
def intern(): String def charAt(x$0: Int): Char def concat(x$0: String): String much more output here ...
This is nice because it shows you not only the method names but also their input parameters and return type.
Discussion
Ammonite’s list of features is long. Another great one is that you can use a startup configuration file, just like using a Unix .bashrc or .bash_profile startup file. Just put some expressions in a ~/.ammonite/predef.sc file:
import
sys
.
process
.
*
repl
.
prompt
()
=
"yo: "
def
clear
=
"clear"
.
!
def
cmd
(
cmd
:
String
)
=
cmd
.
!!
def
ls
(
dir
:
String
)
=
println
(
cmd
(
s"ls -al
$
dir
"
))
def
reset
=
repl
.
sess
.
load
()
// similar to the scala repl ':reset' command
Then, when you start the Ammonite REPL, your prompt will be changed to yo:
, and those other methods will be available to you.
One more great feature is that you can save a REPL session, and it will save everything you’ve done to this point. To test this, create a variable in the REPL, and then save your session:
val
remember
=
42
repl
.
sess
.
save
()
Then create another variable:
val
forget
=
0
Now reload the session, and you’ll see that the remember
variable is still available, but the forget
variable has been forgotten, as desired:
@ repl.sess.load() res3: SessionChanged = SessionChanged(removedImports = Set('forget), addedImports = Set(), removedJars = Set(), addedJars = Set()) @ remember res4: Int = 42 @ forget |val res5 = forget | ^^ | Not found: forget
You can also save and load multiple sessions by giving them different names, like this:
// do some work
val
x
=
1
repl
.
sess
.
save
(
"step 1"
)
// do some more work
val
y
=
2
repl
.
sess
.
save
(
"step 2"
)
// reload the first session
repl
.
sess
.
load
(
"step 1"
)
x
// this will be found
y
// this will not be found
See the Ammonite documentation for details on more features.
1.4 Compiling with scalac and Running with scala
Solution
Compile small programs with scalac
, and run them with scala
. For example, given this Scala source code file named Hello.scala:
@main
def
hello
=
println
(
"Hello, world"
)
compile it at the command line with scalac
:
$ scalac Hello.scala
Then run it with scala
, giving the scala
command the name of the @main
method you created:
$ scala hello Hello, world
Discussion
Compiling and running classes is the same as Java, including concepts like the classpath. For instance, imagine that you have a class named Pizza
in a file named Pizza.scala, and that it depends on a Topping
type:
class
Pizza
(
val
toppings
:
Topping
*
):
override
def
toString
=
toppings
.
toString
Assuming that Topping
is defined like this:
enum
Topping
:
case
Cheese
,
Mushrooms
and that it’s in a file named Topping.scala, and has been compiled to Topping.class in a subdirectory named classes, compile Pizza.scala like this:
$ scalac -classpath classes Pizza.scala
Note that the scalac
command has many additional options you can use. For instance, if you add the -verbose
option to the previous command, you’ll see hundreds of lines of additional output that show how scalac
is working. These options may change over time, so use the -help
option to see additional information:
$ scalac -help Usage: scalac <options> <source files> where possible standard options include: -P Pass an option to a plugin, e.g. -P:<plugin>:<opt> -X Print a synopsis of advanced options. -Y Print a synopsis of private options. -bootclasspath Override location of bootstrap class files. -classpath Specify where to find user class files. much more output here ...
Main methods
While we’re talking about compiling main
methods, it helps to know that they can be declared in two ways with Scala 3:
-
Using the
@main
annotation on a method -
Declaring a
main
method with the proper signature in anobject
As shown in the Solution, a simple @main
method that takes no input parameters can be declared like this:
@main
def
hello
=
println
(
"Hello, world"
)
You can also declare an @main
method to take whatever parameters you want on the command line, such as taking a String
and Int
in this example:
@main
def
hello
(
name
:
String
,
age
:
Int
):
Unit
=
println
(
s"Hello,
$
name
, I think you are
$
age
years old."
)
After that code is compiled with scalac
, it can be run like this:
$ scala hello "Lori" 44 Hello, Lori, I think you are 44 years old.
For the second approach, declaring a main
method inside an object
is just like declaring a main method in Java, and the signature for the Scala main
method must look like this:
object
YourObjectName
:
// the method must take `Array[String]` and return `Unit`
def
main
(
args
:
Array
[
String
]):
Unit
=
// your code here
If you’re familiar with Java, that Scala code is analogous to this Java code:
public
class
YourObjectName
{
public
static
void
main
(
String
[]
args
)
{
// your code here
}
}
1.5 Disassembling and Decompiling Scala Code
Solution
The main way to disassemble Scala code is with the javap
command. You may also be able to use a decompiler to convert your class files back to Java source code, and this option is shown in the Discussion.
Using javap
Because your Scala source code files are compiled into regular JVM class files, you can use the javap
command to disassemble them. For example, assume that you’ve created a file named Person.scala
that contains this source code:
class
Person
(
var
name
:
String
,
var
age
:
Int
)
Next, compile that file with scalac
:
$ scalac Person.scala
Now you can disassemble the resulting Person.class file into its signature using javap
, like this:
$ javap -public Person Compiled from "Person.scala" public class Person { public Person(java.lang.String, int); public java.lang.String name(); public void name_$eq(java.lang.String); public int age(); public void age_$eq(int); }
This shows the public signature of the Person
class, which is its public API, or interface. Even in a simple example like this you can see the Scala compiler doing its work for you, creating methods like name()
, name_$eq
, age()
, and age_$eq
. The Discussion shows more detailed examples.
If you want, you can see additional information with the javap -private
option:
$ javap -private Person Compiled from "Person.scala" public class Person { private java.lang.String name; // new private int age; // new public Person(java.lang.String, int); public java.lang.String name(); public void name_$eq(java.lang.String); public int age(); public void age_$eq(int); }
The javap
has several more options that are useful. Use the -c
option to see the actual commands that comprise the Java bytecode, and add the -verbose
option to that to see many more details. Run javap -help
for details on all options.
Discussion
Disassembling class files with javap
can be a helpful way to understand how Scala works. As you saw in the first example with the Person
class, defining the constructor parameters name
and age
as var
fields generates quite a few methods for you.
As a second example, take the var
attribute off both of those fields, so you have this class definition:
class
Person
(
name
:
String
,
age
:
Int
)
Compile this class with scalac
, and then run javap
on the resulting class file. You’ll see that this results in a much shorter class signature:
$ javap -public Person Compiled from "Person.scala" public class Person { public Person(java.lang.String, int); }
Conversely, leaving var
on both fields and turning the class into a case class significantly expands the amount of code Scala generates on your behalf. To see this, change the code in Person.scala so you have this case class:
case
class
Person
(
var
name
:
String
,
var
age
:
Int
)
When you compile this code, it creates two output files, Person.class and Person$.class. Disassemble those two files using javap
:
$ javap -public Person Compiled from "Person.scala" public class Person implements scala.Product,java.io.Serializable { public static Person apply(java.lang.String, int); public static Person fromProduct(scala.Product); public static Person unapply(Person); public Person(java.lang.String, int); public scala.collection.Iterator productIterator(); public scala.collection.Iterator productElementNames(); public int hashCode(); public boolean equals(java.lang.Object); public java.lang.String toString(); public boolean canEqual(java.lang.Object); public int productArity(); public java.lang.String productPrefix(); public java.lang.Object productElement(int); public java.lang.String productElementName(int); public java.lang.String name(); public void name_$eq(java.lang.String); public int age(); public void age_$eq(int); public Person copy(java.lang.String, int); public java.lang.String copy$default$1(); public int copy$default$2(); public java.lang.String _1(); public int _2(); } $ javap -public Person$ Compiled from "Person.scala" public final class Person$ implements scala.deriving.Mirror$Product, java.io.Serializable { public static final Person$ MODULE$; public static {}; public Person apply(java.lang.String, int); public Person unapply(Person); public java.lang.String toString(); public Person fromProduct(scala.Product); public java.lang.Object fromProduct(scala.Product); }
As shown, when you define a class as a case class, Scala generates a lot of code for you, and this output shows the public signature for that code. See Recipe 5.14, “Generating Boilerplate Code with Case Classes”, for a detailed discussion of this code.
About Those .tasty Files
You may have noticed that in addition to .class files, Scala 3 also generates .tasty files during the compilation process. These files are generated in what’s known as a TASTy format, where the acronym TASTy comes from the term typed abstract syntax trees.
Regarding what these files are, the TASTy Inspection documentation states, “TASTy files contain the full typed tree of a class including source positions and documentation. This is ideal for tools that analyze or extract semantic information from the code.”
One of their uses is for integration between Scala 3 and Scala 2.13+. As this Scala forward compatibility page states, “Scala 2.13 can read these (TASTy) files to learn, for example, which terms, types and implicits are defined in a given dependency, and what code needs to be generated to use them correctly. The part of the compiler that manages this is known as the Tasty Reader.”
See Also
-
In my “How to Create Inline Methods in Scala 3” blog post, I show how to use this technique to understand
inline
methods. -
You may also be able to use decompilers to convert .class files into Java code. I occasionally use a tool named JAD, which was discontinued in 2001, but amazingly it’s still able to at least partially decompile class files twenty years later. A much more modern decompiler named CFR was also mentioned on the Scala Gitter channel.
For more information on TASTy and .tasty files, see these resources:
1.6 Running JAR Files with Scala and Java
Solution
First, create a basic sbt project, as shown in Recipe 17.1, “Creating a Project Directory Structure for sbt”. Then add sbt-assembly into the project configuration by adding this line to the project/plugins.sbt file:
// note: this version number changes several times a year
addSbtPlugin
(
"com.eed3si9n"
%
"sbt-assembly"
%
"0.15.0"
)
Then put this Hello.scala source code file in the root directory of that project:
@main
def
hello
=
println
(
"Hello, world"
)
Next, create a JAR file with either the assembly
or show assembly
command in the sbt shell:
// option 1 sbt:RunJarFile> assembly // option 2: shows the output file location sbt:RunJarFile> show assembly [info] target/scala-3.0.0/RunJarFile-assembly-0.1.0.jar
As shown, the output of the show assembly
command prints the location where the output JAR file is written. This file begins with the name RunJarFile because that’s the value of the name
field in my build.sbt file. Similarly, the 0.1.0 portion of the filename comes from the version
field in that file:
lazy
val
root
=
project
.
in
(
file
(
"."
))
.
settings
(
name
:=
"RunJarFile"
,
version
:=
"0.1.0"
,
scalaVersion
:=
"3.0.0"
)
Next, create an Example subdirectory, move into that directory, and copy the JAR file into that directory:
$ mkdir Example $ cd Example $ cp ../target/scala-3.0.0/RunJarFile-assembly-0.1.0.jar .
Because the sbt-assembly plugin packages everything you need into the JAR file, you can run the hello
main method with this scala
command:
$ scala -cp "RunJarFile-assembly-0.1.0.jar" hello Hello, world
Note that if your JAR file contains multiple @main
methods in packages, you can run them with similar commands, specifying the full path to the methods at the end of the command:
scala
-
cp
"RunJarFile-assembly-0.1.0.jar"
com
.
alvinalexander
.
foo
.
mainMethod1
scala
-
cp
"RunJarFile-assembly-0.1.0.jar"
com
.
alvinalexander
.
bar
.
mainMethod2
Discussion
If you (a) attempt to run your JAR file with the java
command, or (b) create the JAR file with sbt package
instead of sbt assembly
, you’ll need to manually add your JAR file dependencies to your classpath. For example, when running a JAR file with the java
command, you’ll need to use a command like this:
$ java -cp "~/bin/scala3/lib/scala-library.jar:my-packaged-jar-file.jar" ↵ foo.bar.Hello Hello, world
Note that the entire java
command should be on one line, including the foo.bar.Hello
portion at the end of the line.
For this approach you need to find the scala-library.jar file. In my case, because I manage the Scala 3 distribution manually, I found it in the directory shown. If you’re using a tool like Coursier to manage your Scala installation, the files it downloads can be found under these directories:
-
On macOS: ~/Library/Caches/Coursier/v1
-
On Linux: ~/.cache/coursier/v1
-
On Windows: %LOCALAPPDATA%\Coursier\Cache\v1, which, for a user named Alvin, typically corresponds to C:\Users\Alvin\AppData\Local\Coursier\Cache\v1
See the Coursier Cache page for up-to-date details on these directory locations.
Why use sbt-assembly?
Note that if your application uses managed or unmanaged dependencies and you use sbt package
instead of sbt assembly
, you’ll have to understand all of those dependencies and their transitive dependencies, find those JAR files, and then include them in the classpath setting. For that reason, the use of sbt assembly
or a similar tool is strongly recommended.
See Also
-
See Recipe 17.11, “Deploying a Single Executable JAR File”, for more details on how to configure and use sbt-assembly.
Get Scala Cookbook, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.