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.
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.
Set up and run your first program
Install Scala with Coursier (cs setup), which provides scala and the sbt build tool.
scala --version
scala run hello.scala@main def hello(): Unit =
val name = "Ada"
println(s"Hello, AI! Welcome to Scala, $name.")Values and types
val is immutable (preferred), var is mutable. Types are inferred. String interpolation uses s"...$x".
@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")Expressions and pattern matching
if and match are expressions that return values. Pattern matching can destructure data and check types.
@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")Immutable collections
List, Vector, Map and Set are immutable by default, with powerful methods like filter, map, groupBy and foldLeft.
@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)Case classes, traits and enums
Case classes are immutable data types with equality and copying built in. Traits share behaviour; enums model fixed sets.
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)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.
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)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
Gradient descent from scratch
Learn y = 3x + 0.5 functionally, with a fold over training steps.
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")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.
//> 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.
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.Scala learning roadmap
Tick items off as you go. Your progress is saved in this browser.
Beginner
Intermediate
Advanced
Projects to build
Scala cheat sheet
| Task | Syntax |
|---|---|
| Immutable | val x = 1 |
| Interpolate | s"Hi $name" |
| Match | x match { case 1 => "one" } |
| Case class | case class P(x: Int) |
| Map list | List(1,2).map(_ * 2) |
| Option | opt.getOrElse(0) |
Common mistakes
- Mixing up
valandvar; prefervaland immutable collections. - Calling
.geton anOptionthrows if it isNone. - Spark jobs run lazily: nothing happens until an action like
countorwrite.
Where to go next
- Scala docs — official
- Scala CLI — run scripts
- Spark docs — big data
On TensorCore.si: Java volume, SQL volume.
Keyboard: [ previous language · ] next language · / search the library