Blog

Streamlining Scala Development with Nix and Scala-cli

28 Mar, 2024
Xebia Background Header Wave

Commencing the journey into Functional Programming in Scala has become notably simplified with the integration of Nix and Scala-cli-toolkit. This blog post is crafted to provide guidance and most of the commands that facilitate an easy transition from zero to proficient with Scala.

Introduction to Nix and Scala-cli

Nix Package Manager

Nix has emerged as a versatile package manager (it has been 20 years in the making) renowned for its innovative approach to dependency management and reproducible builds. Its declarative language and immutable infrastructure model offer developers control over their development environments. Nix stands out as a tool to empower teams to build, deploy, and manage software with efficiency and reliability.

Scala-cli Introduction

Scala-cli, an essential tool within the Scala ecosystem, streamlines the development process for Scala projects. Designed to enhance productivity and maintainability, Scala-cli provides developers with a unified platform for project management, dependency resolution, and build automation. By simplifying the setup and configuration of Scala projects, Scala-cli enables teams to focus on delivering high-quality code and innovative solutions without the burden of tedious manual tasks.

Dependencies: Just install Nix

We recommend following the installation guide from the source (https://nixos.org/download#nix-install-macos), but on Mac/Linux in one command

sh <(curl -L https://nixos.org/nix/install)
<a href="https://github.com/EstebanMarin/blogpost#scala-cli-introduction"></a>

check the installation

❯ nix --version
nix (Nix) 2.19.3

For the final step, we will enable nix Flakes. The easiest, less permanent way to enable it is by adding the flag to the nix command./nix

nix  --experimental-features 'nix-command flakes'

Typelevel nix develop

With just one command (flakes need to be enabled, follow through the suggested experimental flags when/if an error is raised) nix develop github:typelevel/typelevel-nix#application

This will provide us with a complete development Scala environment with several tools (not exhaustive): Coursier, JVM, node, Gitter8, scala-fmt, scala-fix, scala-cli.

$ nix develop github:typelevel/typelevel-nix#application
🔨 Welcome to typelevel-app-shell

[general commands]

  menu      - prints this menu
  sbt       - A build tool for Scala, Java and more
  scala-cli - Command-line tool to interact with the Scala language

[versions]

  Java - 17.0.1

$ sbt -version
sbt version in this project: 1.9.8
sbt script version: 1.9.8
$ exit

<a href="https://github.com/EstebanMarin/blogpost#typelevel-nix-develop"></a>

After exiting the shell, you will notice the underlying system has not changed and Scala is no longer available.

Scala-CLI

Up to this point, we have a shell with all the tooling necessary to be productive in Scala. Of those tools, we are going to explore scala-cli and how to be functional easily with typelevel:toolkit. First, let’s explore its main features.

Handle Scala versions, dependencies, and JVM with ease

Scala versions

❯ scala-cli run HelloWorld.scala
Compiling project (Scala 3.3.1, JVM (17))
Compiled project (Scala 3.3.1, JVM (17))
Hello, World!

❯ scala-cli --scala 2.13.6  HelloWorld.scala
Compiling project (Scala 2.13.6, JVM (17))
Compiled project (Scala 2.13.6, JVM (17))
Hello, World!<a href="https://github.com/EstebanMarin/blogpost#scala-versions"></a>

JVM versions

❯ scala-cli --jvm 8  HelloWorld.scala

Downloading JVM zulu:8
Compiling project (Scala 3.3.1, JVM (8))
Compiled project (Scala 3.3.1, JVM (8))<a href="https://github.com/EstebanMarin/blogpost#jvm-versions"></a>

Add dependencies to your REPL

❯ scala-cli --dep org.scalatest::scalatest:3.2.18
Welcome to Scala 3.3.1 (17.0.5, Java Java HotSpot(TM) 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.
scala> // scalatest available<a href="https://github.com/EstebanMarin/blogpost#add-dependencies-to-your-repl"></a>

There are many more features worth exploring, so visit the Scala-cli documentation for a full look.

Scripting 3 jobs in a functional way

We are going to write 3 files/scripts, and for that, we will need a basic vscode metals lsp setup.

$> scala-cil setup-ide .
$> touch HttpScript.scala 
$> code . 

Basic http4s client

//> using toolkit typelevel:0.1.21
//> using dependency org.http4s::http4s-dsl:0.23.25
//> using dependency org.http4s::http4s-ember-server:0.23.25

import cats.effect._
import cats.syntax.all._
import org.http4s._, org.http4s.dsl.io._, org.http4s.implicits._
import cats.effect._
import com.comcast.ip4s._
import org.http4s.HttpRoutes
import org.http4s.dsl.io._
import org.http4s.implicits._
import org.http4s.ember.server.EmberServerBuilder

object Http4sScript extends IOApp {

  val helloWorldService = HttpRoutes
    .of[IO] { case GET -> Root / "hello" / name =>
      Ok(s"Hello, $name.")
    }
    .orNotFound

  def run(args: List[String]): IO[ExitCode] =
    EmberServerBuilder
      .default[IO]
      .withHost(ipv4"0.0.0.0")
      .withPort(port"8080")
      .withHttpApp(helloWorldService)
      .build
      .use(_ => IO.never)
      .as(ExitCode.Success)
}
❯ scala-cli run Http4s.scala
Compiling project (Scala 3.3.1, JVM (17))
Compiled project (Scala 3.3.1, JVM (17))
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
# Separate shell
❯ http get localhost:8080/hello/xebia
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 13
Content-Type: text/plain; charset=UTF-8
Date: Sat, 17 Feb 2024 02:38:06 GMT

Hello, xebia.

Basic f2s streams

Also, run tests

//> using scala 3.3.1
//> using dep "org.scalamock::scalamock:6.0.0-M1"
//> using dep "org.scalatest::scalatest:3.2.18"

import org.scalamock.scalatest.MockFactory
import org.scalatest.funsuite.AnyFunSuite

class Example:
  def method(input: String): String = "placeholder"

class MockTest extends AnyFunSuite with MockFactory:
  test("Mocking Example.method") {
    val example = mock[Example]
    (example.method _).expects("input").returning("output")
    assert(example.method("input") == "output")
  }
❯ scala-cli test MockTest.scala
Compiling project (Scala 3.3.1, JVM (17))
Compiled project (Scala 3.3.1, JVM (17))
MockTest:
- Mocking Example.method
Run completed in 66 milliseconds.
Total number of tests run: 1
Suites: completed 1, aborted 0
Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
All tests passed.

Better shell scripting

Scala is now a scripting language. We recommend reading Scala-cli is great for shell script.

Summary

This blog primarily focuses on providing insights into setting up Scala and simplifying the process of creating practical applications. While we have covered a range of tools here, it is important to note that there is a wealth of information to delve into regarding each one. We aspire here to serve as a valuable resource for future reference, igniting inspiration, and encouraging further exploration and creation within the Scala ecosystem.

Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts