---
name: lang-scala-dev
description: Foundational Scala patterns covering immutability, pattern matching, traits, case classes, for-comprehensions, and functional programming. Use when writing Scala code, understanding the type system, or needing guidance on which specialized Scala skill to use. This is the entry point for Scala development.
---
# Scala Fundamentals
## Overview
This is the **foundational skill** for Scala development. Use this skill when writing Scala code, understanding core language features, or determining which specialized Scala skill to use.
### Skill Hierarchy
```
lang-scala-dev (YOU ARE HERE - Foundational)
├── lang-scala-akka-dev (Akka actors, streams, clustering)
├── lang-scala-cats-dev (Cats FP library, type classes, effects)
├── lang-scala-zio-dev (ZIO effects, fibers, resources)
├── lang-scala-spark-dev (Apache Spark, distributed computing)
├── lang-scala-play-dev (Play Framework web applications)
└── lang-scala-testing-dev (ScalaTest, ScalaCheck, property testing)
```
### When to Use This Skill
- **Writing basic Scala code** - syntax, types, control flow
- **Understanding core language features** - pattern matching, traits, case classes
- **Learning Scala fundamentals** - immutability, functional programming
- **Determining skill routing** - which specialized skill to use
- **Troubleshooting common errors** - compilation issues, type errors
---
## Quick Reference
| Pattern | Syntax | Use Case |
|---------|--------|----------|
| **Immutable val** | `val x = 42` | Default variable declaration |
| **Mutable var** | `var x = 42` | When mutation is necessary |
| **Case class** | `case class User(name: String, age: Int)` | Data containers with pattern matching |
| **Pattern match** | `x match { case ... => ... }` | Destructuring, conditional logic |
| **Option** | `Option[A]`, `Some(value)`, `None` | Nullable value handling |
| **Either** | `Either[L, R]`, `Left(error)`, `Right(value)` | Error handling with context |
| **Try** | `Try { riskyOp }` | Exception handling |
| **For-comprehension** | `for { x <- opt1; y <- opt2 } yield x + y` | Sequential monadic operations |
| **Higher-order fn** | `list.map(f).filter(p)` | Function composition |
| **Trait** | `trait Logging { ... }` | Interface with implementation |
| **Object** | `object Utils { ... }` | Singleton, companion object |
| **Implicit (2.x)** | `implicit val ord: Ordering[A]` | Type class instances |
| **Given/Using (3.x)** | `given Ordering[A] with { ... }` | Scala 3 implicits |
---
## Skill Routing
| Task | Use This Skill | Rationale |
|------|---------------|-----------|
| Akka actors, streams, clustering | `lang-scala-akka-dev` | Specialized actor model patterns |
| Cats library, type classes, MTL | `lang-scala-cats-dev` | Advanced FP abstractions |
| ZIO effects, fibers, layers | `lang-scala-zio-dev` | Effect system patterns |
| Spark jobs, RDDs, DataFrames | `lang-scala-spark-dev` | Distributed computing |
| Play web apps, controllers, routes | `lang-scala-play-dev` | Web framework patterns |
| ScalaTest, ScalaCheck, mocking | `lang-scala-testing-dev` | Testing strategies |
| **Core language features** | **This skill** | Foundational patterns |
---
## Core Language Features
### Val vs Var - Immutability
**Prefer immutable `val` over mutable `var`:**
```scala
// Good - immutable
val name = "Alice"
val age = 30
val user = User(name, age)
// Avoid - mutable
var counter = 0
counter += 1 // Mutation creates complexity
// Better - functional update
val counter = 0
val newCounter = counter + 1
```
**When to use `var`:**
```scala
// Loop counters (prefer for-comprehensions)
var i = 0
while (i < 10) {
println(i)
i += 1
}
// Mutable accumulators (prefer foldLeft)
var sum = 0
list.foreach(x => sum += x)
// Better alternatives
(0 until 10).foreach(println)
val sum = list.foldLeft(0)(_ + _)
```
**Immutable collections:**
```scala
// Immutable by default
val list = List(1, 2, 3)
val newList = list :+ 4 // Creates new list
val map = Map("a" -> 1, "b" -> 2)
val newMap = map + ("c" -> 3) // Creates new map
// Mutable collections (import required)
import scala.collection.mutable
val buffer = mutable.ListBuffer(1, 2, 3)
buffer += 4 // In-place mutation
val mutableMap = mutable.Map("a" -> 1)
mutableMap("b") = 2 // In-place mutation
```
---
### Case Classes and Pattern Matching
**Case classes** provide automatic implementations of `equals`, `hashCode`, `toString`, and `copy`:
```scala
// Case class definition
case class User(name: String, age: Int, email: String)
// Automatic features
val user = User("Alice", 30, "alice@example.com")
println(user) // User(Alice,30,alice@example.com)
val updated = user.copy(age = 31) // Immutable update
val User(name, age, email) = user // Destructuring
```
**Pattern matching:**
```scala
// Match on case classes
def describe(user: User): String = user match {
case User("Admin", _, _) => "Administrator"
case User(name, age, _) if age < 18 => s"$name is a minor"
case User(name, age, _) => s"$name is $age years old"
}
// Match on types
def process(value: Any): String = value match {
case s: String => s.toUpperCase
case i: Int => (i * 2).toString
case d: Double => f"$d%.2f"
case _ => "Unknown"
}
// Match on collections
def sumFirst(list: List[Int]): Int = list match {
case Nil => 0
case head :: Nil => head
case head :: tail => head + sumFirst(tail)
}
// Guards and alternatives
def classify(n: Int): String = n match {
case x if x < 0 => "negative"
case 0 => "zero"
case x if x % 2 == 0 => "even positive"
case _ => "odd positive"
}
```
**Sealed traits for ADTs:**
```scala
sealed trait Result[+A]
case class Success[A](value: A) extends Result[A]
case class Failure(error: String) extends Result[Nothing]
case object Pending extends Result[Nothing]
def handle[A](result: Result[A]): String = result match {
case Success(value) => s"Got: $value"
case Failure(error) => s"Error: $error"
case Pending => "Waiting..."
// Compiler ensures exhaustiveness
}
```
---
### Traits and Mixins
**Traits as interfaces:**
```scala
trait Logging {
def log(message: String): Unit
}
trait Auditing {
def audit(event: String): Unit
}
class Service extends Logging with Auditing {
def log(message: String): Unit = println(s"[LOG] $message")
def audit(event: String): Unit = println(s"[AUDIT] $event")
}
```
**Traits with implementation:**
```scala
trait Logging {
def log(message: String): Unit = {
println(s"${java.time.Instant.now()}: $message")
}
}
trait ErrorHandling {
def handleError(error: Throwable): Unit = {
System.err.println(s"Error: ${error.getMessage}")
}
}
class Application extends Logging with ErrorHandling {
def run(): Unit = {
log("Application starting")
try {
// Application logic
} catch {
case e: Exception => handleError(e)
}
}
}
```
**Self-types for dependency declaration:**
```scala
trait DatabaseAccess {
def query(sql: String): List[String]
}
trait UserService {
self: DatabaseAccess => // Requires DatabaseAccess
def getUsers(): List[String] = {
query("SELECT * FROM users") // Can use DatabaseAccess methods
}
}
class Application extends UserService with DatabaseAccess {
def query(sql: String): List[String] = {
// Database implementation
List("user1", "user2")
}
}
```
**Linearization (method resolution order):**
```scala
trait A { def msg = "A" }
trait B extends A { override def msg = "B" + super.msg }
trait C extends A { override def msg = "C" + super.msg }
class D extends B with C // Linearization: D -> C -> B -> A
val d = new D
println(d.msg) // "CBA"
```
---
### For-Comprehensions
**Desugaring to map/flatMap/filter:**
```scala
// For-comprehension
val result = for {
x <- Some(1)
y <- Some(2)
z <- Some(3)
} yield x + y + z
// Desugars to
val result = Some(1).flatMap { x =>
Some(2).flatMap { y =>
Some(3).map { z =>
x + y + z
}
}
}
```
**With filters:**
```scala
val result = for {
x <- List(1, 2, 3, 4, 5)
if x % 2 == 0
y <- List(10, 20)
} yield x * y
// Desugars to
val result = List(1, 2, 3, 4, 5)
.filter(_ % 2 == 0)
.flatMap(x => List(10, 20).map(y => x * y))
// Result: List(20, 40, 40, 80)
```
**With pattern matching:**
```scala
case class User(name: String, age: Int)
val users = List(User("Alice", 30), User("Bob", 25))
val names = for {
User(name, age) <- users
if age >= 30
} yield name.toUpperCase
// Result: List("ALICE")
```
**Combining different monadic types:**
```scala
def findUser(id: Int): Option[User] = ???
def getPermissions(user: User): List[String] = ???
val result = for {
user <- findUser(123)
permission <- getPermissions(user)
} yield s"${user.name} has $permission"
// Result type: Option[List[String]]
```
---
### Option, Either, Try
**Option - handling nullable values:**
```scala
// Creating Options
val some: Option[Int] = Some(42)
val none: Option[Int] = None
// From nullable
val maybeValue: Option[String] = Option(nullableString)
// Pattern matching
def describe(opt: Option[Int]): String = opt match {
case Some(value) => s"Got $value"
case None => "Nothing"
}
// Combinators
val doubled = some.map(_ * 2) // Some(84)
val filtered = some.filter(_ > 50) // None
val orElse = none.orElse(Some(0)) // Some(0)
val getOrElse = none.getOrElse(0) // 0
// For-comprehensions
val result = for {
x <- Some(1)
y <- Some(2)
} yield x + y // Some(3)
```
**Either - error handling with context:**
```scala
// Right for success, Left for failure
type Result[A] = Either[String, A]
def divide(a: Int, b: Int): Result[Int] = {
if (b == 0) Left("Division by zero")
else Right(a / b)
}
// Pattern matching
divide(10, 2) match {
case Right(value) => println(s"Result: $value")
case Left(error) => println(s"Error: $error")
}
// Combinators (right-biased)
val result = divide(10, 2)
.map(_ * 2) // Right(10)
.flatMap(x => divide(x, 5)) // Right(2)
// For-comprehensions
val calculation = for {
a <- divide(10, 2)
b <- divide(a, 5)
c <- divide(b, 1)
} yield c // Right(1)
```
**Try - exception handling:**
```scala
import scala.util.{Try, Success, Failure}
// Creating Try
val attempt = Try {
"123".toInt // Might throw NumberFormatException
}
// Pattern matching
attempt match {
case Success(value) => println(s"Parsed: $value")
case Failure(exception) => println(s"Error: ${exception.getMessage}")
}
// Combinators
val result = Try("123".toInt)
.map(_ * 2)
.recover { case _: NumberFormatException => 0 }
.getOrElse(-1)
// Converting to Option or Either
val opt: Option[Int] = attempt.toOption
val either: Either[Throwable, Int] = attempt.toEither
```
**Choosing between Option, Either, Try:**
| Type | Use When | Error Info |
|------|----------|------------|
| `Option[A]` | Value may be absent | No error context |
| `Either[E, A]` | Need error details | Custom error type `E` |
| `Try[A]` | Catching exceptions | `Throwable` exception |
---
### Collections
**Immutable collections (default):**
```scala
// List - linked list
val list = List(1, 2, 3)
val prepended = 0 :: list // O(1) prepend
val appended = list :+ 4 // O(n) append
val concatenated = list ++ List(4, 5)
// Vector - indexed sequence
val vector = Vector(1, 2, 3)
val updated = vector.updated(1, 42) // O(log n) update
val accessed = vector(1) // O(log n) access
// Set - unique elements
val set = Set(1, 2, 3, 2) // Set(1, 2, 3)
val added = set + 4
val removed = set - 2
// Map - key-value pairs
val map = Map("a" -> 1, "b" -> 2)
val updated = map + ("c" -> 3)
val removed = map - "a"
val value = map.get("a") // Option[Int]
val valueOrDefault = map.getOrElse("z", 0)
```
**Common operations:**
```scala
val list = List(1, 2, 3, 4, 5)
// Transformations
list.map(_ * 2) // List(2, 4, 6, 8, 10)
list.filter(_ % 2 == 0) // List(2, 4)
list.flatMap(x => List(x, x * 10)) // List(1, 10, 2, 20, ...)
// Reductions
list.foldLeft(0)(_ + _) // 15
list.foldRight(0)(_ + _) // 15
list.reduce(_ + _) // 15
list.scan(0)(_ + _) // List(0, 1, 3, 6, 10, 15)
// Grouping
list.groupBy(_ % 2) // Map(0 -> List(2,4), 1 -> List(1,3,5))
list.partition(_ % 2 == 0) // (List(2, 4), List(1, 3, 5))
// Searching
list.find(_ > 3) // Some(4)
list.exists(_ > 3) // true
list.forall(_ > 0) // true
// Sorting
list.sorted // List(1, 2, 3, 4, 5)
list.sortBy(-_) // List(5, 4, 3, 2, 1)
list.sortWith(_ > _) // List(5, 4, 3, 2, 1)
// Zipping
list.zip(List("a", "b", "c")) // List((1,a), (2,b), (3,c))
list.zipWithIndex // List((1,0), (2,1), (3,2), ...)
```
**Performance characteristics:**
| Collection | Access | Prepend | Append | Update |
|------------|--------|---------|--------|--------|
| List | O(n) | O(1) | O(n) | O(n) |
| Vector | O(log n) | O(log n) | O(log n) | O(log n) |
| Array | O(1) | O(n) | O(n) | O(1) |
| Set | O(log n) | O(log n) | O(log n) | - |
| Map | O(log n) | O(log n) | O(log n) | O(log n) |
---
### Higher-Order Functions
**Functions as values:**
```scala
// Function literals
val add: (Int, Int) => Int = (a, b) => a + b
val square: Int => Int = x => x * x
val greet: String => Unit = name => println(s"Hello, $name")
// Method to function
def multiply(a: Int, b: Int): Int = a * b
val multiplyFn = multiply _ // Eta expansion
// Placeholder syntax
val add1 = (_: Int) + 1
val sum = (_: Int) + (_: Int)
```
**Higher-order functions:**
```scala
// Taking functions as parameters
def applyTwice(f: Int => Int, x: Int): Int = f(f(x))
applyTwice(_ * 2, 3) // 12
def repeat(n: Int)(action: => Unit): Unit = {
(1 to n).foreach(_ => action)
}
repeat(3) { println("Hello") }
// Returning functions
def multiplier(factor: Int): Int => Int = {
x => x * factor
}
val double = multiplier(2)
double(5) // 10
// Currying
def add(a: Int)(b: Int): Int = a + b
val add5 = add(5) _
add5(3) // 8
// Partial application
def sum3(a: Int, b: Int, c: Int): Int = a + b + c
val sumWith10 = sum3(10, _: Int, _: Int)
sumWith10(20, 30) // 60
```
**Common higher-order patterns:**
```scala
// Map, filter, fold
List(1, 2, 3)
.map(_ * 2)
.filter(_ > 3)
.foldLeft(0)(_ + _)
// Composition
val f: Int => Int = _ * 2
val g: Int => Int = _ + 1
val composed = f compose g // f(g(x))
val andThen = f andThen g // g(f(x))
composed(5) // 12 = (5 + 1) * 2
andThen(5) // 11 = (5 * 2) + 1
```
---
### Implicits and Given/Using
**Scala 2 implicits:**
```scala
// Implicit parameters
def greet(name: String)(implicit greeting: String): String = {
s"$greeting, $name"
}
implicit val defaultGreeting: String = "Hello"
greet("Alice") // "Hello, Alice"
// Implicit conversions (use sparingly)
implicit def intToString(x: Int): String = x.toString
val s: String = 42 // Implicit conversion
// Implicit classes (extension methods)
implicit class RichInt(x: Int) {
def squared: Int = x * x
}
42.squared // 1764
// Type classes
trait Show[A] {
def show(a: A): String
}
object Show {
implicit val intShow: Show[Int] = (a: Int) => a.toString
implicit val stringShow: Show[String] = (a: String) => s"'$a'"
}
def print[A](a: A)(implicit s: Show[A]): Unit = {
println(s.show(a))
}
print(42) // "42"
print("hello") // "'hello'"
```
**Scala 3 given/using:**
```scala
// Given instances
trait Show[A] {
def show(a: A): String
}
given Show[Int] with {
def show(a: Int): String = a.toString
}
given Show[String] with {
def show(a: String): String = s"'$a'"
}
// Using clauses
def print[A](a: A)(using s: Show[A]): Unit = {
println(s.show(a))
}
print(42) // "42"
print("hello") // "'hello'"
// Extension methods (Scala 3)
extension (x: Int) {
def squared: Int = x * x
def cubed: Int = x * x * x
}
42.squared // 1764
```
**Implicit resolution rules:**
1. **Local scope** - implicits defined in current scope
2. **Imported scope** - explicitly imported implicits
3. **Companion objects** - companion of type or type class
4. **Implicit scope** - package objects, parent types
```scala
// Resolution example
trait Ordering[A]
object Ordering {
// Companion object - implicit scope
implicit val intOrdering: Ordering[Int] = ???
}
class MyClass {
// Local scope takes precedence
implicit val localOrdering: Ordering[Int] = ???
def sort[A](list: List[A])(implicit ord: Ordering[A]): List[A] = ???
sort(List(3, 1, 2)) // Uses localOrdering
}
```
---
### Type System
**Type variance:**
```scala
// Covariance (+A) - subtyping preserved
trait Producer[+A] {
def produce(): A
}
class Animal
class Dog extends Animal
val dogProducer: Producer[Dog] = ???
val animalProducer: Producer[Animal] = dogProducer // OK
// Contravariance (-A) - subtyping reversed
trait Consumer[-A] {
def consume(a: A): Unit
}
val animalConsumer: Consumer[Animal] = ???
val dogConsumer: Consumer[Dog] = animalConsumer // OK
// Invariance (A) - no subtyping
trait Box[A] {
def get: A
def set(a: A): Unit
}
// List is covariant, Array is invariant
val dogs: List[Dog] = List()
val animals: List[Animal] = dogs // OK
val dogArray: Array[Dog] = Array()
// val animalArray: Array[Animal] = dogArray // Compile error
```
**Type bounds:**
```scala
// Upper bound (A <: B) - A must be subtype of B
def findMax[A <: Comparable[A]](list: List[A]): A = {
list.reduce((a, b) => if (a.compareTo(b) > 0) a else b)
}
// Lower bound (A >: B) - A must be supertype of B
sealed trait Animal
case class Dog(name: String) extends Animal
case class Cat(name: String) extends Animal
def prepend[A, B >: A](elem: B, list: List[A]): List[B] = {
elem :: list
}
val dogs: List[Dog] = List(Dog("Fido"))
val animals: List[Animal] = prepend(Cat("Whiskers"), dogs)
// Context bounds (requires implicit)
def sort[A: Ordering](list: List[A]): List[A] = {
list.sorted // Uses implicit Ordering[A]
}
// Multiple bounds
def process[A <: Animal with Comparable[A]: Show](a: A): String = ???
```
**Type aliases and abstract types:**
```scala
// Type alias
type UserId = Int
type Result[A] = Either[String, A]
val id: UserId = 123
val result: Result[Int] = Right(42)
// Abstract types
trait Container {
type Element
def add(e: Element): Unit
def get(): Element
}
class IntContainer extends Container {
type Element = Int
private var value: Int = 0
def add(e: Int): Unit = value = e
def get(): Int = value
}
// Path-dependent types
class Outer {
class Inner
def process(inner: Inner): Unit = ???
}
val outer1 = new Outer
val outer2 = new Outer
val inner1 = new outer1.Inner
// outer2.process(inner1) // Compile error - type mismatch
```
---
## Module System
Scala uses packages and objects to organize code. The module system provides flexible import mechanisms, visibility modifiers, and companion objects for namespace management.
### Packages
```scala
// Package declaration
package com.example.myapp
// Or nested (less common)
package com.example {
package myapp {
class MyClass
}
}
// Package objects - shared utilities for a package
// File: com/example/package.scala
package object example {
type UserId = Long
val DefaultTimeout = 30.seconds
def log(message: String): Unit = println(s"[LOG] $message")
}
// Usage - available to all code in com.example
package com.example.myapp
class Service {
val id: UserId = 123L // From package object
log("Service created") // From package object
}
```
### Import Patterns
```scala
// Basic import
import java.time.LocalDate
// Import all members
import java.time._
// Import multiple specific members
import java.time.{LocalDate, LocalTime, ZonedDateTime}
// Rename on import (avoid conflicts)
import java.util.{List => JList}
import scala.collection.immutable.List
// Exclude on import
import java.util.{Date => _, _} // Import all except Date
// Import object members
object Utils {
def helper(): Unit = ???
}
import Utils.helper
// Import with alias (Scala 3)
import java.time.LocalDate as Date
// Import given instances (Scala 3)
import MyCodecs.given
import MyCodecs.{given JsonEncoder[_]}
```
### Visibility Modifiers
```scala
class Example {
private val privateField = 1 // This class only
protected val protectedField = 2 // This class and subclasses
private[this] val instanceOnly = 3 // This instance only
private[Example] val sameAsPrivate = 4 // This class (same as private)
private[myapp] val packagePrivate = 5 // Package-visible
protected[myapp] val packageProtected = 6
val publicField = 7 // Public (default)
}
// Package-private class
private[myapp] class InternalHelper
// Sealed for ADTs (visible in same file)
sealed trait Result
case class Success(value: Int) extends Result
case class Failure(error: String) extends Result
```
### Companion Objects
```scala
// Class and companion object share private access
class User private (val name: String, val age: Int)
object User {
// Factory method
def apply(name: String, age: Int): User = new User(name, age)
// Smart constructor with validation
def create(name: String, age: Int): Either[String, User] = {
if (name.isEmpty) Left("Name cannot be empty")
else if (age < 0) Left("Age cannot be negative")
else Right(new User(name, age))
}
// Extractor for pattern matching
def unapply(user: User): Option[(String, Int)] =
Some((user.name, user.age))
// Constants
val Anonymous: User = new User("Anonymous", 0)
}
// Usage
val user = User("Alice", 30) // Uses apply
val result = User.create("Bob", 25)
user match {
case User(name, age) => println(s"$name is $age") // Uses unapply
}
```
### Module Patterns
```scala
// Object as module
object StringUtils {
def capitalize(s: String): String = s.capitalize
def reverse(s: String): String = s.reverse
// Nested module
object Validators {
def isEmail(s: String): Boolean = s.contains("@")
def isNotEmpty(s: String): Boolean = s.nonEmpty
}
}
// Usage
import StringUtils._
import StringUtils.Validators._
capitalize("hello")
isEmail("test@example.com")
```
### Scala 3 Exports
```scala
// Export delegates to another object
class UserRepository {
def findById(id: Int): Option[User] = ???
def save(user: User): Unit = ???
def delete(id: Int): Unit = ???
}
class UserService(repo: UserRepository) {
// Export selected members
export repo.{findById, save}
// Export all members
// export repo._
// Export with rename
export repo.{delete as removeUser}
def businessLogic(): Unit = {
// Uses repo internally
}
}
// Clients can call userService.findById directly
val service = new UserService(new UserRepository)
service.findById(123) // Delegated to repo
```
### File Organization
```
// Typical project structure
src/main/scala/
├── com/example/myapp/
│ ├── Main.scala // Entry point
│ ├── domain/
│ │ ├── User.scala // User case class + companion
│ │ ├── Order.scala // Order case class + companion
│ │ └── package.scala // Package object with shared types
│ ├── service/
│ │ ├── UserService.scala
│ │ └── OrderService.scala
│ ├── repository/
│ │ ├── UserRepository.scala
│ │ └── OrderRepository.scala
│ └── util/
│ └── StringUtils.scala
// One public class/trait/object per file (convention)
// File name should match primary type name
```
### Import Best Practices
```scala
// Standard ordering convention
import java.time._ // 1. Java stdlib
import scala.concurrent._ // 2. Scala stdlib
import cats.effect._ // 3. Third-party libraries
import com.example.myapp.domain._ // 4. Project imports
// Avoid wildcard imports for large namespaces
import java.util._ // Avoid - pollutes namespace
// Prefer explicit imports
import java.util.{List, Map, Optional} // Better
// Exception: well-known small namespaces
import cats.syntax.all._ // OK - common in FP code
import scala.concurrent.ExecutionContext.Implicits.global // OK - well-known
```
---
## Common Patterns
### Builder Pattern
**Using copy method (case classes):**
```scala
case class User(
name: String,
age: Int,
email: String,
phone: Option[String] = None,
address: Option[String] = None
)
// Building with copy
val user = User("Alice", 30, "alice@example.com")
.copy(phone = Some("555-1234"))
.copy(address = Some("123 Main St"))
```
**Explicit builder:**
```scala
class UserBuilder private (
private var name: String = "",
private var age: Int = 0,
private var email: String = "",
private var phone: Option[String] = None
) {
def withName(name: String): UserBuilder = {
this.name = name
this
}
def withAge(age: Int): UserBuilder = {
this.age = age
this
}
def withEmail(email: String): UserBuilder = {
this.email = email
this
}
def withPhone(phone: String): UserBuilder = {
this.phone = Some(phone)
this
}
def build(): User = {
require(name.nonEmpty, "Name is required")
require(email.nonEmpty, "Email is required")
User(name, age, email, phone, None)
}
}
object UserBuilder {
def apply(): UserBuilder = new UserBuilder()
}
// Usage
val user = UserBuilder()
.withName("Alice")
.withAge(30)
.withEmail("alice@example.com")
.withPhone("555-1234")
.build()
```
---
### Type Classes
**Definition and implementation:**
```scala
// Type class definition
trait Show[A] {
def show(a: A): String
}
// Type class instances
object Show {
// Summoner method
def apply[A](implicit instance: Show[A]): Show[A] = instance
// Constructor method
def instance[A](f: A => String): Show[A] = new Show[A] {
def show(a: A): String = f(a)
}
// Instances
implicit val intShow: Show[Int] = instance(_.toString)
implicit val stringShow: Show[String] = instance(s => s"'$s'")
implicit val boolShow: Show[Boolean] = instance(_.toString)
// Derived instance
implicit def listShow[A](implicit sa: Show[A]): Show[List[A]] = {
instance(list => list.map(sa.show).mkString("[", ", ", "]"))
}
}
// Extension methods (Scala 2)
implicit class ShowOps[A](val a: A) extends AnyVal {
def show(implicit s: Show[A]): String = s.show(a)
}
// Usage
42.show // "42"
"hello".show // "'hello'"
List(1, 2, 3).show // "[1, 2, 3]"
```
**Type class with operations:**
```scala
trait Monoid[A] {
def empty: A
def combine(x: A, y: A): A
}
object Monoid {
def apply[A](implicit instance: Monoid[A]): Monoid[A] = instance
implicit val intAddMonoid: Monoid[Int] = new Monoid[Int] {
def empty: Int = 0
def combine(x: Int, y: Int): Int = x + y
}
implicit val stringMonoid: Monoid[String] = new Monoid[String] {
def empty: String = ""
def combine(x: String, y: String): String = x + y
}
implicit def listMonoid[A]: Monoid[List[A]] = new Monoid[List[A]] {
def empty: List[A] = Nil
def combine(x: List[A], y: List[A]): List[A] = x ++ y
}
}
def combineAll[A](list: List[A])(implicit m: Monoid[A]): A = {
list.foldLeft(m.empty)(m.combine)
}
combineAll(List(1, 2, 3, 4)) // 10
combineAll(List("a", "b", "c")) // "abc"
combineAll(List(List(1), List(2, 3))) // List(1, 2, 3)
```
---
### Cake Pattern
**Dependency injection using self-types:**
```scala
// Component definitions
trait UserRepositoryComponent {
def userRepository: UserRepository
trait UserRepository {
def findById(id: Int): Option[User]
def save(user: User): Unit
}
}
trait EmailServiceComponent {
def emailService: EmailService
trait EmailService {
def sendEmail(to: String, subject: String, body: String): Unit
}
}
// Implementations
trait UserRepositoryComponentImpl extends UserRepositoryComponent {
def userRepository: UserRepository = new UserRepositoryImpl
class UserRepositoryImpl extends UserRepository {
def findById(id: Int): Option[User] = {
// Database access
Some(User("Alice", 30, "alice@example.com"))
}
def save(user: User): Unit = {
// Database access
println(s"Saving user: $user")
}
}
}
trait EmailServiceComponentImpl extends EmailServiceComponent {
def emailService: EmailService = new EmailServiceImpl
class EmailServiceImpl extends EmailService {
def sendEmail(to: String, subject: String, body: String): Unit = {
println(s"Sending email to $to: $subject")
}
}
}
// Application component with dependencies
trait UserServiceComponent {
self: UserRepositoryComponent with EmailServiceComponent =>
def userService: UserService = new UserServiceImpl
class UserServiceImpl extends UserService {
def registerUser(user: User): Unit = {
userRepository.save(user)
emailService.sendEmail(user.email, "Welcome", "Thanks for registering!")
}
}
trait UserService {
def registerUser(user: User): Unit
}
}
// Wiring
object Application extends UserServiceComponent
with UserRepositoryComponentImpl
with EmailServiceComponentImpl {
def main(args: Array[String]): Unit = {
val user = User("Bob", 25, "bob@example.com")
userService.registerUser(user)
}
}
```
---
### Algebraic Data Types (ADTs)
**Sum types (sealed traits):**
```scala
// Enumeration-like ADT
sealed trait Color
case object Red extends Color
case object Green extends Color
case object Blue extends Color
// Pattern matching is exhaustive
def describe(color: Color): String = color match {
case Red => "red"
case Green => "green"
case Blue => "blue"
// Compiler ensures all cases covered
}
// ADT with data
sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Rectangle(width: Double, height: Double) extends Shape
case class Triangle(base: Double, height: Double) extends Shape
def area(shape: Shape): Double = shape match {
case Circle(r) => math.Pi * r * r
case Rectangle(w, h) => w * h
case Triangle(b, h) => 0.5 * b * h
}
```
**Product types (case classes):**
```scala
// Simple product type
case class Point(x: Double, y: Double)
// Nested product types
case class Address(street: String, city: String, zip: String)
case class Person(name: String, age: Int, address: Address)
// Generic product type
case class Pair[A, B](first: A, second: B)
```
**Combining sum and product types:**
```scala
sealed trait Expression
case class Number(value: Int) extends Expression
case class Add(left: Expression, right: Expression) extends Expression
case class Multiply(left: Expression, right: Expression) extends Expression
case class Divide(left: Expression, right: Expression) extends Expression
def evaluate(expr: Expression): Either[String, Int] = expr match {
case Number(value) => Right(value)
case Add(left, right) =>
for {
l <- evaluate(left)
r <- evaluate(right)
} yield l + r
case Multiply(left, right) =>
for {
l <- evaluate(left)
r <- evaluate(right)
} yield l * r
case Divide(left, right) =>
for {
l <- evaluate(left)
r <- evaluate(right)
result <- if (r != 0) Right(l / r) else Left("Division by zero")
} yield result
}
// Usage
val expr = Divide(Add(Number(10), Number(5)), Number(3))
evaluate(expr) // Right(5)
```
**Recursive ADTs:**
```scala
sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[A](head: A, tail: List[A]) extends List[A]
def sum(list: List[Int]): Int = list match {
case Nil => 0
case Cons(head, tail) => head + sum(tail)
}
// Binary tree
sealed trait Tree[+A]
case object Empty extends Tree[Nothing]
case class Node[A](value: A, left: Tree[A], right: Tree[A]) extends Tree[A]
def size[A](tree: Tree[A]): Int = tree match {
case Empty => 0
case Node(_, left, right) => 1 + size(left) + size(right)
}
```
---
## Troubleshooting
### Common Compilation Errors
**Type mismatch:**
```scala
// Error: type mismatch
val x: String = 42
// Fix: convert types
val x: String = 42.toString
// Error: cannot prove that A =:= B
def process[A](a: A): String = a.toString // Fine
def process[A](a: A): A = "string" // Error
// Fix: specify correct return type
def process[A](a: A): String = "string"
```
**Missing implicit parameter:**
```scala
// Error: could not find implicit value
def sort[A](list: List[A])(implicit ord: Ordering[A]): List[A] = {
list.sorted
}
sort(List(1, 2, 3)) // OK - Ordering[Int] exists
// sort(List(Person("Alice", 30))) // Error - no Ordering[Person]
// Fix: provide implicit
implicit val personOrdering: Ordering[Person] = Ordering.by(_.name)
sort(List(Person("Alice", 30))) // OK now
```
**Pattern match not exhaustive:**
```scala
sealed trait Result
case class Success(value: Int) extends Result
case class Failure(error: String) extends Result
// Warning: match may not be exhaustive
def handle(result: Result): String = result match {
case Success(value) => s"Got $value"
// Missing Failure case
}
// Fix: add all cases
def handle(result: Result): String = result match {
case Success(value) => s"Got $value"
case Failure(error) => s"Error: $error"
}
```
**Recursive value needs type:**
```scala
// Error: recursive value x needs type
val x = x + 1
// Fix: specify type
val x: Int = {
def helper: Int = helper + 1
helper
}
// Or avoid recursion
val x = 1
```
**Variance errors:**
```scala
// Error: covariant type A occurs in contravariant position
trait Producer[+A] {
def produce(): A // OK - covariant position
// def consume(a: A): Unit // Error - contravariant position
}
// Fix: use lower bound
trait Producer[+A] {
def produce(): A
def consume[B >: A](b: B): Unit // OK
}
```
### Runtime Issues
**NullPointerException:**
```scala
// Dangerous - nullable
val name: String = null
// name.toUpperCase // NullPointerException
// Better - use Option
val name: Option[String] = None
name.map(_.toUpperCase) // Safe
```
**StackOverflowError in recursion:**
```scala
// Not tail recursive
def factorial(n: Int): Int = {
if (n <= 1) 1
else n * factorial(n - 1) // Not in tail position
}
// factorial(10000) // StackOverflowError
// Fix: use tail recursion
@scala.annotation.tailrec
def factorial(n: Int, acc: Int = 1): Int = {
if (n <= 1) acc
else factorial(n - 1, n * acc) // Tail call
}
factorial(10000) // OK
```
**ClassCastException:**
```scala
// Dangerous - type erasure
def castList(list: Any): List[Int] = list.asInstanceOf[List[Int]]
val stringList = List("a", "b", "c")
val intList = castList(stringList) // No error yet
// intList.head + 1 // ClassCastException at runtime
// Better - use pattern matching
def safeToIntList(value: Any): Option[List[Int]] = value match {
case list: List[_] if list.forall(_.isInstanceOf[Int]) =>
Some(list.asInstanceOf[List[Int]])
case _ => None
}
```
---
## Performance Tips
**Prefer immutable collections:**
```scala
// Immutable operations create new instances
val list = List(1, 2, 3)
val newList = list :+ 4 // Structural sharing, efficient
// For building, use builders
val builder = List.newBuilder[Int]
(1 to 1000).foreach(builder += _)
val result = builder.result()
```
**Use tail recursion:**
```scala
// Stack-safe tail recursion
@scala.annotation.tailrec
def sum(list: List[Int], acc: Int = 0): Int = list match {
case Nil => acc
case head :: tail => sum(tail, acc + head)
}
```
**Avoid unnecessary Option wrapping:**
```scala
// Inefficient
val result = Some(value).map(transform).getOrElse(default)
// Better
val result = if (condition) transform(value) else default
```
**Use views for large collections:**
```scala
// Eager evaluation - multiple passes
val result = list.map(_ * 2).filter(_ > 10).take(5)
// Lazy evaluation - single pass
val result = list.view.map(_ * 2).filter(_ > 10).take(5).toList
```
---
## Best Practices
### Code Organization
1. **Use package objects for package-level definitions:**
```scala
package com.example
package object utils {
type UserId = Int
type Result[A] = Either[String, A]
implicit class StringOps(s: String) {
def toUserId: UserId = s.toInt
}
}
```
2. **Companion objects for factory methods:**
```scala
case class User private (name: String, age: Int)
object User {
def create(name: String, age: Int): Either[String, User] = {
if (age < 0) Left("Age must be positive")
else if (name.isEmpty) Left("Name cannot be empty")
else Right(new User(name, age))
}
}
```
3. **Sealed traits in same file:**
```scala
// All implementations must be in this file
sealed trait Result[+A]
case class Success[A](value: A) extends Result[A]
case class Failure(error: String) extends Result[Nothing]
case object Pending extends Result[Nothing]
```
### Naming Conventions
- **Classes/Traits:** PascalCase (`UserService`, `HttpClient`)
- **Objects:** PascalCase (`DatabaseConfig`)
- **Methods/Values:** camelCase (`findUser`, `maxRetries`)
- **Type parameters:** Single uppercase letter (`A`, `B`, `T`)
- **Implicits:** Descriptive names (`userOrdering`, `jsonEncoder`)
### Error Handling
**Prefer typed errors over exceptions:**
```scala
// Good
sealed trait UserError
case object UserNotFound extends UserError
case object InvalidEmail extends UserError
def findUser(id: Int): Either[UserError, User] = ???
// Avoid
def findUser(id: Int): User = {
throw new UserNotFoundException(s"User $id not found")
}
```
---
## Concurrency
Scala provides multiple concurrency models: Futures for simple async operations, Akka actors for complex concurrent systems, and modern effect systems like Cats Effect and ZIO.
### Futures - Basic Async
**Creating and using Futures:**
```scala
import scala.concurrent.{Future, Await}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
// Create Future
val future = Future {
Thread.sleep(1000)
42
}
// Transform with map
val doubled = future.map(_ * 2)
// Chain with flatMap
val chained = future.flatMap { value =>
Future(value + 10)
}
// For-comprehension
val result = for {
a <- Future(10)
b <- Future(20)
c <- Future(30)
} yield a + b + c
// Blocking (avoid in production)
val value = Await.result(future, 5.seconds)
```
**Combining Futures:**
```scala
// Sequence - converts List[Future[A]] to Future[List[A]]
val futures = List(Future(1), Future(2), Future(3))
val combined: Future[List[Int]] = Future.sequence(futures)
// Traverse - map then sequence
val ids = List(1, 2, 3)
val users: Future[List[User]] = Future.traverse(ids)(id => fetchUser(id))
// First completed
val fastest = Future.firstCompletedOf(List(
fetchFromPrimary(),
fetchFromBackup()
))
// Recover from failures
val safe = future.recover {
case _: TimeoutException => 0
case _: Exception => -1
}
val safeWith = future.recoverWith {
case _: Exception => fetchFromCache()
}
```
**Promise - explicit completion:**
```scala
import scala.concurrent.Promise
val promise = Promise[Int]()
val future = promise.future
// Complete in another thread
Future {
Thread.sleep(1000)
promise.success(42)
}
// Or fail
promise.failure(new Exception("Failed"))
// Try complete (doesn't throw if already completed)
promise.trySuccess(100)
```
### Akka Actors - Message Passing
**Typed actors (Akka Typed):**
```scala
import akka.actor.typed._
import akka.actor.typed.scaladsl.Behaviors
// Define protocol
sealed trait CounterMessage
case object Increment extends CounterMessage
case object Decrement extends CounterMessage
case class GetCount(replyTo: ActorRef[Int]) extends CounterMessage
// Define behavior
def counter(count: Int): Behavior[CounterMessage] = Behaviors.receive { (context, message) =>
message match {
case Increment =>
counter(count + 1)
case Decrement =>
counter(count - 1)
case GetCount(replyTo) =>
replyTo ! count
Behaviors.same
}
}
// Create actor system
val system = ActorSystem(counter(0), "counter-system")
// Send messages
system ! Increment
system ! Increment
```
**Actor patterns:**
```scala
// Ask pattern (request-response)
import akka.actor.typed.scaladsl.AskPattern._
import akka.util.Timeout
import scala.concurrent.duration._
implicit val timeout: Timeout = 3.seconds
val futureCount: Future[Int] = system.ask(ref => GetCount(ref))
// Supervision
def supervisedBehavior(): Behavior[String] = {
Behaviors.supervise {
Behaviors.receive[String] { (context, message) =>
if (message == "fail") throw new Exception("Failed!")
else Behaviors.same
}
}.onFailure[Exception](SupervisorStrategy.restart)
}
```
### Cats Effect - Functional Effects
**IO monad for side effects:**
```scala
import cats.effect._
// Create IO
val printHello: IO[Unit] = IO.println("Hello")
val readLine: IO[String] = IO.readLine
// Compose with for-comprehension
val program = for {
_ <- IO.println("What's your name?")
name <- IO.readLine
_ <- IO.println(s"Hello, $name!")
} yield ()
// Parallel execution
import cats.effect.syntax.parallel._
import cats.syntax.apply._
val parallel = (
fetchUser(1),
fetchUser(2),
fetchUser(3)
).parMapN((u1, u2, u3) => List(u1, u2, u3))
// Resource management
def useFile(path: String): IO[String] = {
Resource.make(
IO(scala.io.Source.fromFile(path)) // Acquire
)(source => IO(source.close())) // Release
.use(source => IO(source.mkString)) // Use
}
// Error handling
val safeIO = IO.raiseError(new Exception("Error"))
.handleErrorWith(_ => IO.pure(0))
.attempt // Returns IO[Either[Throwable, Int]]
```
**Fibers - lightweight threads:**
```scala
import cats.effect.IO
def task(n: Int): IO[Unit] = IO.sleep(1.second) >> IO.println(s"Task $n")
val program = for {
fiber1 <- task(1).start // Start fiber
fiber2 <- task(2).start
_ <- fiber1.join // Wait for completion
_ <- fiber2.join
} yield ()
// Cancellation
val cancelable = for {
fiber <- IO.sleep(10.seconds).start
_ <- IO.sleep(1.second)
_ <- fiber.cancel // Cancel after 1 second
} yield ()
```
### ZIO - Effect System
**ZIO basics:**
```scala
import zio._
// Create ZIO
val hello: ZIO[Any, Nothing, Unit] = ZIO.succeed(println("Hello"))
val readLine: ZIO[Any, IOException, String] = ZIO.attempt(scala.io.StdIn.readLine())
// Composition
val program = for {
_ <- Console.printLine("What's your name?")
name <- Console.readLine
_ <- Console.printLine(s"Hello, $name!")
} yield ()
// Parallel execution
val parallel = ZIO.collectAllPar(List(
fetchUser(1),
fetchUser(2),
fetchUser(3)
))
// Racing
val raced = fetchFromPrimary() race fetchFromBackup()
// Timeout
val withTimeout = fetchData().timeout(5.seconds)
```
**ZIO Layers - dependency injection:**
```scala
trait UserService {
def getUser(id: Int): ZIO[Any, Throwable, User]
}
case class UserServiceLive(database: Database) extends UserService {
def getUser(id: Int): ZIO[Any, Throwable, User] =
ZIO.attempt(database.query(s"SELECT * FROM users WHERE id = $id"))
}
object UserServiceLive {
val layer: ZLayer[Database, Nothing, UserService] =
ZLayer.fromFunction(UserServiceLive.apply _)
}
// Use the service
val program = for {
user <- ZIO.serviceWithZIO[UserService](_.getUser(123))
_ <- Console.printLine(s"User: $user")
} yield ()
// Provide dependencies
program.provide(UserServiceLive.layer, DatabaseLive.layer)
```
**See also:** `patterns-concurrency-dev` for cross-language concurrency comparison
---
## Build and Dependencies
Scala uses sbt (Simple Build Tool) as the primary build tool, with Mill as a modern alternative. Dependencies are published to Maven Central and Sonatype.
### sbt - Simple Build Tool
**build.sbt basics:**
```scala
// Project metadata
name := "my-project"
version := "0.1.0"
scalaVersion := "3.3.1"
// Organization (for publishing)
organization := "com.example"
// Dependencies
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.10.0",
"org.typelevel" %% "cats-effect" % "3.5.2",
"com.lihaoyi" %% "upickle" % "3.1.3",
// Test dependencies
"org.scalatest" %% "scalatest" % "3.2.17" % Test,
"org.scalatestplus" %% "mockito-4-11" % "3.2.17.0" % Test
)
// Compiler options (Scala 3)
scalacOptions ++= Seq(
"-deprecation",
"-feature",
"-unchecked",
"-Xfatal-warnings"
)
// Resolvers (if needed)
resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
```
**Dependency syntax:**
```scala
// %% - Scala version appended automatically
"org.typelevel" %% "cats-core" % "2.10.0"
// Resolves to: org.typelevel:cats-core_3:2.10.0
// % - Exact artifact name (Java libraries)
"com.google.guava" % "guava" % "32.1.3-jre"
// Cross-version dependencies
"org.scala-lang" % "scala-reflect" % scalaVersion.value
// Test scope
"org.scalatest" %% "scalatest" % "3.2.17" % Test
// Provided scope (available at compile, not packaged)
"javax.servlet" % "javax.servlet-api" % "4.0.1" % Provided
```
**Multi-module projects:**
```scala
// build.sbt root project
lazy val root = (project in file("."))
.aggregate(core, api, client)
.settings(
name := "my-project"
)
lazy val core = (project in file("core"))
.settings(
name := "my-project-core",
libraryDependencies ++= coreDeps
)
lazy val api = (project in file("api"))
.dependsOn(core)
.settings(
name := "my-project-api",
libraryDependencies ++= apiDeps
)
lazy val client = (project in file("client"))
.dependsOn(core)
.settings(
name := "my-project-client"
)
```
**Common sbt tasks:**
```bash
# Compile
sbt compile
# Run application
sbt run
# Run tests
sbt test
# Interactive mode
sbt
> compile
> test
> ~test # Watch mode - rerun on file change
# Package JAR
sbt package
# Create fat JAR (with dependencies)
sbt assembly # Requires sbt-assembly plugin
# Show dependency tree
sbt dependencyTree
# Update dependencies
sbt update
# Clean build
sbt clean
# Publish to local Ivy repository
sbt publishLocal
# Publish to Maven Central
sbt publishSigned
```
**project/plugins.sbt - sbt plugins:**
```scala
// Assembly - fat JAR
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.5")
// Coverage
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.9")
// Publishing
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.21")
// Formatting
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")
// Native compilation
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.16")
```
### Mill - Modern Build Tool
**build.sc (Mill build file):**
```scala
import mill._
import mill.scalalib._
object core extends ScalaModule {
def scalaVersion = "3.3.1"
def ivyDeps = Agg(
ivy"org.typelevel::cats-core:2.10.0",
ivy"org.typelevel::cats-effect:3.5.2"
)
object test extends Tests with TestModule.ScalaTest {
def ivyDeps = Agg(
ivy"org.scalatest::scalatest:3.2.17"
)
}
}
object api extends ScalaModule {
def scalaVersion = "3.3.1"
def moduleDeps = Seq(core)
}
```
**Mill commands:**
```bash
# Compile
mill core.compile
# Run tests
mill core.test
# Run application
mill core.run
# Assembly (fat JAR)
mill core.assembly
# Watch mode
mill -w core.test
```
### Cross-Compilation
**Building for multiple Scala versions:**
```scala
// build.sbt
lazy val core = (project in file("core"))
.settings(
name := "my-library",
crossScalaVersions := Seq("2.12.18", "2.13.12", "3.3.1"),
scalaVersion := "3.3.1" // Default
)
```
```bash
# Compile for all versions
sbt +compile
# Test all versions
sbt +test
# Publish all versions
sbt +publish
```
**Version-specific code:**
```scala
// src/main/scala-2.13/compat.scala
object Compat {
def collect[A](list: List[Option[A]]): List[A] = list.flatten
}
// src/main/scala-3/compat.scala
object Compat {
def collect[A](list: List[Option[A]]): List[A] = list.flatten
}
```
### Publishing to Maven Central
**build.sbt publishing configuration:**
```scala
// Metadata
organization := "io.github.username"
homepage := Some(url("https://github.com/username/project"))
scmInfo := Some(
ScmInfo(
url("https://github.com/username/project"),
"scm:git@github.com:username/project.git"
)
)
developers := List(
Developer(
id = "username",
name = "Your Name",
email = "you@example.com",
url = url("https://github.com/username")
)
)
licenses := Seq("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0"))
// Publishing
publishMavenStyle := true
publishTo := {
val nexus = "https://oss.sonatype.org/"
if (isSnapshot.value)
Some("snapshots" at nexus + "content/repositories/snapshots")
else
Some("releases" at nexus + "service/local/staging/deploy/maven2")
}
// PGP signing
usePgpKeyHex("YOUR_KEY_ID")
```
---
## Testing
Scala has multiple testing frameworks: ScalaTest (most popular), MUnit (lightweight), specs2 (BDD-style), and ScalaCheck for property-based testing.
### ScalaTest - Comprehensive Testing
**Basic test suites:**
```scala
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class CalculatorSpec extends AnyFlatSpec with Matchers {
"A Calculator" should "add two numbers" in {
val calc = new Calculator
calc.add(2, 3) shouldBe 5
}
it should "subtract two numbers" in {
val calc = new Calculator
calc.subtract(5, 3) shouldBe 2
}
it should "throw on division by zero" in {
val calc = new Calculator
assertThrows[ArithmeticException] {
calc.divide(10, 0)
}
}
}
```
**Test styles:**
```scala
// FlatSpec - flat, BDD-style
class UserServiceFlatSpec extends AnyFlatSpec with Matchers {
"UserService" should "find user by id" in {
// test
}
}
// FunSpec - nested describe/it
class UserServiceFunSpec extends AnyFunSpec with Matchers {
describe("UserService") {
describe("findById") {
it("should return user when found") {
// test
}
it("should return None when not found") {
// test
}
}
}
}
// WordSpec - BDD "should" style
class UserServiceWordSpec extends AnyWordSpec with Matchers {
"UserService" should {
"find user by id" in {
// test
}
"handle missing users" in {
// test
}
}
}
// FeatureSpec - acceptance testing
class UserFeatureSpec extends AnyFeatureSpec with GivenWhenThen {
Feature("User management") {
Scenario("Creating a new user") {
Given("a user registration form")
When("the user submits valid data")
Then("a new user is created")
}
}
}
```
**Matchers:**
```scala
// Equality
result shouldBe 42
result should equal(42)
result shouldEqual 42
// Comparison
value should be > 10
value should be <= 100
// Collections
list should contain(42)
list should have size 5
list shouldBe empty
list should contain allOf (1, 2, 3)
list should contain oneOf (1, 2, 3)
// Options
option shouldBe defined
option shouldBe empty
option should contain(42)
// Strings
string should startWith("Hello")
string should endWith("World")
string should include("middle")
string should fullyMatch regex "\\d+".r
// Exceptions
the [IllegalArgumentException] thrownBy {
service.process(null)
} should have message "Input cannot be null"
// Custom matchers
val beEven = be >= 0 and (x => x % 2 == 0)
value should beEven
```
**Fixtures and lifecycle:**
```scala
class DatabaseSpec extends AnyFlatSpec with Matchers with BeforeAndAfter {
var db: Database = _
before {
db = Database.connect()
db.migrate()
}
after {
db.close()
}
"Database" should "insert records" in {
db.insert(Record("test"))
db.count() shouldBe 1
}
}
// Or use BeforeAndAfterEach
class ServiceSpec extends AnyFlatSpec with BeforeAndAfterEach {
override def beforeEach(): Unit = {
// Setup before each test
}
override def afterEach(): Unit = {
// Cleanup after each test
}
}
```
### MUnit - Lightweight Testing
**MUnit basics:**
```scala
import munit.FunSuite
class CalculatorSuite extends FunSuite {
test("addition works") {
assertEquals(2 + 3, 5)
}
test("division by zero fails") {
intercept[ArithmeticException] {
10 / 0
}
}
test("async operation".tag(new Tag("async"))) {
Future(42).map { result =>
assertEquals(result, 42)
}
}
}
```
**Fixtures:**
```scala
class DatabaseSuite extends FunSuite {
val db = FunFixture[Database](
setup = { _ => Database.connect() },
teardown = { db => db.close() }
)
db.test("insert works") { db =>
db.insert(Record("test"))
assertEquals(db.count(), 1)
}
}
```
### specs2 - BDD Style
**specs2 basics:**
```scala
import org.specs2.mutable.Specification
class CalculatorSpec extends Specification {
"Calculator" should {
"add two numbers" in {
val calc = new Calculator
calc.add(2, 3) must_== 5
}
"handle division by zero" in {
val calc = new Calculator
calc.divide(10, 0) must throwA[ArithmeticException]
}
}
}
```
### ScalaCheck - Property-Based Testing
**Property testing basics:**
```scala
import org.scalacheck.Properties
import org.scalacheck.Prop.forAll
object StringProperties extends Properties("String") {
property("reverse twice is identity") = forAll { (s: String) =>
s.reverse.reverse == s
}
property("length of concatenation") = forAll { (s1: String, s2: String) =>
(s1 + s2).length == s1.length + s2.length
}
property("startsWith") = forAll { (s: String, prefix: String) =>
(prefix + s).startsWith(prefix)
}
}
```
**Custom generators:**
```scala
import org.scalacheck.Gen
import org.scalacheck.Arbitrary
case class User(name: String, age: Int)
val genUser: Gen[User] = for {
name <- Gen.alphaStr
age <- Gen.choose(0, 120)
} yield User(name, age)
implicit val arbUser: Arbitrary[User] = Arbitrary(genUser)
property("user age is valid") = forAll { (user: User) =>
user.age >= 0 && user.age <= 120
}
```
**Integration with ScalaTest:**
```scala
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
class ListPropertiesSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyChecks {
"List" should "maintain length on reverse" in {
forAll { (list: List[Int]) =>
list.reverse.length shouldBe list.length
}
}
it should "preserve elements on sort" in {
forAll { (list: List[Int]) =>
list.sorted.toSet shouldBe list.toSet
}
}
}
```
### Mocking
**Using Mockito with ScalaTest:**
```scala
import org.scalatestplus.mockito.MockitoSugar
import org.mockito.Mockito._
import org.mockito.ArgumentMatchers._
class UserServiceSpec extends AnyFlatSpec with MockitoSugar with Matchers {
"UserService" should "fetch user from repository" in {
val repo = mock[UserRepository]
val service = new UserService(repo)
val user = User("Alice", 30)
when(repo.findById(123)).thenReturn(Some(user))
val result = service.getUser(123)
result shouldBe Some(user)
verify(repo).findById(123)
}
it should "handle repository failures" in {
val repo = mock[UserRepository]
val service = new UserService(repo)
when(repo.findById(anyInt())).thenThrow(new DatabaseException("Connection failed"))
assertThrows[DatabaseException] {
service.getUser(123)
}
}
}
```
**Using ScalaMock:**
```scala
import org.scalamock.scalatest.MockFactory
class EmailServiceSpec extends AnyFlatSpec with MockFactory with Matchers {
"EmailService" should "send email via SMTP" in {
val smtp = mock[SmtpClient]
val service = new EmailService(smtp)
(smtp.send _)
.expects("alice@example.com", "Hello", "Message body")
.returning(true)
.once()
service.sendEmail("alice@example.com", "Hello", "Message body") shouldBe true
}
}
```
### Async Testing
**Testing Futures:**
```scala
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.time.{Seconds, Span}
class AsyncServiceSpec extends AnyFlatSpec with ScalaFutures with Matchers {
implicit val patience = PatienceConfig(timeout = Span(5, Seconds))
"AsyncService" should "fetch data asynchronously" in {
val future = service.fetchData()
whenReady(future) { result =>
result shouldBe "data"
}
}
it should "handle failures" in {
val future = service.fetchInvalid()
whenReady(future.failed) { exception =>
exception shouldBe a [NotFoundException]
}
}
}
```
**Testing Cats Effect IO:**
```scala
import cats.effect.testing.scalatest.AsyncIOSpec
class IOServiceSpec extends AsyncIOSpec with Matchers {
"IOService" should "process data" in {
service.processData().asserting { result =>
result shouldBe "processed"
}
}
it should "handle errors" in {
service.processInvalid().assertThrows[ValidationError]
}
}
```
---
## Error Handling
Scala provides multiple error handling strategies, from basic Option/Either to advanced effect system error channels. Choose the right abstraction based on your needs.
### Error Handling Strategies
**Comparison of error handling approaches:**
| Strategy | Use Case | Error Info | Composable | Stack Safe |
|----------|----------|------------|------------|------------|
| `Option[A]` | Absence vs presence | No context | Yes | Yes |
| `Either[E, A]` | Typed errors | Custom error type | Yes | Yes |
| `Try[A]` | Exception catching | Throwable | Yes | No |
| `Cats EitherT` | Stacked Either/Future | Custom error type | Yes | Yes |
| `Cats IO` | Effect errors | Throwable | Yes | Yes |
| `ZIO[R, E, A]` | Effect with typed errors | Custom error type | Yes | Yes |
| Scala 3 boundary/break | Early return | Value-based | Limited | Yes |
### Option - Handling Absence
**Basic Option patterns:**
```scala
// Creating Options
val some: Option[Int] = Some(42)
val none: Option[Int] = None
val fromNullable: Option[String] = Option(nullableValue)
// Transforming Options
val doubled = some.map(_ * 2) // Some(84)
val filtered = some.filter(_ > 50) // None
val flatMapped = some.flatMap(x => Some(x + 1)) // Some(43)
// Extracting values
val value = some.getOrElse(0) // 42
val orElse = none.orElse(Some(0)) // Some(0)
// Pattern matching
def describe(opt: Option[Int]): String = opt match {
case Some(value) if value > 0 => s"Positive: $value"
case Some(value) => s"Non-positive: $value"
case None => "No value"
}
// For-comprehension
val result = for {
a <- Some(1)
b <- Some(2)
c <- Some(3)
} yield a + b + c // Some(6)
// Short-circuits on None
val shortCircuit = for {
a <- Some(1)
b <- None // Stops here
c <- Some(3)
} yield a + b + c // None
```
**Advanced Option patterns:**
```scala
// Folding Options
val folded = some.fold(0)(_ * 2) // 42 * 2 = 84
val foldedNone = none.fold(0)(_ * 2) // 0
// Collecting from Lists
val list = List(Some(1), None, Some(3), None, Some(5))
val collected = list.flatten // List(1, 3, 5)
// Traversing Lists to Option
def safeDivide(a: Int, b: Int): Option[Int] =
if (b == 0) None else Some(a / b)
val divisions = List(10, 20, 30).traverse(safeDivide(_, 5)) // Some(List(2, 4, 6))
val failed = List(10, 20, 30).traverse(safeDivide(_, 0)) // None
// Converting to Either
val asEither: Either[String, Int] = some.toRight("No value")
val asLeft: Either[Int, String] = none.toLeft("Default")
```
### Either - Typed Error Handling
**Either basics (right-biased in Scala 2.12+):**
```scala
// Creating Either values
val success: Either[String, Int] = Right(42)
val failure: Either[String, Int] = Left("Error occurred")
// Transforming Right values
val doubled = success.map(_ * 2) // Right(84)
val chained = success.flatMap(x => Right(x + 1)) // Right(43)
// Pattern matching
def handle[E, A](either: Either[E, A]): String = either match {
case Right(value) => s"Success: $value"
case Left(error) => s"Error: $error"
}
// For-comprehension (short-circuits on Left)
def divide(a: Int, b: Int): Either[String, Int] =
if (b == 0) Left("Division by zero") else Right(a / b)
val computation = for {
a <- divide(10, 2) // Right(5)
b <- divide(a, 5) // Right(1)
c <- divide(b, 1) // Right(1)
} yield c // Right(1)
val failed = for {
a <- divide(10, 2) // Right(5)
b <- divide(a, 0) // Left - stops here
c <- divide(b, 1) // Never executed
} yield c // Left("Division by zero")
```
**Advanced Either patterns:**
```scala
// Custom error types
sealed trait AppError
case class ValidationError(message: String) extends AppError
case class DatabaseError(cause: Throwable) extends AppError
case class NotFoundError(id: String) extends AppError
def findUser(id: String): Either[AppError, User] = {
if (id.isEmpty) Left(ValidationError("ID cannot be empty"))
else if (id == "999") Left(NotFoundError(id))
else Right(User(id, "Name"))
}
// Accumulating errors (requires Cats)
import cats.implicits._
def validateName(name: String): Either[String, String] =
if (name.nonEmpty) Right(name) else Left("Name is empty")
def validateAge(age: Int): Either[String, Int] =
if (age >= 0) Right(age) else Left("Age is negative")
// Sequential validation (stops at first error)
val user = for {
name <- validateName("") // Stops here
age <- validateAge(-5)
} yield User(name, age) // Left("Name is empty")
// Parallel validation (with Validated)
import cats.data.Validated
import cats.data.ValidatedNec // NonEmptyChain
def validateNameV(name: String): ValidatedNec[String, String] =
if (name.nonEmpty) Validated.validNec(name) else Validated.invalidNec("Name is empty")
def validateAgeV(age: Int): ValidatedNec[String, Int] =
if (age >= 0) Validated.validNec(age) else Validated.invalidNec("Age is negative")
val validated = (validateNameV(""), validateAgeV(-5)).mapN(User.apply)
// Invalid(NonEmptyChain("Name is empty", "Age is negative"))
// Converting to Either
validated.toEither // Left(NonEmptyChain("Name is empty", "Age is negative"))
```
**Either combinators:**
```scala
// Recovering from Left
val recovered = failure.recover {
case "specific error" => 0
}
val recoveredWith = failure.recoverWith {
case "error" => Right(0)
}
// Folding
val folded = success.fold(
error => s"Failed: $error",
value => s"Success: $value"
)
// Swapping sides
val swapped = success.swap // Left(42)
// BiMap - transform both sides
val bimapped = success.bimap(
error => s"Error: $error",
value => value * 2
)
// Filtering to Option
val filtered = success.filterOrElse(_ > 50, "Too small") // Left("Too small")
```
### Try - Exception Handling
**Try basics:**
```scala
import scala.util.{Try, Success, Failure}
// Creating Try
val tryValue = Try("123".toInt) // Success(123)
val tryFailed = Try("abc".toInt) // Failure(NumberFormatException)
// Pattern matching
tryValue match {
case Success(value) => println(s"Parsed: $value")
case Failure(exception) => println(s"Failed: ${exception.getMessage}")
}
// Transforming Success
val doubled = tryValue.map(_ * 2) // Success(246)
// Chaining operations
val chained = tryValue.flatMap { value =>
Try(value / 10)
}
// For-comprehension
val computation = for {
a <- Try("10".toInt)
b <- Try("5".toInt)
c <- Try(a / b)
} yield c // Success(2)
// Short-circuits on Failure
val failed = for {
a <- Try("10".toInt)
b <- Try("abc".toInt) // Failure - stops here
c <- Try(a / b)
} yield c // Failure(NumberFormatException)
```
**Try recovery patterns:**
```scala
// Recover with default value
val recovered = tryFailed.recover {
case _: NumberFormatException => 0
}
// Recover with another Try
val recoveredWith = tryFailed.recoverWith {
case _: NumberFormatException => Try("456".toInt)
}
// Fallback to another Try
val fallback = tryFailed.orElse(Try("456".toInt))
// Converting to Option and Either
val asOption = tryValue.toOption // Some(123)
val asEither = tryValue.toEither // Right(123)
// Filtering
val filtered = tryValue.filter(_ > 100) // Success(123)
val failedFilter = tryValue.filter(_ > 200) // Failure(NoSuchElementException)
// Folding
val folded = tryValue.fold(
exception => s"Error: ${exception.getMessage}",
value => s"Success: $value"
)
```
### Cats Effect Error Handling
**IO error handling:**
```scala
import cats.effect._
// Creating IO that might fail
val io: IO[Int] = IO.raiseError(new Exception("Failed"))
val successful: IO[Int] = IO.pure(42)
// Handling errors
val handled = io.handleError { error =>
println(s"Error: ${error.getMessage}")
0
}
val handledWith = io.handleErrorWith { error =>
IO.pure(0)
}
// Recovering from specific errors
val recovered = io.recover {
case _: IllegalArgumentException => 0
}
val recoveredWith = io.recoverWith {
case _: IllegalArgumentException => IO.pure(0)
}
// Attempt - convert to Either
val attempt: IO[Either[Throwable, Int]] = io.attempt
val processed = attempt.flatMap {
case Right(value) => IO.println(s"Success: $value")
case Left(error) => IO.println(s"Error: ${error.getMessage}")
}
// Redeem - handle both success and failure
val redeemed = io.redeem(
error => s"Failed: ${error.getMessage}",
value => s"Success: $value"
)
// RedeemWith - effectful version
val redeemedWith = io.redeemWith(
error => IO.pure(s"Failed: ${error.getMessage}"),
value => IO.pure(s"Success: $value")
)
// Timeout
val withTimeout = io.timeout(5.seconds)
// Retry
val retried = io.handleErrorWith { error =>
IO.sleep(1.second) >> io // Retry after delay
}
// Retry with exponential backoff (requires cats-retry)
import retry._
val policy = RetryPolicies.exponentialBackoff[IO](1.second)
val retriedWithPolicy = retryingOnAllErrors[Int](policy, onError = (_, _) => IO.unit)(io)
```
**MonadError type class:**
```scala
import cats.MonadError
import cats.syntax.all._
def safeDivide[F[_]](a: Int, b: Int)(implicit F: MonadError[F, Throwable]): F[Int] = {
if (b == 0) F.raiseError(new ArithmeticException("Division by zero"))
else F.pure(a / b)
}
// Works with any F[_] that has MonadError instance
val ioResult: IO[Int] = safeDivide[IO](10, 2)
val eitherResult: Either[Throwable, Int] = safeDivide[Either[Throwable, *]](10, 2)
// Generic error handling
def handleDivision[F[_]: MonadError[*[_], Throwable]](a: Int, b: Int): F[String] = {
safeDivide[F](a, b)
.map(result => s"Result: $result")
.handleError(error => s"Error: ${error.getMessage}")
}
```
### ZIO Error Channel
**ZIO typed errors:**
```scala
import zio._
// ZIO[R, E, A] - R=environment, E=error type, A=success type
sealed trait AppError
case class ValidationError(message: String) extends AppError
case class DatabaseError(cause: Throwable) extends AppError
// Creating ZIO with typed errors
val success: ZIO[Any, AppError, Int] = ZIO.succeed(42)
val failure: ZIO[Any, AppError, Int] = ZIO.fail(ValidationError("Invalid input"))
// Handling errors
val handled = failure.catchAll { error =>
error match {
case ValidationError(msg) => ZIO.succeed(0)
case DatabaseError(cause) => ZIO.succeed(-1)
}
}
// Catching specific error types
val catchSome = failure.catchSome {
case ValidationError(msg) => ZIO.succeed(0)
}
// Converting to Either
val either: ZIO[Any, Nothing, Either[AppError, Int]] = success.either
// Fold - handle both success and failure
val folded = success.fold(
error => s"Error: $error",
value => s"Success: $value"
)
// FoldZIO - effectful version
val foldedZIO = success.foldZIO(
error => ZIO.succeed(s"Error: $error"),
value => ZIO.succeed(s"Success: $value")
)
// Mapping errors
val mappedError = failure.mapError {
case ValidationError(msg) => DatabaseError(new Exception(msg))
case other => other
}
// Retrying
val retried = failure.retry(Schedule.recurs(3))
// Retry with backoff
val retriedWithBackoff = failure.retry(
Schedule.exponential(1.second) && Schedule.recurs(5)
)
// Timeout
val withTimeout = success.timeout(5.seconds)
```
**Error accumulation with ZIO:**
```scala
import zio._
import zio.prelude.Validation
def validateName(name: String): IO[String, String] =
if (name.nonEmpty) ZIO.succeed(name) else ZIO.fail("Name is empty")
def validateAge(age: Int): IO[String, Int] =
if (age >= 0) ZIO.succeed(age) else ZIO.fail("Age is negative")
// Sequential validation (fails fast)
val sequential = for {
name <- validateName("") // Fails here
age <- validateAge(-5) // Not executed
} yield User(name, age)
// Parallel validation (accumulates errors)
val parallel = ZIO.validatePar(
validateName(""),
validateAge(-5)
)(User.apply) // Fails with both errors
// Using Validation
val validated = Validation.validateWith(
Validation.fromEither(validateName("").either),
Validation.fromEither(validateAge(-5).either)
)(User.apply)
```
### For-Comprehension Error Propagation
**Short-circuiting behavior:**
```scala
// Option - short-circuits on None
val optionChain = for {
a <- Some(1)
b <- Some(2)
c <- None // Stops here
d <- Some(4) // Never executed
} yield a + b + c + d // None
// Either - short-circuits on Left
def divide(a: Int, b: Int): Either[String, Int] =
if (b == 0) Left("Division by zero") else Right(a / b)
val eitherChain = for {
a <- divide(10, 2) // Right(5)
b <- divide(a, 0) // Left - stops here
c <- divide(10, 2) // Never executed
} yield c // Left("Division by zero")
// Try - short-circuits on Failure
val tryChain = for {
a <- Try("10".toInt)
b <- Try("abc".toInt) // Failure - stops here
c <- Try("5".toInt) // Never executed
} yield a + b + c // Failure(NumberFormatException)
```
**Mixing error types in for-comprehensions:**
```scala
// Converting between types
val mixed = for {
a <- Some(10)
b <- Right(5).toOption // Convert Either to Option
c <- Try(a / b).toOption // Convert Try to Option
} yield c // Some(2)
// Using EitherT to stack Either and Future
import cats.data.EitherT
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
def findUser(id: Int): Future[Either[String, User]] = ???
def findPosts(userId: Int): Future[Either[String, List[Post]]] = ???
val result = for {
user <- EitherT(findUser(123))
posts <- EitherT(findPosts(user.id))
} yield (user, posts)
val unwrapped: Future[Either[String, (User, List[Post])]] = result.value
```
### Scala 3 Boundary and Break
**Early return with boundary/break:**
```scala
import scala.util.boundary, boundary.break
// Early return from computation
def findFirst[A](list: List[A])(predicate: A => Boolean): Option[A] = {
boundary {
for (elem <- list) {
if (predicate(elem)) break(Some(elem))
}
None
}
}
findFirst(List(1, 2, 3, 4, 5))(_ > 3) // Some(4)
// Labeled boundaries
def processData(data: List[Int]): Either[String, Int] = {
boundary[Either[String, Int]] {
var sum = 0
for (value <- data) {
if (value < 0) break(Left("Negative value found"))
sum += value
}
Right(sum)
}
}
processData(List(1, 2, -3, 4)) // Left("Negative value found")
// Nested boundaries
def nestedSearch(matrix: List[List[Int]]): Option[Int] = {
boundary {
for (row <- matrix) {
boundary {
for (elem <- row) {
if (elem > 10) break(break(Some(elem))) // Break both boundaries
}
}
}
None
}
}
nestedSearch(List(List(1, 2), List(3, 15))) // Some(15)
```
**Comparison with traditional approaches:**
```scala
// Traditional approach with recursion
def findFirstRecursive[A](list: List[A])(predicate: A => Boolean): Option[A] = {
list match {
case Nil => None
case head :: tail =>
if (predicate(head)) Some(head)
else findFirstRecursive(tail)(predicate)
}
}
// Traditional approach with fold
def findFirstFold[A](list: List[A])(predicate: A => Boolean): Option[A] = {
list.foldLeft[Option[A]](None) { (acc, elem) =>
acc.orElse(if (predicate(elem)) Some(elem) else None)
}
}
// Boundary/break is more imperative and familiar
// Use when converting Java code or for performance-critical loops
```
### Error Handling Best Practices
**Choosing the right abstraction:**
```scala
// Use Option for simple absence/presence
def findUser(id: Int): Option[User] = ???
// Use Either for typed errors
def validateUser(user: User): Either[ValidationError, User] = ???
// Use Try when catching exceptions
def parseJson(json: String): Try[JsonObject] = Try(Json.parse(json))
// Use IO/ZIO for effectful computations
def saveToDatabase(user: User): IO[User] = ???
// Use boundary/break for imperative-style early returns
def processLargeDataset(data: List[Data]): Result = {
boundary {
// Complex imperative logic with early returns
}
}
```
**Error composition:**
```scala
// Combining multiple error-prone operations
def registerUser(data: UserData): Either[AppError, User] = {
for {
validated <- validateUserData(data)
hashed <- hashPassword(validated.password)
user <- createUser(validated.copy(password = hashed))
_ <- sendWelcomeEmail(user)
} yield user
}
// Parallel execution with error accumulation
import cats.implicits._
def validateUserParallel(data: UserData): ValidatedNec[ValidationError, User] = {
(
validateName(data.name),
validateEmail(data.email),
validateAge(data.age)
).mapN(User.apply)
}
```
---
## Metaprogramming
Scala provides powerful metaprogramming capabilities, evolving from Scala 2's macro system to Scala 3's safer and more principled approach with inline, transparent inline, and the new macro system.
### Metaprogramming Evolution
**Scala 2 vs Scala 3 metaprogramming:**
| Feature | Scala 2 | Scala 3 |
|---------|---------|---------|
| **Macros** | def macros (blackbox/whitebox) | inline + quoted code |
| **Compile-time** | Limited | Rich compile-time operations |
| **Type-level** | Shapeless library | Built-in match types, unions |
| **Derivation** | Manual implicit derivation | `derives` keyword |
| **Safety** | Hygiene issues possible | Hygienic by default |
| **Complexity** | High learning curve | More principled |
### Scala 2 Macros (Legacy)
**Def macros (avoid in new code):**
```scala
// Scala 2 blackbox macro example
import scala.language.experimental.macros
import scala.reflect.macros.blackbox.Context
object DebugMacros {
def debug(value: Any): Unit = macro debugImpl
def debugImpl(c: Context)(value: c.Expr[Any]): c.Expr[Unit] = {
import c.universe._
val valueTree = value.tree
val valueString = show(valueTree)
reify {
println(s"$valueString = ${value.splice}")
}
}
}
// Usage
val x = 42
DebugMacros.debug(x + 10) // Prints: "x + 10 = 52"
// Whitebox macro - can refine return type
def mkArray[T](elements: T*): Array[T] = macro mkArrayImpl[T]
def mkArrayImpl[T: c.WeakTypeTag](c: Context)(elements: c.Expr[T]*): c.Expr[Array[T]] = {
import c.universe._
c.Expr[Array[T]](
q"Array(..${elements.map(_.tree)})"
)
}
```
**Macro bundles (Scala 2):**
```scala
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
trait JsonMacros {
val c: blackbox.Context
import c.universe._
def encodeImpl[T: c.WeakTypeTag]: c.Expr[JsonEncoder[T]] = {
val tpe = weakTypeOf[T]
val fields = tpe.decls.collect {
case m: MethodSymbol if m.isCaseAccessor =>
val name = m.name.toString
val returnType = m.returnType
q"($name, encode(value.$m))"
}
c.Expr[JsonEncoder[T]](q"""
new JsonEncoder[$tpe] {
def encode(value: $tpe): Json = Json.obj(..$fields)
}
""")
}
}
object JsonEncoder {
def derived[T]: JsonEncoder[T] = macro JsonMacrosImpl.encodeImpl[T]
}
class JsonMacrosImpl(val c: blackbox.Context) extends JsonMacros
```
### Scala 3 Inline
**Inline definitions:**
```scala
// Simple inline method
inline def square(x: Int): Int = x * x
// Compiler inlines the code at call site:
val result = square(5) // Becomes: val result = 5 * 5
// Inline parameters
inline def repeat(inline n: Int)(body: => Unit): Unit = {
if (n > 0) {
body
repeat(n - 1)(body)
}
}
repeat(3)(println("Hello"))
// Expands to:
// println("Hello")
// println("Hello")
// println("Hello")
// Inline values
inline val DEBUG = true
inline def log(msg: String): Unit = {
if (DEBUG) println(s"[DEBUG] $msg")
// If DEBUG is false, entire if-statement is eliminated
}
```
**Inline match (type-based specialization):**
```scala
inline def process[T](value: T): String = inline value match {
case _: String => s"String: $value"
case _: Int => s"Int: $value"
case _: Boolean => s"Boolean: $value"
case _ => s"Other: $value"
}
// Specialized at compile time
process("hello") // String: hello
process(42) // Int: 42
// Inline match on types
inline def defaultValue[T]: T = inline erasedValue[T] match {
case _: Int => 0.asInstanceOf[T]
case _: String => "".asInstanceOf[T]
case _: Boolean => false.asInstanceOf[T]
case _ => null.asInstanceOf[T]
}
```
**Compile-time operations:**
```scala
import scala.compiletime._
// Compile-time error
inline def requirePositive(inline x: Int): Int = {
inline if (x <= 0) {
error("Value must be positive")
}
x
}
val good = requirePositive(5) // OK
// val bad = requirePositive(-1) // Compile error: Value must be positive
// Compile-time assertions
inline def assertType[T, U](value: T): Unit = {
inline if (!constValue[T =:= U]) {
error("Type mismatch")
}
}
// Summon implicits
inline def summonAll[T <: Tuple]: List[Any] = inline erasedValue[T] match {
case _: EmptyTuple => Nil
case _: (t *: ts) => summonInline[t] :: summonAll[ts]
}
```
### Transparent Inline
**Transparent inline for type refinement:**
```scala
// Regular inline - return type is fixed
inline def choose(inline b: Boolean, x: Int, y: String): Any =
if (b) x else y
val result1 = choose(true, 42, "hello") // Type: Any
// Transparent inline - return type is refined
transparent inline def chooseT(inline b: Boolean, x: Int, y: String) =
if (b) x else y
val result2 = chooseT(true, 42, "hello") // Type: Int
val result3 = chooseT(false, 42, "hello") // Type: String
// Generic transparent inline
transparent inline def transformOrKeep[T](inline transform: Boolean, value: T) =
inline if (transform) {
value.toString
} else {
value
}
val a = transformOrKeep(true, 42) // Type: String
val b = transformOrKeep(false, 42) // Type: Int
```
**Type-level computations:**
```scala
// Transparent inline for type-level arithmetic
transparent inline def toIntSingleton(inline x: Int): x.type = x
val five = toIntSingleton(5) // Type: 5
// Tuple operations
transparent inline def head[T <: Tuple](t: T): Any = inline t match {
case t: (h *: tail) => t.head
}
val tuple = (1, "hello", true)
val h = head(tuple) // Type: Int (refined!)
// Dependent types via transparent inline
trait Size[N <: Int]
transparent inline def sizedArray[N <: Int](inline n: N): Array[Int] = {
new Array[Int](n)
}
```
### Scala 3 Macros
**Quote and splice:**
```scala
import scala.quoted._
// Simple macro
inline def debug(inline expr: Any): Unit = ${debugImpl('expr)}
def debugImpl(expr: Expr[Any])(using Quotes): Expr[Unit] = {
import quotes.reflect._
val exprString = expr.show
'{
println(s"$exprString = ${$expr}")
}
}
// Usage
val x = 42
debug(x + 10) // Prints: "x.+(10) = 52"
// Macro with type parameter
inline def createInstance[T]: T = ${createInstanceImpl[T]}
def createInstanceImpl[T: Type](using Quotes): Expr[T] = {
import quotes.reflect._
val tpe = TypeRepr.of[T]
tpe.classSymbol match {
case Some(cls) =>
// Generate code to construct instance
New(Inferred(tpe)).select(cls.primaryConstructor).appliedToNone.asExprOf[T]
case None =>
report.errorAndAbort(s"Cannot create instance of ${tpe.show}")
}
}
```
**Pattern matching on code:**
```scala
import scala.quoted._
inline def optimize(inline expr: Int): Int = ${optimizeImpl('expr)}
def optimizeImpl(expr: Expr[Int])(using Quotes): Expr[Int] = {
expr match {
// Optimize x + 0 to x
case '{($x: Int) + 0} => x
// Optimize x * 1 to x
case '{($x: Int) * 1} => x
// Optimize x * 0 to 0
case '{($x: Int) * 0} => '{0}
// No optimization
case _ => expr
}
}
val x = 5
val a = optimize(x + 0) // Optimized to: x
val b = optimize(x * 1) // Optimized to: x
val c = optimize(x * 0) // Optimized to: 0
```
**Generating code:**
```scala
import scala.quoted._
// Generate a method that sums N integers
inline def sumN(inline n: Int): (Int*) => Int = ${sumNImpl('n)}
def sumNImpl(n: Expr[Int])(using Quotes): Expr[(Int*) => Int] = {
import quotes.reflect._
n.value match {
case Some(count) =>
val params = (1 to count).map(i => s"x$i").toList
// Generate: (x1, x2, ..., xN) => x1 + x2 + ... + xN
'{
(args: Seq[Int]) => args.sum
}
case None =>
report.errorAndAbort("n must be a constant")
}
}
val sum3 = sumN(3)
sum3(1, 2, 3) // 6
```
### Type-Level Programming
**Match types:**
```scala
// Type-level pattern matching
type Elem[X] = X match {
case String => Char
case Array[t] => t
case Iterable[t] => t
case AnyVal => X
}
val charElem: Elem[String] = 'a'
val intElem: Elem[Array[Int]] = 42
val stringElem: Elem[List[String]] = "hello"
// Recursive match types
type LeafElem[X] = X match {
case String => Char
case Array[t] => LeafElem[t]
case Iterable[t] => LeafElem[t]
case AnyVal => X
}
val deepElem: LeafElem[List[Array[String]]] = 'x' // Char
```
**Union and intersection types:**
```scala
// Union types
def process(value: Int | String): String = value match {
case i: Int => s"Int: $i"
case s: String => s"String: $s"
}
process(42) // "Int: 42"
process("hello") // "String: hello"
// Intersection types
trait Readable {
def read(): String
}
trait Writable {
def write(data: String): Unit
}
def copy(source: Readable, dest: Writable): Unit = {
dest.write(source.read())
}
// Require both traits
def processFile(file: Readable & Writable): Unit = {
val data = file.read()
file.write(data.toUpperCase)
}
```
**Dependent types:**
```scala
// Path-dependent types
trait Container {
type Element
def add(e: Element): Unit
def get(): Element
}
def transfer(from: Container, to: Container { type Element = from.Element }): Unit = {
to.add(from.get())
}
// Dependent function types
trait Entry {
type Key
def key: Key
}
// Function that returns type dependent on input
val extractKey: (e: Entry) => e.Key = (e: Entry) => e.key
case class StringEntry(key: String) extends Entry {
type Key = String
}
val entry = StringEntry("test")
val key: String = extractKey(entry) // Type refined to String
```
**Type-level arithmetic:**
```scala
// Church numerals (compile-time numbers)
sealed trait Nat
case object Zero extends Nat
case class Succ[N <: Nat](n: N) extends Nat
type _0 = Zero.type
type _1 = Succ[_0]
type _2 = Succ[_1]
type _3 = Succ[_2]
// Type-level addition
type Add[N <: Nat, M <: Nat] <: Nat = N match {
case Zero.type => M
case Succ[n] => Succ[Add[n, M]]
}
val three: Add[_1, _2] = Succ(Succ(Succ(Zero)))
// Sized collections
case class Vec[N <: Nat, A](values: List[A]) {
def append[M <: Nat](other: Vec[M, A]): Vec[Add[N, M], A] =
Vec(values ++ other.values)
}
val vec1 = Vec[_2, Int](List(1, 2))
val vec2 = Vec[_3, Int](List(3, 4, 5))
val vec5: Vec[Add[_2, _3], Int] = vec1.append(vec2) // Type: Vec[_5, Int]
```
### Derivation with Derives
**Automatic type class derivation:**
```scala
// Define derivable type class
trait Show[T] {
def show(value: T): String
}
object Show {
// Primitive instances
given Show[Int] = (value: Int) => value.toString
given Show[String] = (value: String) => s"\"$value\""
given Show[Boolean] = (value: Boolean) => value.toString
// Derive for case classes
import scala.deriving._
inline def derived[T](using m: Mirror.Of[T]): Show[T] = {
inline m match {
case p: Mirror.ProductOf[T] => productShow(p)
case s: Mirror.SumOf[T] => sumShow(s)
}
}
private def productShow[T](p: Mirror.ProductOf[T]): Show[T] = {
new Show[T] {
def show(value: T): String = {
val values = value.asInstanceOf[Product].productIterator.toList
val labels = constValueTuple[p.MirroredElemLabels].toList
val fields = labels.zip(values).map { case (label, value) =>
s"$label = $value"
}
s"${constValue[p.MirroredLabel]}(${fields.mkString(", ")})"
}
}
}
private def sumShow[T](s: Mirror.SumOf[T]): Show[T] = {
new Show[T] {
def show(value: T): String = value.toString
}
}
}
// Use derives keyword
case class Person(name: String, age: Int) derives Show
val person = Person("Alice", 30)
summon[Show[Person]].show(person) // "Person(name = Alice, age = 30)"
// Multiple derivations
enum Color derives Show, Eq, Ordering {
case Red, Green, Blue
}
// Generic derivation
case class Box[T](value: T) derives Show
given [T: Show]: Show[Box[T]] = Show.derived
```
**Common derivable type classes:**
```scala
// Eq - equality
trait Eq[T] {
def eqv(x: T, y: T): Boolean
}
case class User(id: Int, name: String) derives Eq
val user1 = User(1, "Alice")
val user2 = User(1, "Alice")
summon[Eq[User]].eqv(user1, user2) // true
// Ordering
case class Score(value: Int) derives Ordering
val scores = List(Score(10), Score(5), Score(20))
scores.sorted // List(Score(5), Score(10), Score(20))
// Encoder/Decoder (JSON libraries)
import io.circe.Codec
case class Event(id: String, timestamp: Long) derives Codec
// Automatically derives JSON encoder and decoder
```
### Compile-Time Operations
**Compile-time values:**
```scala
import scala.compiletime._
// Extract constant values
inline val SIZE = 100
transparent inline def arraySize: Int = constValue[SIZE.type]
val size: 100 = arraySize // Type refined to literal 100
// Const operations
transparent inline def add(inline a: Int, inline b: Int): Int = {
inline val result = constValue[a.type] + constValue[b.type]
result
}
val sum: 5 = add(2, 3) // Type refined to 5
// Error reporting
inline def assertPositive(inline x: Int): Int = {
inline if (constValue[x.type] <= 0) {
error("Value must be positive at compile time")
}
x
}
val good = assertPositive(5) // OK
// val bad = assertPositive(-1) // Compile error
```
**Tuple operations:**
```scala
import scala.compiletime.ops.int._
// Type-level size
type Size[T <: Tuple] = T match {
case EmptyTuple => 0
case _ *: tail => S[Size[tail]]
}
val size: Size[(Int, String, Boolean)] = 3 // Type: 3
// Type-level concat
type Concat[A <: Tuple, B <: Tuple] <: Tuple = A match {
case EmptyTuple => B
case h *: t => h *: Concat[t, B]
}
val concat: Concat[(Int, String), (Boolean, Double)] = (1, "hi", true, 3.14)
// Type-level reverse
type Reverse[T <: Tuple] <: Tuple = T match {
case EmptyTuple => EmptyTuple
case h *: t => Concat[Reverse[t], h *: EmptyTuple]
}
val reversed: Reverse[(Int, String, Boolean)] = (true, "hi", 1)
```
**Code generation:**
```scala
import scala.quoted._
// Generate boilerplate code
inline def generateGetters[T]: Unit = ${generateGettersImpl[T]}
def generateGettersImpl[T: Type](using Quotes): Expr[Unit] = {
import quotes.reflect._
val tpe = TypeRepr.of[T]
val fields = tpe.typeSymbol.caseFields
fields.foreach { field =>
println(s"def get${field.name.capitalize}(): ${field.termRef.widenTermRefByName.show}")
}
'{}
}
case class User(name: String, age: Int, email: String)
generateGetters[User]
// Prints at compile time:
// def getName(): String
// def getAge(): Int
// def getEmail(): String
```
**Compile-time reflection:**
```scala
import scala.quoted._
inline def inspectType[T]: Unit = ${inspectTypeImpl[T]}
def inspectTypeImpl[T: Type](using Quotes): Expr[Unit] = {
import quotes.reflect._
val tpe = TypeRepr.of[T]
println(s"Type: ${tpe.show}")
println(s"Base classes: ${tpe.baseClasses.map(_.name)}")
println(s"Members: ${tpe.typeSymbol.declaredMethods.map(_.name)}")
tpe.typeSymbol.caseFields.foreach { field =>
println(s"Field: ${field.name}: ${field.termRef.widenTermRefByName.show}")
}
'{}
}
case class Person(name: String, age: Int)
inspectType[Person]
// Compile-time output:
// Type: Person
// Base classes: List(Person, Product, Serializable, Equals, Object, Any)
// Members: List(name, age, copy, productElementNames, ...)
// Field: name: String
// Field: age: Int
```
### Metaprogramming Best Practices
**When to use metaprogramming:**
```scala
// Good use cases:
// 1. Eliminating boilerplate
case class User(name: String, age: Int) derives JsonCodec
// 2. Performance optimization
inline def fastSum(xs: Array[Int]): Int = {
var sum = 0
var i = 0
while (i < xs.length) {
sum += xs(i)
i += 1
}
sum
}
// 3. Type-safe APIs
val query = sql"SELECT * FROM users WHERE id = $userId" // Compile-time SQL checking
// 4. Configuration validation
inline val config = validateConfig("config.json") // Compile-time validation
// Bad use cases:
// - Obscuring simple code
// - When runtime reflection would suffice
// - Overly complex type-level programming
```
**Comparison with other languages:**
| Feature | Scala 3 | Rust | Haskell | C++ |
|---------|---------|------|---------|-----|
| Macros | Quote/splice | Procedural/declarative | Template Haskell | Preprocessor |
| Inline | inline keyword | inline attribute | INLINE pragma | inline keyword |
| Type-level | Match types, unions | Traits, associated types | Type families | Template metaprogramming |
| Derivation | derives keyword | derive macros | deriving clause | Not built-in |
| Safety | Hygienic | Hygienic | Hygienic | Not hygienic |
---
## Serialization
Scala has excellent serialization support with multiple libraries for JSON, XML, and binary formats. This section covers common serialization patterns and library choices.
### Library Comparison
| Library | Style | Performance | Type Safety | Derivation |
|---------|-------|-------------|-------------|------------|
| **circe** | FP-first | Good | Excellent | Semi-auto/auto |
| **upickle** | Simple | Excellent | Good | Automatic |
| **play-json** | Familiar | Good | Good | Macro-based |
| **jsoniter-scala** | Performance | Best | Good | Compile-time |
| **zio-json** | ZIO ecosystem | Excellent | Excellent | Derives |
### Circe (Most Popular)
```scala
import io.circe._
import io.circe.generic.semiauto._
import io.circe.syntax._
import io.circe.parser._
// Case class
case class User(name: String, age: Int, email: Option[String])
// Semi-automatic derivation (recommended)
object User {
implicit val encoder: Encoder[User] = deriveEncoder[User]
implicit val decoder: Decoder[User] = deriveDecoder[User]
}
// Or combined codec
object User {
implicit val codec: Codec[User] = deriveCodec[User]
}
// Encoding
val user = User("Alice", 30, Some("alice@example.com"))
val json: Json = user.asJson
val jsonString: String = user.asJson.noSpaces
// {"name":"Alice","age":30,"email":"alice@example.com"}
// Decoding
val parsed: Either[Error, User] = decode[User](jsonString)
parsed match {
case Right(user) => println(s"Got user: $user")
case Left(error) => println(s"Parse error: $error")
}
// Custom field names
case class ApiResponse(
@JsonKey("user_id") userId: Long,
@JsonKey("created_at") createdAt: String
)
```
### Circe with ADTs
```scala
import io.circe._
import io.circe.generic.semiauto._
// Sealed trait hierarchy
sealed trait PaymentMethod
case class CreditCard(number: String, expiry: String) extends PaymentMethod
case class BankTransfer(iban: String) extends PaymentMethod
case object Cash extends PaymentMethod
object PaymentMethod {
implicit val encoder: Encoder[PaymentMethod] = deriveEncoder[PaymentMethod]
implicit val decoder: Decoder[PaymentMethod] = deriveDecoder[PaymentMethod]
}
// Custom discriminator
import io.circe.generic.extras.semiauto._
import io.circe.generic.extras.Configuration
implicit val config: Configuration = Configuration.default
.withDiscriminator("type")
.withSnakeCaseMemberNames
// Result: {"type":"CreditCard","number":"1234","expiry":"12/25"}
```
### Upickle (Simple & Fast)
```scala
import upickle.default._
// Automatic derivation for case classes
case class User(name: String, age: Int, email: Option[String])
object User {
implicit val rw: ReadWriter[User] = macroRW[User]
}
// Or inline
implicit val userRW: ReadWriter[User] = macroRW
// Encoding
val user = User("Alice", 30, Some("alice@example.com"))
val json: String = write(user)
val prettyJson: String = write(user, indent = 2)
// Decoding
val parsed: User = read[User](json)
// Handle errors
val result: Either[Throwable, User] = scala.util.Try(read[User](json)).toEither
// Custom field names
case class ApiUser(
@key("user_name") userName: String,
@key("user_age") userAge: Int
)
object ApiUser {
implicit val rw: ReadWriter[ApiUser] = macroRW
}
```
### Play JSON
```scala
import play.api.libs.json._
// Case class with companion
case class User(name: String, age: Int, email: Option[String])
object User {
// Automatic format
implicit val format: Format[User] = Json.format[User]
// Or separate reads/writes
implicit val reads: Reads[User] = Json.reads[User]
implicit val writes: Writes[User] = Json.writes[User]
}
// Encoding
val user = User("Alice", 30, Some("alice@example.com"))
val json: JsValue = Json.toJson(user)
val jsonString: String = Json.stringify(json)
// Decoding
val parsed: JsResult[User] = Json.parse(jsonString).validate[User]
parsed match {
case JsSuccess(user, _) => println(s"Got user: $user")
case JsError(errors) => println(s"Errors: $errors")
}
// Custom format
implicit val customFormat: Format[User] = (
(__ \ "user_name").format[String] and
(__ \ "user_age").format[Int] and
(__ \ "email_address").formatNullable[String]
)(User.apply, unlift(User.unapply))
```
### Jsoniter-Scala (High Performance)
```scala
import com.github.plokhotnyuk.jsoniter_scala.core._
import com.github.plokhotnyuk.jsoniter_scala.macros._
case class User(name: String, age: Int, email: Option[String])
// Compile-time codec generation
implicit val codec: JsonValueCodec[User] = JsonCodecMaker.make
// Encoding
val user = User("Alice", 30, Some("alice@example.com"))
val bytes: Array[Byte] = writeToArray(user)
val json: String = writeToString(user)
// Decoding
val parsed: User = readFromString[User](json)
val fromBytes: User = readFromArray[User](bytes)
// Configuration
implicit val customCodec: JsonValueCodec[User] = JsonCodecMaker.make(
CodecMakerConfig
.withFieldNameMapper(JsonCodecMaker.EnforcePascalCase)
.withDiscriminatorFieldName(Some("type"))
)
```
### ZIO JSON
```scala
import zio.json._
case class User(name: String, age: Int, email: Option[String])
object User {
implicit val encoder: JsonEncoder[User] = DeriveJsonEncoder.gen[User]
implicit val decoder: JsonDecoder[User] = DeriveJsonDecoder.gen[User]
// Or combined
implicit val codec: JsonCodec[User] = DeriveJsonCodec.gen[User]
}
// Encoding
val user = User("Alice", 30, Some("alice@example.com"))
val json: String = user.toJson
val prettyJson: String = user.toJsonPretty
// Decoding
val parsed: Either[String, User] = json.fromJson[User]
// With custom field names
case class ApiUser(
@jsonField("user_name") userName: String,
@jsonField("user_age") userAge: Int
)
```
### Validation Patterns
```scala
import io.circe._
import io.circe.generic.semiauto._
// Custom decoder with validation
case class Email private (value: String)
object Email {
def apply(value: String): Either[String, Email] =
if (value.contains("@")) Right(new Email(value))
else Left("Invalid email format")
implicit val decoder: Decoder[Email] = Decoder.decodeString.emap(apply)
implicit val encoder: Encoder[Email] = Encoder.encodeString.contramap(_.value)
}
// Refined types integration
import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.string._
type ValidEmail = String Refined MatchesRegex["^[\\w.-]+@[\\w.-]+\\.[a-z]{2,}$"]
case class User(
name: String,
email: ValidEmail,
age: Int
)
// Accumulating validation with cats
import cats.data.ValidatedNec
import cats.syntax.all._
def validateUser(json: Json): ValidatedNec[String, User] = {
(
validateName(json),
validateEmail(json),
validateAge(json)
).mapN(User.apply)
}
```
### XML Serialization
```scala
import scala.xml._
case class User(name: String, age: Int)
// Manual XML generation
def toXml(user: User): Elem =
{user.name}
{user.age}
// Manual XML parsing
def fromXml(xml: Elem): User = User(
name = (xml \ "name").text,
age = (xml \ "age").text.toInt
)
// With scala-xml
val xml =
Alice
30
val users = (xml \ "user").map { node =>
User((node \ "name").text, (node \ "age").text.toInt)
}
```
### Binary Formats
```scala
// Protocol Buffers with ScalaPB
// build.sbt: libraryDependencies += "com.thesamet.scalapb" %% "scalapb-runtime" % "..."
// user.proto
// message User {
// string name = 1;
// int32 age = 2;
// }
// Generated code usage
import myapp.proto.user.User
val user = User(name = "Alice", age = 30)
val bytes: Array[Byte] = user.toByteArray
val parsed: User = User.parseFrom(bytes)
// Avro with avro4s
import com.sksamuel.avro4s._
case class User(name: String, age: Int)
val schema = AvroSchema[User]
val record = RecordFormat[User].to(User("Alice", 30))
val bytes = AvroOutputStream.binary[User].to(outputStream).write(user).close()
// MessagePack with msgpack4s
import org.msgpack.core._
// Custom binary protocol
trait BinaryCodec[A] {
def encode(a: A): Array[Byte]
def decode(bytes: Array[Byte]): A
}
```
### Streaming JSON
```scala
import io.circe.fs2._
import fs2.Stream
import cats.effect.IO
// Stream JSON array
val jsonStream: Stream[IO, Byte] = ???
val users: Stream[IO, User] = jsonStream
.through(byteStreamParser)
.through(decoder[IO, User])
// Process large files
import java.nio.file.Paths
import fs2.io.file.Files
Files[IO]
.readAll(Paths.get("users.json"))
.through(byteStreamParser)
.through(decoder[IO, User])
.evalMap(user => IO(println(user)))
.compile
.drain
```
### Best Practices
```scala
// 1. Use semi-automatic derivation for control
object User {
implicit val codec: Codec[User] = deriveCodec[User] // Explicit
}
// 2. Define codecs in companion objects
case class User(name: String, age: Int)
object User {
implicit val codec: Codec[User] = deriveCodec
}
// 3. Use Either for parsing results
def parseUser(json: String): Either[Error, User] = decode[User](json)
// 4. Validate on deserialization
implicit val emailDecoder: Decoder[Email] =
Decoder.decodeString.emap(Email.apply)
// 5. Use consistent naming conventions
implicit val config: Configuration = Configuration.default
.withSnakeCaseMemberNames
// 6. Handle optional fields explicitly
case class Config(
required: String,
optional: Option[String] = None,
withDefault: Int = 42
)
```
---
## Cross-Cutting Patterns
For cross-language comparison and translation patterns, see:
- `patterns-concurrency-dev` - Futures, actors, effects comparison across languages
- `patterns-serialization-dev` - JSON handling, schema validation patterns
- `patterns-metaprogramming-dev` - Macros, implicits, type classes vs other languages
---
## References
### Official Documentation
- **Scala Documentation:** https://docs.scala-lang.org/
- **Scala 3 Book:** https://docs.scala-lang.org/scala3/book/introduction.html
- **API Docs:** https://www.scala-lang.org/api/current/
### Books
- **Programming in Scala (Odersky, Spoon, Venners)**
- **Functional Programming in Scala (Chiusano, Bjarnason)**
- **Scala with Cats (Underscore)**
### Community Resources
- **Scala Center:** https://scala.epfl.ch/
- **Scala Users Forum:** https://users.scala-lang.org/
- **ScalaDex (package index):** https://index.scala-lang.org/
### Build Tools
- **sbt:** https://www.scala-sbt.org/
- **Mill:** https://mill-build.org/
- **Maven:** https://maven.apache.org/
- **Gradle:** https://gradle.org/
### Testing
- **ScalaTest:** https://www.scalatest.org/
- **Specs2:** https://etorreborre.github.io/specs2/
- **MUnit:** https://scalameta.org/munit/
- **ScalaCheck:** https://scalacheck.org/
### Related Skills
When you need specialized functionality:
- **Akka (actors, streams):** Use `lang-scala-akka-dev`
- **Cats (FP library):** Use `lang-scala-cats-dev`
- **ZIO (effects):** Use `lang-scala-zio-dev`
- **Spark (distributed):** Use `lang-scala-spark-dev`
- **Play Framework:** Use `lang-scala-play-dev`
- **Testing:** Use `lang-scala-testing-dev`
---
## Summary
This skill covers **foundational Scala development**:
- Immutability - val vs var, immutable collections
- Pattern matching - case classes, sealed traits, ADTs
- Traits - interfaces with implementation, mixins
- For-comprehensions - monadic composition
- Error handling - Option, Either, Try
- Collections - List, Vector, Set, Map operations
- Higher-order functions - map, filter, fold, composition
- Type system - variance, bounds, type classes
- Common patterns - builder, type classes, cake, ADTs
For specialized topics, route to the appropriate skill from the hierarchy.