Skip to content
Volume S · The TensorCore.si Library

Scala

Functional and object-oriented power on the JVM.

Scala blends object-oriented and functional programming on the JVM. Apache Spark, the most widely used engine for big data and ML pipelines, is written in Scala, as are Kafka's original core and many high-scale back ends.

Prologue

Why learn Scala?

Scala lets you write concise, type-safe code and is the native language of Spark, making it valuable for data engineering at scale.

  • Spark: the original and most complete Spark API.
  • Expressive types: catch errors at compile time with powerful types.
  • Data and ML pipelines: process terabytes for model training.
Prologue

Set up and run your first program

Install Scala with Coursier (cs setup), which provides scala and the sbt build tool.

terminal
scala --version
scala run hello.scala
hello.scala
@main def hello(): Unit =
  val name = "Ada"
  println(s"Hello, AI! Welcome to Scala, $name.")
TipScala 3 supports indentation-based syntax as well as braces.
Chapter 1

Values and types

val is immutable (preferred), var is mutable. Types are inferred. String interpolation uses s"...$x".

values.scala
@main def values(): Unit =
  val layers = 32
  var lr = 3e-4
  lr = lr / 2
  val params = 8_030_000_000L
  val gb = params * 2 / 1e9
  println(s"$layers layers, lr $lr, FP16 $gb GB")
Chapter 2

Expressions and pattern matching

if and match are expressions that return values. Pattern matching can destructure data and check types.

control.scala
@main def control(): Unit =
  for epoch <- 0 until 3 do println(s"epoch $epoch")
  val bits = 8
  val name = bits match
    case 32 => "FP32"
    case 16 => "FP16"
    case 8  => "FP8"
    case _  => "other"
  val tier = if bits <= 8 then "low" else "high"
  println(s"$name $tier")
Chapter 3

Immutable collections

List, Vector, Map and Set are immutable by default, with powerful methods like filter, map, groupBy and foldLeft.

collections.scala
@main def collections(): Unit =
  val vram = Map("H100" -> 80, "H200" -> 141, "B200" -> 180)
  val big = vram.filter((_, gb) => gb > 100).keys.toList.sorted
  val total = vram.values.foldLeft(0)(_ + _)
  println(big)
  println(total)
Chapter 4

Case classes, traits and enums

Case classes are immutable data types with equality and copying built in. Traits share behaviour; enums model fixed sets.

types.scala
enum Precision:
  case FP32, FP16, FP8

trait Describable:
  def describe: String

case class Gpu(name: String, vramGb: Int) extends Describable:
  def fits(gb: Double): Boolean = gb <= vramGb
  def describe = s"$name: $vramGb GB"

@main def types(): Unit =
  val g = Gpu("H100", 80)
  println(g.describe + " " + g.fits(70) + " " + g.copy(vramGb = 94))
  println(Precision.FP8)
Chapter 5

Option, Either and errors

Instead of null, Scala uses Option (Some or None). Either and Try represent results that may fail, so errors are part of the type.

errors.scala
import scala.util.Try

def parseVram(s: String): Option[Int] = s.toIntOption

@main def errors(): Unit =
  println(parseVram("80").getOrElse(0))
  println(parseVram("lots"))         // None
  val r = Try(1 / 0)
  println(r.isFailure)
Part II · AI with Scala

AI with Scala

Scala's role in AI is data at scale: Spark prepares and cleans the huge datasets used for training, Spark MLlib trains classic models, and Scala services can call LLM APIs or run ONNX models.

The essential libraries

Apache Spark MLlibDistributed machine learning on big data.
Spark NLPNLP and LLM pipelines on Spark.
DJL / ONNX RuntimeRun deep learning models on the JVM.
sttp + circeHTTP and JSON for calling LLM APIs.

Gradient descent from scratch

Learn y = 3x + 0.5 functionally, with a fold over training steps.

gradient.scala
import scala.util.Random

@main def gradient(): Unit =
  val n = 100
  val xs = (0 until n).map(i => -1 + 2.0 * i / (n - 1))
  val ys = xs.map(x => 3 * x + 0.5 + (Random.nextDouble() - 0.5) * 0.2)
  val (w, b) = (1 to 500).foldLeft((0.0, 0.0)) { case ((w, b), _) =>
    val errs = xs.zip(ys).map((x, y) => (w * x + b - y, x))
    val gw = errs.map((e, x) => 2 * e * x).sum / n
    val gb = errs.map((e, _) => 2 * e).sum / n
    (w - 0.1 * gw, b - 0.1 * gb)
  }
  println(f"w=$w%.2f b=$b%.2f")
Part III · Build an AI agent

Build an AI agent in Scala

With the sttp HTTP client and ujson (from the com-lihaoyi toolkit) the agent loop is short. Install them with Scala CLI directives at the top of the file.

Your goal→Model plans→Tool call→Your code runs the tool→Result back to model→Final answer
agent.scala
//> using dep com.softwaremill.sttp.client4::core:4.0.0
//> using dep com.lihaoyi::upickle:4.1.0
import sttp.client4.quick.*

def getWeather(city: String) = s"It is 24°C and sunny in $city."

@main def agent(): Unit =
  val tools = ujson.read("""[{"type":"function","function":{"name":"get_weather","description":"Weather for a city",
    "parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}}]""")
  val messages = ujson.Arr(ujson.Obj("role" -> "user", "content" -> "Should I take an umbrella in Lahore today?"))
  var done = false
  var round = 0
  while !done && round < 5 do
    round += 1
    val body = ujson.Obj("model" -> "llama3.1", "messages" -> messages, "tools" -> tools)
    val res = quickRequest.post(uri"http://localhost:11434/v1/chat/completions")
      .header("Content-Type", "application/json").body(ujson.write(body)).send()
    val msg = ujson.read(res.body)("choices")(0)("message")
    messages.arr += msg
    val calls = msg.obj.get("tool_calls").map(_.arr.toSeq).getOrElse(Seq.empty)
    if calls.isEmpty then { println(msg("content").str); done = true }
    else calls.foreach { c =>
      val city = ujson.read(c("function")("arguments").str)("city").str
      messages.arr += ujson.Obj("role" -> "tool", "tool_call_id" -> c("id").str, "content" -> getWeather(city))
    }

Run it with scala run agent.scala; adjust the dependency versions to the latest releases.

Run it locallyInstall Ollama, run ollama pull llama3.1, and keep it running. The code talks to its OpenAI-compatible API at http://localhost:11434/v1, so the same program also works with vLLM, llama.cpp server or a cloud API by changing the URL, model name and adding an API key.
Part IV · Your path

Scala learning roadmap

Tick items off as you go. Your progress is saved in this browser.

Beginner

Intermediate

Advanced

Part IV · Your path

Projects to build

Log analyserParse and summarise large log files with collections.
Spark ETLClean a big dataset and train an MLlib model.
LLM microserviceAn HTTP service that wraps an LLM with your own tools.
Reference

Scala cheat sheet

TaskSyntax
Immutableval x = 1
Interpolates"Hi $name"
Matchx match { case 1 => "one" }
Case classcase class P(x: Int)
Map listList(1,2).map(_ * 2)
Optionopt.getOrElse(0)
Reference

Common mistakes

  • Mixing up val and var; prefer val and immutable collections.
  • Calling .get on an Option throws if it is None.
  • Spark jobs run lazily: nothing happens until an action like count or write.
Reference

Where to go next

On TensorCore.si: Java volume, SQL volume.

Keyboard: [ previous language · ] next language · / search the library