Last updated on

Degrees converter

The goal of this lab is to familiarize yourself with the tools and environment of this class. Even though the grade in this lab won’t count towards your final score, it’s essential to complete this lab carefully.

Obtaining the lab files

Start by ensuring you’ve completed the Tools Setup instructions.

Then, clone the lab repository:

$ git clone https://gitlab.epfl.ch/lamp/cs-214/degrees.git

For now, do not worry about what the git clone command does. We will cover this in a later lecture.

The $ is called the prompt and isn’t part of the command. It’s a convention used to indicate that you should type the text after the $ sign and then press the Enter or key to execute the command.

This will create a directory named degrees containing the lab project files. You can change your working directory by running the change directory (cd) command:

$ cd degrees

Now that we’ve obtained the lab, let’s take a look at its structure:

.
├── build.sbt
├── project
│   └── ...
└── src
    ├── main
    │   └── scala
    │       ├── degrees
    │       │   ├── cli
    │       │   │   └── main.scala
    │       │   └── convert.scala
    │       └── examples.worksheet.sc
    └── test
        └── scala
            └── degrees
                └── ConvertTest.scala

Using SBT and running the tests

SBT stands for Scala Build Tool, it’s a dependency manager and build tool for Scala. We will use it to compile our code and run the tests.

Learn more about SBT

On one hand, SBT is a dependency manager, allowing developers to specify and fetch libraries their code relies upon. This ensures consistent and reproducible builds, this is similar, for example, to how NPM handles JavaScript dependencies. On the other hand, SBT also functions as a build tool, like the classic ‘Make’ for C/C++. It can compile Scala code, run tests, and package applications into deployable units.

There are other build tools for Scala—such as Scala CLI or Mill, that are worth checking out— but SBT is currently the most popular one, and the one we will use in this course.

At the root of the project, you’ll find a file named build.sbt. This file is used by SBT to know how to build your project. You don’t need to modify it or to understand it for now, but you can take a look at it if you’re curious. You will see that it contains three lines: the first line defines the name of the project, the second line defines the version of Scala we are using, and the third line adds a dependency on MUnit, a library that we will use for testing.

Start SBT by running the following command in the project’s root directory (the directory that you cloned from git and that contains the build.sbt file):

$ sbt

Once it’s finished starting (this may take a while), you’ll be able to enter SBT commands:

The first time you’ll run test in a lab you should see many errors: that’s normal, your job is to make the tests pass!

Let’s look at the output of the test command in more details:

$ sbt
[info] welcome to sbt 1.10.1 (Eclipse Adoptium Java 17.0.6)
[info] loading settings for project degrees-build-build-build from metals.sbt ...
 ⋮ [some lines omitted]
[info] started sbt server
sbt:degrees> test
[info] compiling 2 Scala sources to /Users/mbovel/degrees/target/scala-3.3.1/classes ...
[info] compiling 1 Scala source to /Users/mbovel/degrees/target/scala-3.3.1/test-classes ...
degrees.ConvertTest:
  + celsiusToFahrenheit(0) should be 32 (1pt) 0.007s
  + celsiusToFahrenheit(100) should be 212 (1pt) 0.001s
==> X degrees.ConvertTest.fahrenheitToCelsius(32) should be 0 (1pt)  0.012s munit.ComparisonFailException: /Users/mbovel/degrees/src/test/scala/degrees/ConvertTest.scala:13 values are not the same expected: 0.0 but was: 42.0
12:  test("fahrenheitToCelsius(32) should be 0 (1pt)"):
13:    assertEqualsDouble(fahrenheitToCelsius(32), 0.0, DELTA)
14:
    at munit.Assertions.failComparison(Assertions.scala:274)
==> X degrees.ConvertTest.fahrenheitToCelsius(212) should be 100 (1pt)  0.0s munit.ComparisonFailException: /Users/mbovel/degrees/src/test/scala/degrees/ConvertTest.scala:16 values are not the same expected: 100.0 but was: 42.0
15:  test("fahrenheitToCelsius(212) should be 100 (1pt)"):
16:    assertEqualsDouble(fahrenheitToCelsius(212), 100.0, DELTA)
17:
    at munit.Assertions.failComparison(Assertions.scala:274)
==> X degrees.ConvertTest.celsiusToFahrenheit and fahrenheitToCelsius should be inverse functions (2pts)  0.003s munit.ComparisonFailException: /Users/mbovel/degrees/src/test/scala/degrees/ConvertTest.scala:20 values are not the same expected: -100.0 but was: 42.0
19:    for v <- -100 to 100 do
20:      assertEqualsDouble(fahrenheitToCelsius(celsiusToFahrenheit(v)), v.toDouble, DELTA)
21:      assertEqualsDouble(celsiusToFahrenheit(fahrenheitToCelsius(v)), v.toDouble, DELTA)
    at munit.Assertions.failComparison(Assertions.scala:274)
[error] Failed: Total 5, Failed 3, Errors 0, Passed 2
[error] Failed tests:
[error]         degrees.ConvertTest
[error] (Test / test) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 4 s, completed Sep 15, 2023, 7:51:51 PM

This tells us several things:

You’ll need to go ahead and implement the fahrenheitToCelsius function to fix these test failures!

Starting VS Code

(If you are still inside SBT, exit it by entering exit at the SBT prompt.)

To start VS Code, run the following in the project directory (the same directory where you previously ran sbt).

$ code .

In the terminal, a . signifies the current directory. It is important to include in the command above so that VS Code runs in the correct directory and is able to import the project.

If you see an error message, “Expected ID character […]”, it means you’re still inside SBT. You can exit SBT by typing exit and pressing Enter or .

The first time the IDE starts, it will take some time to download more components. Wait until it asks you to import the build, and then click “Import build”:

You’ll need to wait a bit more for the import to finish. If an error occurs, try closing and restarting VS Code in the same way you started it above.

Digging further

It’s now time to dig in! Earlier, we saw a failing test. The stack trace told us that it was failing on line 10 of the file src/test/scala/degrees/ConvertTest.scala.

Here is the source code of the failing test:

test("fahrenheitToCelsius(32) should be 0 (1pt)"):
  assertEqualsDouble(fahrenheitToCelsius(32), 0.0, DELTA)

src/test/scala/degrees/ConvertTest.scala

The first line gives a name to the test. The second line runs fahrenheitToCelsius(32) and tests that it equals 0.0 (up to some delta, because we are dealing with floating-point numbers), but in our case we never got to this point because an exception was thrown. Recall that the second line of the stack trace was:

==> X degrees.ConvertTest.fahrenheitToCelsius(32) should be 0 (1pt)  0.012s munit.ComparisonFailException: /Users/mbovel/degrees/src/test/scala/degrees/ConvertTest.scala:13 values are not the same expected: 0.0 but was: 42.0

This tells us that the exception was thrown when calling the fahrenheitToCelsius while testing the output of fahrenheitToCelsius(32), in the ConvertTest.scala file at line 13.

We can hover over the call to fahrenheitToCelsius in the test method for further information:

If hovering doesn’t show this, see the Troubleshooting section.

The hover text has two lines. The first line is:

def fahrenheitToCelsius(fahrenheit: Double): Double

This means that fahrenheitToCelsius is a method that takes a Double argument and returns a Double.

The second line is the documentation of fahrenheitToCelsius. We can jump to the definition of fahrenheitToCelsius by clicking on it while pressing Ctrl (or Cmd on Mac), or by right clicking on it and selecting “Go to Definition”. There, we see:

/** Converts a temperature in Fahrenheit degrees to Celsius */
def fahrenheitToCelsius(fahrenheit: Double): Double =
  42.0

src/main/scala/degrees/convert.scala

Now we know why the test failed: fahrenheitToCelsius always returns 42.0!

Once you’ve fixed this method, you can run test from SBT again to see if the test passed.

If you want to run a single test instead of all tests, you can use the testOnly command in the SBT shell to match on the name of the test. For example:

sbt:degrees> testOnly -- "*fahrenheitToCelsius(32)*"

This will match and run the single test degrees.ConvertTest.fahrenheitToCelsius(32) should be 0 (1pt). (The first * means anything can appear before fahrenheitToCelsius(32) and the second * means anything can appear after.)

You now know enough to be able to work with the IDE. Here are some additional tips:

Running your code

Writing code and running tests is nice, but sometimes more direct feedback is useful, like when you want to experiment with Scala, or try out some methods that you implemented. You can do this with a worksheet, the Scala REPL, or a main function.

The worksheet mode

A worksheet is a file where every line of code written in the IDE is executed and its output displayed as a comment. Any file that ends in .worksheet.sc in src/main/scala is considered to be a worksheet by the IDE.

There is a worksheet in this example project at src/main/scala/degrees/examples.worksheet.sc. You can open it by clicking on it in the file explorer on the left.

Inside this file, you can type any Scala code. The worksheet will be automatically run when the code is saved. Each line of code will be executed one by one and its output will be shown in green on the right.

If your IDE is configured correctly, you should see the following initial output:

If no output is shown, see the Troubleshooting section, and ask a member of the teaching staff for help if you can’t get it to work.

The REPL

If you are more of a terminal person, you can also use the Scala REPL for similar purposes. REPL stands for Read-Eval-Print-Loop, it’s a program that reads Scala expressions, evaluates them, prints the result, and then loops back to read the next expression.

After having started SBT, you can start the REPL by typing console, you will see the following prompt:

scala>

At this point you can write any Scala expression you want, for example:

scala> val x = 41 + 1
val x: Int = 42

If you write an expression without wrapping it in a val or a def, the REPL will give it a name for you, starting with res:

scala> 41 + 1
val res0: Int = 42

scala> 41.5 + 0.5
val res1: Double = 42.0

scala> "Hello".isBlank
val res2: Boolean = false

The functions and classes of the lab are available inside the REPL, so you can, for instance, import all the methods fahrenheitToCelsius from the degrees package by typing:

scala> import degrees.fahrenheitToCelsius

scala> fahrenheitToCelsius(0.0)
val res4: Double = 42.0

You can enter a multi-line expression in the REPL by using Alt+Enter at the ends of lines instead of Enter:

scala> if 1 == 1 then
     |   "a"
     | else
     |   "b"
val res5: String = a

In order to exit the Scala REPL and go back to SBT, type :quit or press Ctrl+D.

Running a main function

The last way to run your code is to run a main function. A main function is a method designed to be called from the command line. It is similar to a main method in Java. We will come back to this later in the course.

In this lab, there is a main called main defined for you in the cli package. You do not need to understand or modify this code yet.

However, know that you can run this main function by typing run in the SBT console, followed by the arguments expected by this method. In this lab, the main function defined for you expects two arguments: the first one is the temperature to convert, and the second one is the unit of the temperature to convert. For example:

sbt:degrees> run 30 c
[info] running degrees.main 30 c
30°C = 86.00°F
[success] Total time: 1 s, completed Sep 14, 2023, 7:46:27 PM
sbt:degrees> run 20 f
[info] running degrees.main 20 f
20°F = 42.00°C
[success] Total time: 0 s, completed Sep 14, 2023, 7:47:32 PM

If you run the method with the wrong number of arguments, or with invalid arguments, you will get an error:

sbt:degrees> run
[info] running degrees.main 
Illegal command line: more arguments expected

sbt:degrees> run 40 m
[info] running degrees.main 40 m
[error] java.lang.IllegalArgumentException: Unknown unit m
[error] 	at degrees.main$package$.main(main.scala:9)
...

Submitting your work

If all the tests run successfully, then you are done—congratulations!

To turn in your lab, submit the convert.scala file (and only this file) to Moodle.

On the assignment page:

  1. Click “Add submission”.
  2. Drop the convert.scala file in the “File submissions” box.
  3. Click “Save changes”.
  4. Click “Submit assignment”.
  5. Confirm that you respected the rules by checking the box next to “This assignment is my own work”.
  6. Click “Continue”.

Double-check that:

Troubleshooting

SBT fails to start

If you see any kind of error when SBT starts that prevents you from using it, try cleaning the project cache by running:

$ git clean -Xdf

(To understand this command, consult git clean documentation.)

Then, restart SBT. If this still doesn’t work, try deleting the global SBT cache:

IDE features like hover text, go-to-definition, or worksheets do not work

It’s likely that the build wasn’t imported. Try importing it manually: click on the “m” logo in the left bar, and then in the sidebar that appears, click on “Import build”, and then wait a bit.

If things still don’t work, try restarting VS Code (launch it in the same way you started it before, using code . from the project directory). If you’re still having issues, try clicking on “Clean compile workspace” from the same sidebar.

Import errors for external packages

Some of our labs use advanced features of SBT. If you try to do these labs with the default VS Code configuration, you may run into issues, such as missing completions or import errors. These issues will not prevent the code from compiling and running correctly, because they only concern the IDE support for Scala, not the project itself. IDE support is provided by software called Metals.

To fix these issues, you can ask Metals to talk to SBT directly, rather than using the default build tool (called “Bloop”). To do this just once:

  1. Press Ctrl+Shift+P (“Show All Commands”).
  2. Type “metals switch” and select the item “Metals: Switch build server”.
  3. When prompted, choose “SBT” instead of “Bloop”.

To make this switch permanent, you can either add "defaultBspToBuildTool": true to VS Code’s settings.json (at either the project level or for all projects), or click the gears icon to open the settings menu and search for “metals bsp” to find the corresponding setting in the UI.

Note: It’s not safe to run multiple copies of SBT in parallel. So, if you configure Metals to use SBT as above, then to run your code or tests, start the console, etc. using SBT at the command line, you should use sbt --client instead of just sbt. If nothing appears when you type commands into SBT, try sbt -Djline.terminal=none --client instead.

Last resort

If you’re still having issues, come to a lab or exercise session and ask a staff member for help.