Skip to content

Scala Folha de referência

Linguagem híbrida funcional/OO na JVM.

01

Noções Básicas

Variáveis e Tipos

Prefira val (imutável) em vez de var (mutável) para código mais seguro e previsível. Scala infere tipos, mas anotações explícitas auxiliam a legibilidade para APIs públicas. Tudo é um objeto—sem primitivos (Int, Boolean são classes).

scala
val name = "Alice"   // immutable (preferred)
var age = 30          // mutable
val pi: Double = 3.14159
val isDev: Boolean = true
val nums: List[Int] = List(1, 2, 3)
println(name.getClass)  // class java.lang.String

Interpolação de Strings

s"..." habilita interpolação ${expr}. f"..." adiciona formatação estilo printf (%s, %.2f). raw"..." desabilita sequências de escape. Esses prefixos tornam a construção de strings type-safe e legível.

scala
val name = "Alice"
val age = 30
println(s"Name: ${name}, Age: ${age}")
println(s"Next year: ${age + 1}")
println(s"Upper: ${name.toUpperCase}")
println(f"${name}%s weighs ${65.5}%.1f kg")  // formatted
println(raw"No \n escape")  // raw string

Tuplas

Tuplas agrupam 2-22 valores heterogêneos. Acesso via _1, _2 (1-indexado). Destruture com val (a, b) = tuple. Para mais de 2 elementos, prefira case classes para campos nomeados e melhor legibilidade.

scala
val pair = ("Alice", 30)
println(pair._1)  // Alice
println(pair._2)  // 30
val (name, age) = pair  // destructure
println(s"${name}: ${age}")
// Scala 3: val p = ("a", 1, 2.0)
val triple = ("a", 1, 2.0)
println(triple._3)  // 2.0

Inferência de Tipos e Ascription

Scala infere tipos para variáveis locais e tipos de retorno. Use tipos explícitos para APIs públicas, funções recursivas e casos ambíguos. Type ascription (expr: Type) força um tipo—útil para upcasting ou desambiguação.

scala
val x = 42           // Int inferred
val y: Long = 42      // explicit Long
val z = 42: Long      // type ascription
val list = List(1, 2, 3)  // List[Int]
val mixed: List[Any] = List(1, "a", true)
def double(x: Int) = x * 2  // return type inferred

Unit e Nothing

Unit é como void (um valor: ()). Nothing é o bottom type sem instâncias—usado para funções que nunca retornam (throw, loop infinito). Nothing é um subtipo de todos os tipos, permitindo inferência de tipos flexível.

scala
def printIt(x: Int): Unit = println(x)  // like void
val u: Unit = ()    // Unit has one value: ()
// Nothing is the bottom type—no instances
def error(msg: String): Nothing =
  throw new RuntimeException(msg)
// Nothing is a subtype of everything
val n: Nothing = error("boom")
02

Strings

Métodos Comuns de String

Strings Scala são strings Java com métodos extras via conversões implícitas (StringOps). A maioria dos métodos retorna novas strings (imutáveis). Use-os em vez de loops manuais para clareza e correção.

scala
val s = "Hello, World"
println(s.length)        // 12
println(s.toUpperCase)   // HELLO, WORLD
println(s.toLowerCase)   // hello, world
println(s.split(", "))   // Array(Hello, World)
println(s.replace("o", "0"))  // Hell0, W0rld
println(s.reverse)       // dlroW ,olleH
println(s.contains("World"))  // true

Strings Multilinha (Aspas Triplas)

Strings com aspas triplas preservam todo whitespace e novas linhas. Use stripMargin com | para alinhar código de forma limpa—apenas texto após | é mantido. Ideal para SQL, JSON ou templates embutidos em código.

scala
val sql = """
  SELECT * FROM users
  WHERE age > 18
  ORDER BY name
"""
println(sql.trim)
// StripMargin for clean indentation
val text = """|Hello
              |World""".stripMargin
println(text)  // Hello
World

Construção de Strings

mkString junta coleções com prefix/suffix opcional—idiomático e eficiente. Use StringBuilder para construir strings grandes em loops. Evite concatenação + repetida em loops (cria muitos objetos intermediários).

scala
val parts = List("apple", "banana", "cherry")
println(parts.mkString(", "))     // apple, banana, cherry
println(parts.mkString("[", ", ", "]"))  // [apple, banana, cherry]
val sb = new StringBuilder
for (p <- parts) sb.append(p).append(" ")
println(sb.toString.trim)

String para Número

toInt/toDouble lançam em entrada inválida. Use toIntOption (Scala 2.13+) para parsing seguro retornando Option. Para parsing em massa, use Try ou Either para tratar erros funcionalmente sem exceções.

scala
val n = "42".toInt        // 42
val d = "3.14".toDouble    // 3.14
val b = "true".toBoolean   // true
val safe = "abc".toIntOption  // Some(42) or None
// Handling errors
val result = try "x".toInt catch { case _ => 0 }
println(result)  // 0

Regex

.r converte uma string em Regex. findFirstIn retorna Option, findAllIn retorna um iterador. Use em pattern matching com case email(e) => para extração. Regex é o Pattern do Java por baixo dos panos.

scala
import scala.util.matching.Regex
val email: Regex = "[\w.]+@[\w]+\.[a-z]+".r
val text = "Contact: [email protected]"
email.findFirstIn(text) match {
  case Some(e) => println(s"Found: ${e}")
  case None => println("No email")
}
val replaced = "[0-9]+".r.replaceAllIn("a1b2c3", "#")
println(replaced)  // a#b#c#
03

Estruturas de Dados

List e Seq

List é uma lista imutável simplesmente encadeada—O(1) head/prepend, O(n) acesso aleatório. Use Vector para acesso aleatório (O(1) efetivo). +: prepende, :+ anexa. Prefira coleções imutáveis para thread safety.

scala
val nums = List(1, 2, 3, 4, 5)
println(nums.head)      // 1
println(nums.tail)      // List(2,3,4,5)
println(nums.reverse)   // List(5,4,3,2,1)
println(nums.take(2))   // List(1, 2)
println(nums.drop(2))   // List(3, 4, 5)
println(nums.mkString)  // 12345
val combined = 0 +: nums :+ 6  // List(0,1,2,3,4,5,6)

Map

Map é imutável—operações retornam novos Maps. Use get(key) para acesso Option, getOrElse para padrões. + adiciona/atualiza, - remove. Para maps mutáveis, use scala.collection.mutable.Map. Chaves devem ser Hashable.

scala
val ages = Map("Alice" -> 30, "Bob" -> 25)
println(ages("Alice"))            // 30 (throws if missing)
println(ages.getOrElse("Eve", 0)) // 0 (safe)
val updated = ages + ("Eve" -> 28)  // new Map
val removed = ages - "Bob"
ages.foreach { case (k, v) => println(s"${k}: ${v}") }
println(ages.keys)  // Set(Alice, Bob)

Set

Set é imutável com O(1) contains. union (|), intersect (&), diff (~) para operações de conjunto. + adiciona, - remove. Use para desduplicação e teste de pertinência. Variante mutável: scala.collection.mutable.Set.

scala
val a = Set(1, 2, 3)
val b = Set(3, 4, 5)
println(a union b)        // Set(1,2,3,4,5)
println(a intersect b)    // Set(3)
println(a diff b)         // Set(1,2)
println(a subsetOf(Set(1,2,3,4)))  // true
val added = a + 6  // Set(1,2,3,6)

Option (Null Safety)

Option substitui null—Some(value) ou None. Use map/filter/flatMap para transformações, getOrElse para padrões. For-comprehensions funcionam em Option. Isso elimina NullPointerException ao tornar a ausência explícita no tipo.

scala
def findUser(id: Int): Option[String] =
  if (id == 1) Some("Alice") else None
val name = findUser(1)
println(name.getOrElse("Unknown"))  // Alice
println(name.map(_.toUpperCase))    // Some(ALICE)
println(name.filter(_.startsWith("A")))  // Some(Alice)
val result = for {
  n <- findUser(1)
  if n.startsWith("A")
} yield n.toUpperCase  // Some(ALICE)

Array e Vector

Array é um array Java mutável (mais rápido, mas sem atualizações funcionais). Vector é imutável com acesso aleatório e atualizações efetivamente O(1)—preferido para coleções imutáveis de acesso aleatório. Use List para sequencial, Vector para indexado.

scala
val arr = Array(1, 2, 3, 4)  // mutable, Java array
arr(0) = 10
println(arr(0))  // 10
val vec = Vector(1, 2, 3, 4)  // immutable, fast random access
println(vec(2))  // 3
val updated = vec.updated(0, 10)  // Vector(10,2,3,4)
// Vector: O(1) random access + immutable
// Array: mutable, Java interop, fastest
04

Fluxo de Controle

If / Else (Expressão)

Em Scala, if/else é uma expressão que retorna um valor. Isso elimina a necessidade de um operador ternário. Ambas as ramificações devem ter tipos compatíveis. Use isso para atribuição condicional concisa.

scala
val score = 85
val grade =
  if (score >= 90) "A"
  else if (score >= 80) "B"
  else if (score >= 70) "C"
  else "F"
println(grade)  // B
// if returns a value—no ternary operator needed

For Comprehension

for comprehensions iteram e podem filtrar (if guards) e transformar (yield). Sem yield, é um loop; com yield, constrói uma coleção. to inclui o final, until exclui. Equivalente a cadeias flatMap/map/filter.

scala
for (i <- 1 to 5) println(i)  // 1 2 3 4 5
for (i <- 1 until 5) print(i)  // 1 2 3 4
// With yield (generates collection)
val doubled = for (n <- List(1,2,3)) yield n * 2
println(doubled)  // List(2, 4, 6)
// With filter (guard)
val evens = for (n <- 1 to 10 if n % 2 == 0) yield n
println(evens.toList)  // List(2,4,6,8,10)

Match (Pattern Matching)

match é o poderoso pattern matching de Scala—como switch nos esteroides. Suporta literais, OR (|), guards (if), correspondência de tipo e destructuring. Deve ser exaustivo (compilador avisa sobre cases faltantes). É uma expressão que retorna um valor.

scala
val n = 2
val label = n match {
  case 0 => "zero"
  case 1 | 2 | 3 => "small"
  case x if x < 10 => "medium"
  case _ => "large"
}
println(label)  // small
// Match on types
def describe(x: Any): String = x match {
  case i: Int => s"Int: ${i}"
  case s: String => s"String: ${s}"
  case _ => "unknown"
}

While e Do-While

while/do-while são loops imperativos que retornam Unit. Requerem estado mutável (var). Prefira for-comprehensions ou recursão para Scala funcional idiomático. Use while apenas quando o desempenho exigir ou para efeitos colaterais.

scala
var i = 0
while (i < 3) {
  println(i)
  i += 1
}
var j = 0
do {
  println(j)
  j += 1
} while (j < 3)
// Prefer recursion or for-comprehensions for immutability

Try / Catch / Finally

try/catch/finally é como Java, mas usa pattern matching no catch. Prefira Try para tratamento de erros funcional—envolve exceções como valores Success/Failure, habilitando cadeias map/flatMap sem boilerplate try/catch.

scala
import scala.util.{Try, Success, Failure}
val result = try {
  "abc".toInt
} catch {
  case e: NumberFormatException => 0
} finally {
  println("cleanup")
}
println(result)  // 0
// Functional alternative
val r2 = Try("abc".toInt).getOrElse(0)
println(r2)  // 0
05

Funções

Definição de Método

Métodos usam def name(params): ReturnType = body. Parâmetros padrão e argumentos nomeados são suportados. Retorno Unit = apenas efeito colateral. Corpos de expressão única omitem chaves. = é obrigatório (sem ele, é um procedimento retornando Unit).

scala
def add(a: Int, b: Int): Int = a + b
def greet(name: String, greeting: String = "Hello"): String =
  s"${greeting}, ${name}!"
def log(msg: String): Unit = println(msg)
println(add(3, 4))           // 7
println(greet("Alice"))      // Hello, Alice!
println(greet("Bob", greeting = "Hi"))  // named arg

Lambda (Função Anônima)

Lambdas: (params) => body. Use _ como abreviação para parâmetros únicos (x => x * 2 torna-se _ * 2). Múltiplos _ referem-se a parâmetros diferentes (_ + _). Lambdas são first-class—passe-os para map, filter, reduce, etc.

scala
val square = (x: Int) => x * x
println(square(5))  // 25
val nums = List(1, 2, 3)
println(nums.map(_ * 2))      // List(2, 4, 6)
println(nums.filter(_ > 1))   // List(2, 3)
println(nums.reduce(_ + _))   // 6
// _ is shorthand for the parameter

Funções de Ordem Superior

Funções de ordem superior recebem ou retornam funções. Isso permite abstrações poderosas: map/filter/reduce, composição, aplicação parcial. makeAdder retorna uma closure capturando n. Este é o coração da programação funcional.

scala
def applyTwice(f: Int => Int, x: Int): Int = f(f(x))
println(applyTwice(_ + 3, 5))  // 11
def makeAdder(n: Int): Int => Int = _ + n
val add5 = makeAdder(5)
println(add5(10))  // 15
// Functions returning functions = currying-like

Currying e Aplicação Parcial

Currying divide parâmetros em múltiplas listas: def f(a)(b). Aplique parcialmente com _ para criar funções especializadas. Múltiplas listas de parâmetros melhoram a inferência de tipos (o compilador pode inferir tipos de f a partir da lista). Comum em APIs de coleção.

scala
def add(a: Int)(b: Int): Int = a + b  // curried
val add5 = add(5)_  // partially applied
println(add5(3))  // 8
// Multiple parameter lists
def foldLeft[A, B](list: List[A])(z: B)(f: (B, A) => B): B = ???
// Helps type inference
val sum = List(1,2,3).foldLeft(0)(_ + _)

By-Name e Lazy

Parâmetros by-name (=> T) são avaliados lazymente a cada uso—úteis para logging (pula msg cara se desabilitado) e estruturas de controle customizadas. lazy val adia a inicialização até o primeiro acesso—use para valores caros ou opcionais.

scala
// By-name parameter: evaluated on each use
def debug(msg: => String): Unit =
  if (debugEnabled) println(msg)
// Lazy evaluation
lazy val expensive = computeHeavy()
println(expensive)  // computed now
def computeHeavy(): Int = { println("computing"); 42 }
06

Classes e POO

Classe e Construtor

Parâmetros do construtor primário estão na assinatura da classe. Parâmetros val tornam-se campos imutáveis (getter público), var mutáveis. O corpo da classe É o construtor. Construtores auxiliares usam def this(...) e devem chamar outro construtor.

scala
class Person(val name: String, val age: Int) {
  // Constructor params with val/var become fields
  def greet: String = s"Hi, I'm ${name}"
  def isAdult: Boolean = age >= 18
}
val p = new Person("Alice", 30)
println(p.greet)   // Hi, I'm Alice
println(p.name)    // Alice (val field)
println(p.isAdult) // true

Case Class

Case classes são classes de dados imutáveis com equals, hashCode, toString, copy e companion object com apply/unapply gerados automaticamente. Use para modelagem de dados e pattern matching. Sem 'new' necessário. Elas são a fundação de ADTs em Scala.

scala
case class Point(x: Int, y: Int)
val p1 = Point(3, 4)  // no 'new' needed
val p2 = Point(3, 4)
println(p1 == p2)  // true (value equality)
val moved = p1.copy(x = 5)  // Point(5, 4)
println(p1.x, p1.y)  // 3 4 (fields auto-visible)
// Auto: equals, hashCode, toString, copy, companion

Traits (Interfaces com Implementação)

Traits são como interfaces Java, mas podem ter implementação. Uma classe pode misturar múltiplos traits (com extends/with). Traits permitem herança múltipla de comportamento. Use para interfaces compartilhadas, mixins e modificações empilháveis via linearização.

scala
trait Greetable {
  def name: String  // abstract
  def greet: String = s"Hello, ${name}"  // concrete
}
trait Named {
  val name: String
}
class User(val name: String) extends Greetable
val u = new User("Alice")
println(u.greet)  // Hello, Alice
// Stackable traits via linearization

Object (Singleton)

object declara um singleton (uma instância). Companion objects (mesmo nome de uma classe) contêm métodos semelhantes a static, factories (apply) e extractors (unapply). Use apply para factory methods para que chamadores omitam 'new'. Isso é Scala idiomático.

scala
object Config {
  val version = "1.0"
  def load(): Map[String, String] = Map("key" -> "value")
}
println(Config.version)  // 1.0
// Companion object (same name as class)
class Person(val name: String)
object Person {
  def apply(name: String): Person = new Person(name)
}
val p = Person("Alice")  // uses apply, no 'new'

Herança e Classe Abstrata

abstract class pode ter membros não implementados. Use extends para herdar, override para redefinir. Prefira traits para mixins (herança múltipla). Use abstract class quando precisar de parâmetros de construtor ou quiser um tipo base. Herança única de classes.

scala
abstract class Shape {
  def area: Double  // abstract
  def describe: String = s"Area: ${area}"
}
class Circle(r: Double) extends Shape {
  def area: Double = math.Pi * r * r
}
val c = new Circle(5)
println(c.describe)  // Area: 78.53...
// override required for concrete members
// abstract class vs trait: use abstract for base, trait for mixins
07

Coleções e Funcional

Map / Filter / Fold

map transforma, filter seleciona, reduce/foldLeft agrega. Esses são o núcleo da transformação funcional de dados. foldLeft recebe uma seed e é associativo; reduce requer não-vazio. Use-os em vez de loops para clareza e imutabilidade.

scala
val nums = List(1, 2, 3, 4, 5)
println(nums.map(_ * 2))           // List(2,4,6,8,10)
println(nums.filter(_ % 2 == 0))   // List(2,4)
println(nums.reduce(_ + _))        // 15
println(nums.foldLeft(0)(_ + _))   // 15
println(nums.sum)                  // 15
println(nums.mkString(", "))       // 1, 2, 3, 4, 5

FlatMap e For-Comprehension

flatMap mapeia e aplaina em um passo—essencial para coleções aninhadas e operações monádicas. For-comprehensions são syntactic sugar para cadeias flatMap/map/filter. Use para transformações aninhadas complexas—é mais legível.

scala
val nested = List(List(1, 2), List(3, 4))
println(nested.flatten)        // List(1,2,3,4)
println(nested.flatMap(_.map(_ * 2)))  // List(2,4,6,8)
// Equivalent for-comprehension:
val result = for {
  inner <- nested
  n <- inner
} yield n * 2
println(result)  // List(2,4,6,8)

Agrupamento e Ordenação

groupBy particiona por uma chave em um Map. sorted ordena naturalmente, sortBy por uma função chave, sortWith com um comparator. Esses retornam novas coleções (imutáveis). Use para análise de dados, categorização e ordenação.

scala
val words = List("apple", "bat", "cat", "ant")
val byFirst = words.groupBy(_.head)
// Map(a -> List(apple, ant), b -> List(bat), c -> List(cat))
println(byFirst)
val sorted = words.sorted  // List(ant, apple, bat, cat)
val byLen = words.sortBy(_.length)  // List(ant, bat, cat, apple)
val desc = words.sortWith(_ > _)  // descending
println(sorted, byLen)

Either e Try

Try envolve exceções como valores Success/Failure. Either representa Left (erro) ou Right (sucesso)—use para erros de domínio onde você quer distinguir tipos de erro. Ambos suportam map/flatMap para propagação funcional de erros.

scala
import scala.util.{Try, Success, Failure}
def parse(s: String): Try[Int] = Try(s.toInt)
parse("42") match {
  case Success(n) => println(s"OK: ${n}")
  case Failure(e) => println(s"Err: ${e.getMessage}")
}
// Either for domain errors
def divide(a: Int, b: Int): Either[String, Int] =
  if (b == 0) Left("div by zero") else Right(a / b)
println(divide(10, 2))  // Right(5)
println(divide(10, 0))  // Left(div by zero)

Coleções Lazy (View e Lazy)

.view torna coleções lazy—operações são adiadas até forçadas (toList, sum, etc.). Evita coleções intermediárias para melhor desempenho em dados grandes. LazyList (antigo Stream) permite sequências infinitas—elementos computados sob demanda.

scala
val nums = (1 to 1000000).view
val result = nums
  .filter(_ % 2 == 0)
  .map(_ * 2)
  .take(5)
  .toList  // forces evaluation
println(result)  // List(4, 8, 12, 16, 20)
// view = lazy, no intermediate collections
// LazyList (Stream) for infinite sequences
val fibs: LazyList[Int] = 0 #:: 1 #:: fibs.zip(fibs.tail).map(_ + _)
08

Pattern Matching

Correspondência de Case Classes

Sealed traits + case classes formam Algebraic Data Types (ADTs). O compilador verifica exaustividade—adicione um case e ele avisa onde matches precisam de atualização. Pattern matching destrutura case classes diretamente. Isso é modelagem Scala idiomática.

scala
sealed trait Shape
case class Circle(r: Double) extends Shape
case class Square(s: Double) extends Shape
case class Rect(w: Double, h: Double) extends Shape
def area(s: Shape): Double = s match {
  case Circle(r) => math.Pi * r * r
  case Square(s) => s * s
  case Rect(w, h) => w * h
}
println(area(Circle(5)))  // 78.53...

Guards e Condições

Guards (if condition) adicionam verificações em tempo de execução a padrões. Tornam o matching mais expressivo. A ordem importa—primeira correspondência vence. Use guards para ranges, condições ou lógica complexa que padrões simples não conseguem expressar.

scala
val n = 15
val desc = n match {
  case x if x < 0 => "negative"
  case 0 => "zero"
  case x if x % 2 == 0 => "even"
  case _ => "odd"
}
println(desc)  // odd
// Guards add boolean conditions to patterns

Correspondência de Coleções

:: padrão cons destrutura listas em head e tail. _* corresponde a zero ou mais elementos em arrays/listas. Esses padrões permitem processamento recursivo de listas e decomposição estrutural. Poderoso para parsing e traversal de árvores.

scala
val list = List(1, 2, 3, 4)
list match {
  case Nil => println("empty")
  case head :: Nil => println(s"one: ${head}")
  case head :: tail => println(s"head=${head}, rest=${tail}")
  case _ => println("other")
}
// head :: tail destructures a list
val arr = Array(1, 2, 3)
arr match {
  case Array(1, _*) => println("starts with 1")
  case _ => println("other")
}

Correspondência de Option e Either

Pattern matching em Option/Either é idiomático—Some(x)/None, Right(x)/Left(e). For-comprehensions desugam para flatMap com match. Isso faz o tratamento de erros fluir naturalmente sem verificações if-else explícitas.

scala
def find(id: Int): Option[String] =
  if (id == 1) Some("Alice") else None
find(1) match {
  case Some(name) => println(s"Found: ${name}")
  case None => println("Not found")
}
// In for-comprehensions
val result = for {
  name <- find(1)
  upper = name.toUpperCase
} yield upper
println(result)  // Some(ALICE)

Extractors (unapply)

Extractors customizados via unapply permitem pattern matching em qualquer tipo. O método unapply retorna Option dos valores extraídos. Isso permite definir seus próprios padrões—poderoso para DSLs e parsing. Case classes geram unapply automaticamente.

scala
object Email {
  def unapply(s: String): Option[(String, String)] = {
    val parts = s.split("@")
    if (parts.length == 2) Some((parts(0), parts(1))) else None
  }
}
"[email protected]" match {
  case Email(user, domain) =>
    println(s"User: ${user}, Domain: ${domain}")
  case _ => println("Not an email")
}
09

Generics e Implicits

Classes e Métodos Genéricos

Generics (parâmetros de tipo [A]) escrevem código type-safe e reutilizável. Use [A] para um único tipo, [A, B] para dois. A inferência de tipos geralmente descobre o tipo. Generics são apagados em runtime (limitação da JVM), mas verificados em tempo de compilação.

scala
class Stack[A] {
  private var items: List[A] = Nil
  def push(x: A): Unit = { items = x :: items }
  def pop: Option[A] = items.headOption
}
val s = new Stack[Int]
s.push(1); s.push(2)
println(s.pop)  // Some(2)
def first[A](list: List[A]): Option[A] = list.headOption
println(first(List("a", "b")))  // Some(a)

Type Bounds

<: upper bound (A é um subtipo), >: lower bound (A é um supertipo). Context bounds (A: Ordering) exigem um valor implícito daquele tipo. View bounds (A <% B) estão depreciados—use context bounds em vez. Esses restringem parâmetros de tipo.

scala
// Upper bound: A must be Animal or subclass
class Box[A <: Animal](val content: A)
// Lower bound: A must be Dog or superclass
class Kennel[A >: Dog](val occupant: A)
// Context bound: A must have an Ordering
def max[A: Ordering](a: A, b: A): A =
  if (implicitly[Ordering[A]].gt(a, b)) a else b
abstract class Animal { def name: String }
class Dog extends Animal { def name = "Rex" }

Parâmetros Implícitos

Parâmetros implícitos são injetados pelo compilador a partir do escopo. Use para configuração, type classes ou dependências que você não quer passar por toda parte. Declare com implicit val/def. O compilador pesquisa o escopo envolvente e companion objects.

scala
def greet(name: String)(implicit greeting: String): String =
  s"${greeting}, ${name}!"
implicit val defaultGreeting: String = "Hello"
println(greet("Alice"))  // Hello, Alice! (implicit injected)
println(greet("Bob")("Hi"))  // Hi, Bob! (explicit override)
// Compiler finds implicit in scope

Type Classes (Conversões Implícitas)

Type classes (via implicits) adicionam comportamento a tipos sem modificá-los—polimorfismo ad-hoc. Defina uma trait, forneça instâncias implícitas e use parâmetros implícitos. É assim que Ordering, Numeric e Show funcionam. Mais flexível que herança.

scala
trait Show[A] { def show(a: A): String }
object Show {
  implicit val intShow: Show[Int] = (a: Int) => a.toString
  implicit val strShow: Show[String] = identity
}
def printIt[A](a: A)(implicit s: Show[A]): Unit =
  println(s.show(a))
printIt(42)       // 42
printIt("hello")  // hello
// Type class: ad-hoc polymorphism

Extension Methods (Scala 2)

Classes implícitas adicionam extension methods a tipos existentes. Defina implicit class com um único parâmetro, e seus métodos tornam-se disponíveis naquele tipo. Use para adicionar métodos utilitários a Int, String, etc. Scala 3 usa a sintaxe 'extension' mais limpa.

scala
implicit class IntOps(val n: Int) extends AnyVal {
  def times(f: => Unit): Unit = (1 to n).foreach(_ => f)
  def squared: Int = n * n
}
5.times { print("hi") }  // hihihihihi
println(5.squared)  // 25
// Scala 3: extension (n: Int) def squared = n * n
10

Concorrência e Future

Future e Async

Future representa uma computação assíncrona. onComplete registra um callback. Nunca bloqueie (Await.result) em servidores web de produção—ocupa threads. Use for-comprehensions para encadear futures funcionalmente. Requer um ExecutionContext.

scala
import scala.concurrent.{Future, ExecutionContext}
import ExecutionContext.Implicits.global
val f: Future[Int] = Future {
  Thread.sleep(1000)
  42
}
f.onComplete {
  case scala.util.Success(v) => println(s"Got ${v}")
  case scala.util.Failure(e) => println(s"Err: ${e}")
}
// Don't block in production—use callbacks or for-comprehensions

Compondo Futures

For-comprehensions em Futures executam-nos sequencialmente (cada um espera o anterior). Para execução paralela, inicie todos os Futures primeiro, depois use Future.sequence para combinar. Future.traverse mapeia e sequencia em um passo. Esta é a maneira idiomática de compor trabalho assíncrono.

scala
import scala.concurrent.{Future, ExecutionContext}
import ExecutionContext.Implicits.global
val f1 = Future { 10 }
val f2 = Future { 20 }
val sum = for {
  a <- f1
  b <- f2
} yield a + b  // Future(30)
// Parallel execution
val results = Future.sequence(List(
  Future { 1 }, Future { 2 }, Future { 3 }
))
results.map(_.sum)  // Future(6)

Coleções Paralelas

.par converte uma coleção em uma versão paralela—operações usam múltiplas threads automaticamente. Bom para trabalho CPU-bound em coleções grandes. Cuidado: operações não associativas (como subtração) podem dar resultados diferentes. Não para trabalho I/O-bound.

scala
val nums = (1 to 1000000).toList
val sum = nums.par.sum  // parallel sum
println(sum)
val doubled = nums.par.map(_ * 2).toList
// .par converts to ParCollection
// Operations run on multiple threads
// Use for CPU-bound work on large collections

Promise (Future Manual)

Promise é o lado gravável de um Future—você o completa manualmente com success/failure. Use ao fazer ponte de APIs baseadas em callback para Futures, ou quando precisar completar um Future de múltiplos lugares. Future é read-only; Promise é write-once.

scala
import scala.concurrent.{Promise, Future, ExecutionContext}
import ExecutionContext.Implicits.global
val p = Promise[Int]()
val f = p.future
// Complete the promise from another thread
Future { Thread.sleep(100); p.success(42) }
f.foreach(println)  // 42 (when complete)
// p.failure(new Exception) for errors
// Promise = write side, Future = read side

Sync vs Async (Await)

Await.result bloqueia a thread atual até o Future concluir (com timeout). Use apenas em testes ou métodos main—bloquear em código assíncrono derrota o propósito. Em produção, use callbacks (onComplete, map) ou for-comprehensions para permanecer non-blocking.

scala
import scala.concurrent.{Future, Await}
import scala.concurrent.duration._
import ExecutionContext.Implicits.global
val f = Future { Thread.sleep(500); 42 }
// Block and wait (use sparingly—mainly in tests)
val result = Await.result(f, 1.second)
println(result)  // 42
// Await.ready returns Try, Await.result returns value
// Avoid in production servers—use callbacks instead
11

Implicits Profundo

Parâmetros Implícitos

Parâmetros implícitos são passados automaticamente pelo compilador quando um valor implícito do tipo correspondente está no escopo. Isso reduz boilerplate para parâmetros de 'contexto' (ExecutionContext, logging, configuração). O compilador pesquisa: escopo local, companion objects, implicit scope. Você sempre pode passar explicitamente para sobrescrever. Excesso torna o código difícil de rastrear—use para dependências genuinamente contextuais.

scala
import scala.concurrent.ExecutionContext

// Method with implicit parameter
def process[A](data: List[A])
    (implicit ec: ExecutionContext): Future[Unit] = {
  Future { data.foreach(println) }
}

// The compiler finds an implicit ExecutionContext in scope
implicit val ec: ExecutionContext = ExecutionContext.global
process(List(1, 2, 3))  // ec passed automatically

// Explicitly providing (overrides implicit)
process(List(1, 2, 3))(myCustomEC)

// Multiple implicit parameters
def log(msg: String)(implicit
    level: Level, logger: Logger): Unit = {
  logger.log(level, msg)
}

Conversões Implícitas

Conversões implícitas convertem automaticamente entre tipos quando necessário. implicit class (estendendo AnyVal para zero overhead) adiciona extension methods a tipos existentes—é assim que Scala adiciona métodos a Int, String, etc. Cuidado: conversões implícitas podem tornar o código confuso (o que está sendo convertido?). Prefira implicit classes para extensões em vez de implicit defs brutos. Habilite com import scala.language.implicitConversions.

scala
import scala.language.implicitConversions

// Implicit conversion: one type to another
implicit def intToString(n: Int): String = n.toString
val s: String = 42  // intToString(42) called implicitly

// Extension via implicit class (Scala 2.10+)
implicit class RichInt(val self: Int) extends AnyVal {
  def times(f: => Unit): Unit = (1 to self).foreach(_ => f)
  def squared: Int = self * self
}

5.times { println("hi") }  // prints hi 5 times
3.squared  // 9

// Implicit conversion for type compatibility
implicit def javaToScalaList(jl: java.util.List[Int]): List[Int] =
  import scala.jdk.CollectionConverters._
  jl.asScala.toList

val javaList: java.util.List[Int] = ???
val scalaList: List[Int] = javaList  // converted

Prioridade de Resolução Implícita

O compilador resolve implicits por prioridade: escopo local > companion objects > importados > herdados. Se dois implicits do mesmo tipo estão igualmente no escopo, você recebe um erro de 'ambiguous implicit'. O padrão de trait LowPriorityImplicits fornece padrões que podem ser sobrescritos por implicits mais específicos. Entender a ordem de resolução é crucial para design de bibliotecas—coloque padrões em traits de baixa prioridade para que usuários possam sobrescrever.

scala
// Priority of implicit resolution (highest to lowest):
// 1. Local implicit (defined in current scope)
implicit val ec1: ExecutionContext = ec1

// 2. Implicit in companion object
object MyService {
  implicit val ec2: ExecutionContext = ec2  // lower priority
}

// 3. Implicit scope (imported)
import somePackage.Implicits._

// 4. Implicit parameter default (inherited trait)
trait DefaultEc {
  implicit val ec: ExecutionContext = ExecutionContext.global
}

// More specific type wins
implicit def ord1: Ordering[Int] = ???
implicit def ord2: Ordering[Int] = ???  // ambiguous error!

// LowPriorityImplicits trait pattern
object MyLib {
  implicit val high: Ordering[Int] = ???
}
object MyLib extends LowPriorityImplicits
trait LowPriorityImplicits {
  implicit val low: Ordering[Int] = ???  // fallback
}

Context Bounds e Evidence

Context bounds [A: TypeClass] são syntactic sugar para parâmetros implícitos—eles afirmam que um TypeClass[A] implícito existe. Use implicitly[TypeClass[A]] (Scala 2) ou summon[TypeClass[A]] (Scala 3) para recuperá-lo. Context bounds tornam restrições de type class legíveis: def sort[A: Ordering]. Múltiplos bounds empilham: [A: Ordering: Numeric]. Esta é a maneira idiomática de expressar requisitos de type class.

scala
// Context bound: [A: Ordering] means there's an implicit Ordering[A]
def max[A: Ordering](a: A, b: A): A = {
  val ord = implicitly[Ordering[A]]  // retrieve the implicit
  if (ord.gt(a, b)) a else b
}

// Equivalent to:
def max2[A](a: A, b: A)(implicit ord: Ordering[A]): A =
  if (ord.gt(a, b)) a else b

// summon (Scala 3) instead of implicitly
def max3[A: Ordering](a: A, b: A): A = {
  val ord = summon[Ordering[A]]
  if (ord.gt(a, b)) a else b
}

// Type class evidence
def sort[A: Ordering](list: List[A]): List[A] =
  list.sorted  // uses the implicit Ordering

// Multiple context bounds
def process[A: Ordering: Numeric](x: A, y: A): A = ???

Implicit Scope e Companion Objects

Implicit scope é mais amplo que apenas o escopo atual—inclui companion objects dos tipos envolvidos. É por isso que você não precisa importar Ordering[Int]: ele vive no companion de Int. Esse mecanismo torna as type classes ergonômicas: defina a instância no companion do tipo, e ela é automaticamente disponível. Package objects contêm implicits compartilhados para um package. Esse design permite uso de type class sem imports.

scala
// Implicit in companion object is found automatically
case class UserId(value: Long)
object UserId {
  implicit val ordering: Ordering[UserId] =
    Ordering.by(_.value)
}

// No import needed—companion object implicits are in scope
List(UserId(3), UserId(1), UserId(2)).sorted
// Works because Ordering[UserId] is in UserId's companion

// Implicit scope includes:
// 1. Companion object of the type (UserId)
// 2. Companion object of the type class (Ordering)
// 3. Companion objects of type parameters

// This is why Int has an Ordering:
// object Int { implicit val ord: Ordering[Int] = ... }

// Package object for shared implicits
package object myapp {
  implicit val ec: ExecutionContext = ExecutionContext.global
  type Id = Long
}
12

Type Classes

Definindo uma Type Class

Uma type class é uma trait parametrizada por tipo, com instâncias fornecendo comportamento para tipos específicos. Diferente da herança, você pode adicionar instâncias de type class retroativamente (para tipos que você não possui). Show[A] define como exibir A. Instâncias vivem no companion object (implicit scope automático). Isso é polimorfismo ad-hoc—comportamento diferente por tipo sem modificar os tipos. Type classes são a abstração mais poderosa de Scala.

scala
// Type class: a trait parameterized by type
trait Show[A] {
  def show(a: A): String
}

// Instances for specific types
object Show {
  // Instance for Int
  implicit val showInt: Show[Int] = (a: Int) => a.toString

  // Instance for String
  implicit val showString: Show[String] = (s: String) => s""$s""

  // Instance for List (recursive)
  implicit def showList[A](implicit s: Show[A]): Show[List[A]] =
    (list: List[A]) => list.map(s.show).mkString("[", ", ", "]")
}

// Usage with implicit parameter
def print[A](a: A)(implicit s: Show[A]): Unit =
  println(s.show(a))

print(42)           // 42
print("hello")      // "hello"
print(List(1, 2, 3))  // [1, 2, 3]

Usando Type Classes (syntactic sugar)

Context bounds [A: Show] + summon recuperam instâncias de type class. Extension methods (implicit class) adicionam métodos como .show que usam a type class. Essa combinação fornece uma API limpa: 42.show funciona se Show[Int] existe. A biblioteca padrão fornece muitas type classes: Numeric, Ordering, Eq, Monoid (Cats). Importar syntax (Numeric.Implicits._) adiciona operadores como + e sum que usam a type class.

scala
// Context bound syntax
def printAll[A: Show](items: List[A]): Unit =
  items.foreach(a => println(summon[Show[A]].show(a)))

// Extension methods via implicit class
implicit class ShowOps[A](val a: A) extends AnyVal {
  def show(implicit s: Show[A]): String = s.show(a)
}

42.show           // "42"
"hi".show         // ""hi""
List(1,2).show    // "[1, 2]"

// Combining: type class + extension methods
def format[A: Show](a: A): String = a.show

// Standard library type classes
def sum[A: Numeric](xs: List[A]): A =
  summon[Numeric[A]].plus(xs.head, xs.tail.foldLeft(
    summon[Numeric[A]].zero)(summon[Numeric[A]].plus))

// Or with syntax:
import Numeric.Implicits._
def sum2[A: Numeric](xs: List[A]): A = xs.sum

Type Classes Comuns (Cats/Scalaz)

Cats e Scalaz fornecem type classes padrão. Monoid (empty + combine) permite agregação genérica. Functor (map) e Monad (pure + flatMap) abstraem sobre containers (List, Option, Future, IO). Eq fornece igualdade type-safe (sem comparações cross-type acidentais). Esses compõem: um Monad é um Functor, um Monoid é um Semigroup. Type classes permitem escrever código genérico e reutilizável que funciona em muitos tipos.

scala
// Monoid: combine values with empty
trait Monoid[A] {
  def empty: A
  def combine(a: A, b: A): A
}
object Monoid {
  implicit val intAdd: Monoid[Int] = new Monoid[Int] {
    def empty = 0
    def combine(a: Int, b: Int) = a + b
  }
  implicit def listMonoid[A]: Monoid[List[A]] = new Monoid[List[A]] {
    def empty = Nil
    def combine(a: List[A], b: List[A]) = a ++ b
  }
}

// Functor: map over structure
trait Functor[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]
}

// Monad: chain operations
trait Monad[F[_]] {
  def pure[A](a: A): F[A]
  def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
}

// Eq: type-safe equality
trait Eq[A] {
  def eqv(a: A, b: A): Boolean
}

// Semigroup: combine (no empty)
trait Semigroup[A] {
  def combine(a: A, b: A): A
}

Leis e Testes de Type Classes

Leis de type class são propriedades matemáticas que instâncias devem satisfazer. Monoid exige associatividade e identidade. Functor exige identidade e preservação de composição. Bibliotecas como Cats fornecem definições de leis; discipline + ScalaCheck as testam automaticamente. Leis são o motivo pelo qual type classes são poderosas: código genérico (como foldMap) funciona corretamente para qualquer instância que respeita as leis. Sempre verifique se suas instâncias satisfazem as leis—bugs em instâncias quebram todo código genérico que as usa.

scala
// Type class laws: properties that must hold
// Monoid laws:
// 1. Left identity:  combine(empty, a) == a
// 2. Right identity: combine(a, empty) == a
// 3. Associativity:  combine(a, combine(b, c)) == combine(combine(a, b), c)

// Functor laws:
// 1. Identity:    map(fa)(identity) == fa
// 2. Composition: map(fa)(f andThen g) == map(map(fa)(f))(g)

// Testing laws with ScalaCheck (discipline)
import org.scalacheck.Prop.forAll
import cats.kernel.laws.MonoidLaws

class MonoidSpec extends munit.FunSuite with Discipline {
  checkAll("Int Monoid", MonoidLaws[Int].monoid)
}

// Custom law test
def monoidLeftIdentity[A](implicit m: Monoid[A], arb: Arbitrary[A]) =
  forAll { (a: A) =>
    m.combine(m.empty, a) == a
  }

// Laws make type classes trustworthy:
// if an instance satisfies laws, generic code works correctly

Derivação de Type Class (Scala 3)

Scala 3 simplifica a derivação de type class com a palavra-chave 'derives' e Mirror. O compilador pode auto-gerar instâncias para case classes e enums compondo instâncias de elemento. Isso elimina o boilerplate de escrever instâncias para toda case class (comum no Scala 2 com shapeless). Bibliotecas como Cats e Circe suportam derivação do Scala 3. O tipo Mirror dá acesso em tempo de compilação à estrutura de um tipo (tipos de campo, labels) para programação genérica.

scala
// Scala 3: derive type class instances automatically
import scala.deriving.Mirror

trait Show[A] {
  def show(a: A): String
}

object Show {
  // Inline given for derivation
  given showInt: Show[Int] with
    def show(a: Int) = a.toString

  given showString: Show[String] with
    def show(s: String) = s""$s""

  // Derive for products (case classes)
  given showProduct[A](using m: Mirror.ProductOf[A])
      (using ev: Show[m.MirroredElemTypes]): Show[A] with
    def show(a: A): String = ???

  // Or use Scala 3's derivation
  inline given derive[A](using m: Mirror.Of[A]): Show[A] = ???
}

// Auto-derive for case classes
case class Person(name: String, age: Int) derives Show
// Show[Person] is generated automatically
13

For Comprehensions Profundo

For Comprehensions Básicos

For comprehensions são syntactic sugar para flatMap/map/withFilter. Cada <- é flatMap (exceto o último, que é map). if guards tornam-se withFilter. yield faz retornar uma coleção; omitir yield torna-o imperativo (foreach). Isso funciona em qualquer tipo com flatMap/map (Monad): List, Option, Future, Try, IO. Dominar for-comprehensions é chave para Scala idiomático—eles substituem maps/flatMaps aninhados por sintaxe sequencial legível.

scala
// For comprehension: syntactic sugar for flatMap/map
val result = for {
  x <- List(1, 2, 3)
  y <- List(10, 20)
} yield x + y
// List(11, 21, 12, 22, 13, 23)

// Desugared:
List(1, 2, 3).flatMap { x =>
  List(10, 20).map { y => x + y }
}

// With filters (if guards)
val evens = for {
  x <- 1 to 10
  if x % 2 == 0
} yield x
// Vector(2, 4, 6, 8, 10)

// Desugared:
(1 to 10).withFilter(_ % 2 == 0).map(identity)

// Without yield: imperative (foreach)
for (x <- 1 to 3) println(x)  // 1, 2, 3

For com Option e Future

For-comprehensions funcionam em qualquer Monad. Com Option, eles short-circuit em None (retornam None). Com Future, eles short-circuit em falha. Isso faz código assíncrono/pode-falhar ler como código imperativo em linha reta, permanecendo funcional. Cada linha <- pode depender de bindings anteriores. Isso é muito mais limpo que chamadas flatMap aninhadas. A mesma sintaxe funciona para Try, Either, IO e monads customizados.

scala
// Option: chain operations that might return None
def getUser(id: Int): Option[User] = ???
def getEmail(user: User): Option[String] = ???

val email: Option[String] = for {
  user <- getUser(42)
  email <- getEmail(user)
} yield email

// Desugared:
getUser(42).flatMap(user => getEmail(user).map(email => email))

// Future: chain async operations
val result: Future[Int] = for {
  user <- fetchUser(1)      // Future[User]
  posts <- fetchPosts(user) // Future[List[Post]]
} yield posts.size

// If any returns None/failed Future, the whole chain short-circuits
// This is the power of monadic composition

For com Either e Tratamento de Erros

Either é o tratamento de erros tipado de Scala. For-comprehensions encadeiam Eithers, short-circuitando em Left (erro). Isso é tratamento de erros funcional—sem exceções, erros são valores. O tipo Left é o erro (geralmente String ou uma sealed trait). Either é right-biased no Scala 2.12+ (map/flatMap operam em Right). Esse padrão substitui try/catch por propagação de erros composável e type-safe. Validated do Cats é uma alternativa para acumular erros.

scala
// Either for error handling
def parseAge(s: String): Either[String, Int] =
  s.toIntOption.toRight(s"not a number: $s")

def validateAge(age: Int): Either[String, Int] =
  if (age >= 0) Right(age) else Left(s"negative: $age")

val result: Either[String, Int] = for {
  age <- parseAge("30")     // Right(30)
  valid <- validateAge(age) // Right(30)
} yield valid

// With error in chain
val error: Either[String, Int] = for {
  age <- parseAge("abc")    // Left("not a number: abc")
  valid <- validateAge(age) // skipped!
} yield valid
// error == Left("not a number: abc")

// Scala 3: for-comprehensions work with Either directly
// (Scala 2 needed either.map(_.right) or withFilter)

Desugaring e Monads Customizados

Qualquer tipo com flatMap e map suporta for-comprehensions—este é o padrão Monad. Defina esses métodos no seu tipo para habilitar for-syntax. O = (não <-) cria um binding local (não desugado para flatMap). Entender desugaring ajuda a depurar comprehensions complexos e implementar monads customizados. O compilador traduz for-comprehensions para cadeias flatMap/map/withFilter. É por isso que for funciona uniformemente em List, Option, Future, IO, etc.

scala
// For-comprehensions require flatMap, map, withFilter
// Define your own monad to use for-comprehensions

case class Box[A](value: A) {
  def map[B](f: A => B): Box[B] = Box(f(value))
  def flatMap[B](f: A => Box[B]): Box[B] = f(value)
  def withFilter(p: A => Boolean): Box[A] =
    if (p(value)) this else throw new NoSuchElementException
}

val result = for {
  x <- Box(10)
  y <- Box(20)
  if x < y
} yield x + y
// Box(30)

// Desugaring steps:
// 1. Box(10).flatMap { x =>
// 2.   Box(20).withFilter(_ > x... wait, order matters
// Actual:
// Box(10).flatMap(x =>
//   Box(20).withFilter(y => x < y).map(y => x + y))

// Assignment within for (= instead of <-)
for {
  x <- List(1, 2, 3)
  doubled = x * 2  // local val
} yield doubled

For vs Map/FlatMap (quando usar)

Use for-comprehensions para 2+ operações dependentes—são mais legíveis que flatMaps aninhados. Para uma única transformação, map é mais claro. Para aplainar, flatMap diretamente. Para efeitos colaterais (sem resultado), use for sem yield. For-comprehensions brilham quando cada passo depende do anterior (encadeamento monádico). Eles fazem código assíncrono/de tratamento de erros ler sequencialmente. Evite fors profundamente aninhados (>5 níveis)—extraia helpers para legibilidade.

scala
// For: sequential, dependent operations
val result = for {
  user <- fetchUser(id)
  profile <- fetchProfile(user.id)
  avatar <- fetchAvatar(profile.avatarId)
} yield avatar

// Equivalent with flatMap (harder to read):
fetchUser(id).flatMap(user =>
  fetchProfile(user.id).flatMap(profile =>
    fetchAvatar(profile.avatarId)))

// Map: single transformation (no chaining)
users.map(_.name)  // simple, use map

// FlatMap: when you need to chain but for is overkill
users.flatMap(_.posts)  // List[Post]

// For without yield: side effects
for (user <- users) {
  saveToDb(user)
  sendEmail(user)
}

// Guidelines:
// - 1 operation: use map/flatMap directly
// - 2+ dependent operations: use for-comprehension
// - Side effects: use for without yield
14

Scala 3: Given e Using

Instâncias Given (substituindo implicit val)

Scala 3 substitui implicit val/def por 'given'. givens são mais claros e explícitos. Givens anônimos (given Type = ...) têm nomes gerados pelo compilador. 'given T with' define instâncias com múltiplos métodos. Givens condicionais (given [A: Ordering]: Ordering[List[A]]) substituem implicit defs. givens em companion objects estão em implicit scope automaticamente, assim como no Scala 2. A mudança de palavra-chave reduz a sobrecarga de 'implicit' (que significava 4 coisas no Scala 2).

scala
// Scala 2: implicit val
// implicit val ec: ExecutionContext = ExecutionContext.global

// Scala 3: given
given ec: ExecutionContext = ExecutionContext.global

// Anonymous given (inferred name)
given ExecutionContext = ExecutionContext.global

// Given with 'with' for complex types
given Show[Int] with
  def show(a: Int): String = a.toString

// Given in companion object (automatic scope)
object MyType:
  given Ordering[MyType] = Ordering.by(_.id)

// Conditional given (like implicit def)
given [A: Ordering]: Ordering[List[A]] with
  def compare(a: List[A], b: List[A]): Int =
    a.zip(b).find((x, y) => x != y) match
      case Some((x, y)) => summon[Ordering[A]].compare(x, y)
      case None => a.length - b.length

Cláusulas Using (substituindo implicit params)

Scala 3 substitui parâmetros implícitos por 'using'. Isso separa os dois significados de 'implicit' (parâmetros vs conversões). cláusulas using são passadas explicitamente com a palavra-chave 'using'. Context bounds [A: T] permanecem os mesmos. summon[T] substitui implicitly[T] (nome mais claro). A palavra-chave using torna óbvio nos call sites quando você está fornecendo um contexto. Isso é uma mudança puramente sintática—a semântica é a mesma de parâmetros implícitos.

scala
// Scala 2: implicit parameter
// def process[A](data: List[A])(implicit ec: ExecutionContext): Unit

// Scala 3: using
def process[A](data: List[A])(using ec: ExecutionContext): Unit =
  data.foreach(println)

// Context bound (unchanged)
def sort[A: Ordering](list: List[A]): List[A] = list.sorted

// Multiple using clauses
def log(msg: String)(using level: Level, logger: Logger): Unit =
  logger.log(level, msg)

// summon replaces implicitly
def max[A: Ordering](a: A, b: A): A =
  val ord = summon[Ordering[A]]
  if ord.gt(a, b) then a else b

// Provide explicitly with 'using'
process(data)(using myEC)
log("hello")(using Level.INFO, myLogger)

Extension Methods (Scala 3)

Scala 3 substitui implicit classes por 'extension'—mais claro e focado. extension (self: T) define métodos em T. Extensions podem ser genéricas (extension [A]). Combinadas com using, fornecem syntax de type class (42.show). Extensions são apenas adições de métodos—não criam objetos wrapper (zero overhead com AnyVal no Scala 2, nativo no Scala 3). Esta é a maneira idiomática de adicionar métodos a tipos existentes no Scala 3.

scala
// Scala 2: implicit class
// implicit class RichInt(val self: Int) extends AnyVal {
//   def squared: Int = self * self
// }

// Scala 3: extension
extension (self: Int)
  def squared: Int = self * self
  def times(f: => Unit): Unit = (1 to self).foreach(_ => f)

5.squared   // 25
3.times { println("hi") }

// Extension on generic type
extension [A](self: List[A])
  def takeWhileInclusive(p: A => Boolean): List[A] = ???

// Extension with using (type class syntax)
extension [A](self: A) def show(using s: Show[A]): String = s.show(self)

42.show  // uses given Show[Int]

// Multiple extensions in one block
extension (s: String)
  def isBlank: Boolean = s.trim.isEmpty
  def words: List[String] = s.split(" ").toList

Enums e ADTs (Scala 3)

Enums do Scala 3 substituem sealed trait + case objects para ADTs. São mais concisos e suportam parâmetros, campos e métodos. Cases de enum podem ter parâmetros (como case classes). Pattern matching é verificado por exaustividade. Enums podem ser genéricos (Option[A]). Isso unifica enums e ADTs em um constructo. Para hierarquias abertas (extensíveis), use sealed trait + case classes ainda. Para enumerações/ADTs fechados, enum é mais limpo.

scala
// Scala 3 enum (replaces sealed trait + case objects)
enum Color:
  case Red, Green, Blue

enum HttpStatus(val code: Int):
  case Ok extends HttpStatus(200)
  case NotFound extends HttpStatus(404)
  case Error extends HttpStatus(500)

// Pattern matching (exhaustive)
def describe(c: Color): String = c match
  case Color.Red => "red"
  case Color.Green => "green"
  case Color.Blue => "blue"

// Access fields
HttpStatus.Ok.code  // 200

// Parameterized enum cases
enum Option[+A]:
  case Some(value: A)
  case None

// ADT with methods
enum Tree[+A]:
  case Leaf(value: A)
  case Node(left: Tree[A], right: Tree[A])

  def size: Int = this match
    case Leaf(_) => 1
    case Node(l, r) => l.size + r.size + 1

Definições Top-Level e Indentação

Scala 3 permite definições top-level—sem necessidade de envolver tudo em um object. Isso simplifica a estrutura de arquivos (como Python/Go). A nova sintaxe suporta tanto chaves quanto indentação significativa (opcional). if/then substitui if/else com parênteses. match pode ser usado como uma expressão sem chaves. Essas mudanças tornam o Scala 3 mais acessível enquanto mantém compatibilidade com versões anteriores. Você pode misturar estilos—use chaves onde adicionam clareza, indentação onde reduz ruído.

scala
// Scala 3: top-level definitions (no class wrapper needed)
// File: MyMath.scala
def add(a: Int, b: Int): Int = a + b  // top-level function

val Pi: Double = 3.14159  // top-level val

type Id = Long  // top-level type alias

given Show[Int] = (a: Int) => a.toString  // top-level given

extension (i: Int) def squared: Int = i * i  // top-level extension

// Indentation-based syntax (optional, braces still work)
def factorial(n: Int): Int =
  if n <= 1 then 1
  else n * factorial(n - 1)

// Or with braces:
def factorial2(n: Int): Int = {
  if (n <= 1) 1
  else n * factorial2(n - 1)
}

// if/then, match/case without braces
val sign = if x > 0 then 1 else -1
val desc = x match
  case 0 => "zero"
  case _ => "nonzero"
15

Extension Methods e Syntax

Implicit Classes (Scala 2)

implicit class (Scala 2.10+) adiciona extension methods a tipos existentes. Estender AnyVal torna-a zero-allocation (o compilador apaga o wrapper). A classe deve receber um único parâmetro de construtor (o tipo sendo estendido). Métodos na implicit class tornam-se disponíveis no tipo estendido. É assim que Scala enriquece Int, String, etc. Coloque implicit classes em um package object ou utility object para compartilhamento. No Scala 3, use 'extension' em vez.

scala
// Scala 2: implicit class for extension methods
import scala.language.implicitConversions

implicit class RichString(val s: String) extends AnyVal {
  def wordCount: Int = s.split("\\s+").length
  def slug: String = s.toLowerCase.replaceAll("[^a-z0-9]+", "-")
  def encrypt(key: Int): String = s.map(c => (c + key).toChar)
}

"Hello World".wordCount  // 2
"My Blog Post!".slug     // "my-blog-post-"
"abc".encrypt(1)         // "bcd"

// AnyVal avoids allocation (zero overhead)
// implicit class must be in a trait, class, or object

// For generic extensions
implicit class RichList[A](val list: List[A]) extends AnyVal {
  def middle: Option[A] = list.lift(list.length / 2)
}

List(1, 2, 3, 4, 5).middle  // Some(3)

Extension Methods na Prática

Extension methods são a maneira padrão de adicionar utilitários a coleções e outros tipos. distinctBy, chunked, tap são adições comuns. Os operadores === e =!= (via type class Eq) fornecem igualdade type-safe (diferente de == que permite cross-type). Agrupe extensões em um object e importe onde necessário. Isso mantém os tipos centrais limpos enquanto permite métodos específicos de domínio. A biblioteca de coleções do Scala usa esse padrão extensivamente.

scala
// Common pattern: add utility methods to collections
object CollectionExtensions {
  implicit class RichSeq[A](val seq: Seq[A]) extends AnyVal {
    def chunked(size: Int): Seq[Seq[A]] =
      seq.grouped(size).toSeq

    def distinctBy[B](f: A => B): Seq[A] =
      seq.groupBy(f).values.map(_.head).toSeq

    def tap(f: A => Unit): Seq[A] = {
      seq.foreach(f)
      seq
    }
  }
}

import CollectionExtensions._

List(1, 2, 3, 4, 5).chunked(2)  // List(List(1,2), List(3,4), List(5))
List("aa", "bb", "ab").distinctBy(_.head)  // List("aa", "ab")
List(1, 2, 3).tap(println)  // prints 1,2,3, returns List(1,2,3)

// Type class syntax via extension
implicit class EqOps[A](val a: A) extends AnyVal {
  def ===(b: A)(implicit eq: Eq[A]): Boolean = eq.eqv(a, b)
  def =!=(b: A)(implicit eq: Eq[A]): Boolean = !eq.eqv(a, b)
}

Conversões Implícitas (Use com Cuidado)

Conversões implícitas convertem automaticamente tipos, mas são perigosas—o comportamento do código torna-se não óbvio. Scala 2.10+ exige opt-in explícito (scala.language.implicitConversions). Prefira extension methods (que adicionam métodos sem mudar tipos) em vez de conversões (que mudam tipos). Usos legítimos: interop com Java (converter entre tipos de coleção), construção de DSL. O compilador avisa sobre conversões implícitas—trate esses avisos a sério. No Scala 3, given Conversion[T, U] é o mecanismo explícito.

scala
import scala.language.implicitConversions

// Implicit conversion between types
implicit def stringToInt(s: String): Int = s.toInt
val n: Int = "42"  // stringToInt("42")

// Dangerous: can cause surprising behavior
implicit def intToBoolean(n: Int): Boolean = n != 0
if (1) println("yes")  // works! (intToBoolean(1))

// Safer: use extension methods instead
extension (n: Int) def toBool: Boolean = n != 0
if (1.toBool) println("yes")

// When implicit conversions are appropriate:
// 1. Java interop (java.util.List <-> scala.List)
implicit def javaListToScala[A](jl: java.util.List[A]): List[A] =
  import scala.jdk.CollectionConverters._
  jl.asScala.toList

// 2. Backward compatibility layers
// 3. DSL construction (use sparingly)

// Enable per-file: import scala.language.implicitConversions

Programação em Nível de Tipo

Programação em nível de tipo codifica informações em tipos que o compilador verifica. Phantom types (parâmetros de tipo não usados) rastreiam estado (Open/Closed) prevenindo uso indevido (não pode ler um arquivo fechado). Peano naturals representam números como tipos. Isso permite garantias de correção em tempo de compilação—bugs tornam-se erros de compilação. Usado em state machines, unidades de medida, vetores dimensionados. Poderoso, mas complexo—use quando a segurança vale a complexidade de tipo. Bibliotecas como shapeless (Scala 2) permitem programação avançada em nível de tipo.

scala
// Phantom types: track state at type level
sealed trait State
trait Open extends State
trait Closed extends State

class File[S <: State] private (val path: String)

object File {
  def open(path: String): File[Open] = new File[Open](path)
}

def close[S <: Open](f: File[S]): File[Closed] =
  new File[Closed](f.path)  // unsafe cast internally

def read[S <: Open](f: File[S]): String = "data"
// def read[S <: Closed](f: File[S]): String  // compile error

val f = File.open("test.txt")
read(f)  // OK: f is File[Open]
val closed = close(f)
// read(closed)  // COMPILE ERROR: closed is File[Closed]

// Type-level natural numbers (Peano)
sealed trait Nat
trait Zero extends Nat
trait Succ[N <: Nat] extends Nat

// Compile-time list length check
type _0 = Zero
type _1 = Succ[_0]
type _2 = Succ[_1]

Opaque Types (Scala 3)

Opaque types (Scala 3) criam newtypes zero-overhead—diferente de value classes (AnyVal), eles nunca fazem boxing e são verdadeiramente apenas o tipo subjacente em runtime. A distinção de tipo existe apenas em tempo de compilação, prevenindo confusões (Celsius vs Fahrenheit, UserId vs Long). Dentro do object definidor, o tipo e seu tipo subjacente são intercambiáveis; fora, são distintos. Este é o padrão 'newtype' do Haskell—tipos orientados a domínio sem custo de runtime. Use para IDs, unidades e primitivas de domínio.

scala
// Scala 2: value classes for zero-overhead wrappers
// case class UserId(value: Long) extends AnyVal

// Scala 3: opaque types (true zero overhead, no boxing)
object Types:
  type UserId = Long
  object UserId:
    def apply(value: Long): UserId = value

  type Email = String
  object Email:
    def apply(s: String): Email =
      require(s.contains("@"), "invalid email")
      s

import Types.*
val id: UserId = UserId(42)  // just a Long at runtime
val email: Email = Email("[email protected]")

// UserId and Long are NOT interchangeable outside the object
// def wrong(x: Long): UserId = x  // ERROR
// But inside the object, they're the same

// Newtype pattern: domain types without overhead
type Celsius = Double
type Fahrenheit = Double
// Prevents mixing up Celsius and Fahrenheit
def toF(c: Celsius): Fahrenheit = c * 9 / 5 + 32
16

Testes (ScalaTest e ScalaCheck)

Estilos do ScalaTest

ScalaTest oferece múltiplos estilos. FunSuite é o mais simples (test("name") { ... }). FlatSpec é estilo BDD ("A Stack" should "..."). Matchers fornece asserções legíveis (shouldBe, should contain, should throw). Escolha um estilo por projeto para consistência. FunSuite é popular para testes de unidade; FlatSpec para testes focados em comportamento. Todos os estilos suportam os mesmos matchers e hooks de lifecycle. O estilo afeta a sintaxe, não as capacidades.

scala
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers

// FunSuite: simple test functions
class MyTest extends AnyFunSuite with Matchers {
  test("addition works") {
    1 + 1 should be (2)
    1 + 1 shouldBe 2
    List(1, 2, 3) should contain (2)
  }

  test("string operations") {
    "hello".length shouldBe 5
    "hello" should startWith ("he")
  }
}

// FlatSpec: BDD-style
import org.scalatest.flatspec.AnyFlatSpec
class StackSpec extends AnyFlatSpec with Matchers {
  "A Stack" should "pop values in LIFO order" in {
    val stack = Stack(1, 2, 3)
    stack.pop() shouldBe 3
  }

  it should "throw on empty pop" in {
    val stack = Stack()
    a [NoSuchElementException] shouldBe thrownBy(stack.pop())
  }
}

// WordSpec, FreeSpec, PropSpec also available

Asserções e Matchers

Matchers fornecem asserções expressivas. shouldBe/should be para igualdade. contain, have size, have key para coleções. startWith/endWith/include para strings. thrownBy para exceções. Matchers customizados (be >, be <=) para comparações. A DSL 'should' lê como inglês, tornando os testes auto-documentáveis. Para asserções complexas, use matchers customizados ou assert(condition) simples. Evite encadear matchers em excesso—legibilidade sobre esperteza.

scala
import org.scalatest.matchers.should.Matchers._

// Equality
result shouldBe 42
result should be (42)
result should equal (42)

// Collections
list should contain (5)
list should not contain (0)
list shouldBe empty
list should have size 5
list should contain inOrder (1, 2, 3)
map should contain key ("name")
map should contain value ("Alice")

// Strings
s should startWith ("Hello")
s should endWith ("world")
s should include ("lo")
s should fullyMatch regex "H.*d".r

// Exceptions
a [IOException] should be thrownBy riskyOp()
the [IOException] thrownBy riskyOp() should have message "fail"

// Custom matchers
result should be > 0
result should be <= 100

// Type checks
result shouldBe a [List[_]]
result should be an [IllegalArgumentException]

BeforeAndAfter e Fixtures

BeforeAndAfterEach executa setup/teardown ao redor de cada teste. Sobrescreva beforeEach/afterEach. Para gerenciamento de recursos, o loan pattern (withDb { conn => ... }) é mais limpo—garante limpeza via try/finally e torna o recurso explícito. ScalaTest também suporta contextos de fixture (FixtureContext) e fixtures compartilhados via traits. Prefira o loan pattern ou métodos de fixture em vez de estado beforeEach mutável—é mais funcional e evita bugs de estado compartilhado entre testes.

scala
import org.scalatest.BeforeAndAfterEach
import org.scalatest.funsuite.AnyFunSuite

class DbTest extends AnyFunSuite with BeforeAndAfterEach {
  var conn: Connection = _

  override def beforeEach(): Unit = {
    conn = DriverManager.getConnection("jdbc:h2:mem:test")
    conn.execute("CREATE TABLE users (id INT, name VARCHAR)")
  }

  override def afterEach(): Unit = {
    conn.close()
  }

  test("insert works") {
    conn.execute("INSERT INTO users VALUES (1, 'Alice')")
    val count = conn.query("SELECT COUNT(*) FROM users")
    count shouldBe 1
  }
}

// Fixture via loan pattern
class FixtureTest extends AnyFunSuite {
  def withDb(test: Connection => Unit): Unit = {
    val conn = DriverManager.getConnection("jdbc:h2:mem:test")
    try test(conn) finally conn.close()
  }

  test("query works") {
    withDb { conn =>
      conn.execute("INSERT ...")
      // assertions
    }
  }
}

Property-Based Testing (ScalaCheck)

ScalaCheck gera entradas de teste aleatórias, testando propriedades em muitos casos. forAll executa a propriedade com 100 entradas aleatórias por padrão. whenever filtra entradas. Tipos Gen customizados restringem dados gerados (Gen.choose, Gen.nonEmptyListOf). Isso captura casos extremos que você perderia com testes baseados em exemplo (listas vazias, números negativos, valores grandes). Testes table-driven (Table) são para casos específicos. Property-based testing é poderoso para funções puras e transformações de dados—defina o que deve ser sempre verdadeiro.

scala
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
import org.scalacheck.Prop.forAll

class ListSpec extends AnyFunSuite with ScalaCheckPropertyChecks {
  // Property: reversing twice = identity
  test("reverse twice is identity") {
    forAll { (xs: List[Int]) =>
      xs.reverse.reverse shouldBe xs
    }
  }

  // Property with conditions
  test("head of sorted list is min") {
    forAll { (xs: List[Int]) =>
      whenever(xs.nonEmpty) {
        xs.sorted.head shouldBe xs.min
      }
    }
  }

  // Custom generators
  import org.scalacheck.Gen
  val smallInt = Gen.choose(1, 100)
  val nonEmptyList = Gen.nonEmptyListOf(smallInt)

  test("custom generator") {
    forAll(nonEmptyList) { (xs: List[Int]) =>
      xs should not be empty
      xs.forall(_ >= 1) shouldBe true
    }
  }

  // Table-driven tests
  test("addition table") {
    val cases = Table(
      ("a", "b", "sum"),
      (1, 2, 3),
      (10, 20, 30),
      (-1, 1, 0)
    )
    forAll(cases) { (a, b, sum) =>
      a + b shouldBe sum
    }
  }
}

Mocking e Test Doubles

Mockito (via ScalaTestPlus) cria test doubles. mock[T] cria um mock; when(...).thenReturn(...) faz stub de métodos; verify verifica interações. Use mocks para isolar a unidade em teste de dependências (bancos de dados, APIs). Argument matchers (argThat) verificam argumentos específicos. Não faça mock em excesso—se você está mockando tudo, teste a integração real em vez. Prefira fakes (implementações em memória) em vez de mocks para dependências complexas—são mais robustos e legíveis.

scala
import org.scalatest.funsuite.AnyFunSuite
import org.scalatestplus.mockito.MockitoSugar
import org.mockito.Mockito._

class UserServiceSpec extends AnyFunSuite with MockitoSugar {
  test("getUser returns user from repo") {
    // Create mock
    val repo = mock[UserRepository]
    val user = User(1, "Alice")

    // Stub: when X then Y
    when(repo.findById(1)).thenReturn(Some(user))

    val service = new UserService(repo)
    val result = service.getUser(1)

    result shouldBe Some(user)

    // Verify: was X called?
    verify(repo).findById(1)
    verify(repo, never()).findById(2)
  }

  test("createUser saves to repo") {
    val repo = mock[UserRepository]
    val service = new UserService(repo)

    service.createUser("Bob")

    // Verify with argument matcher
    verify(repo).save(argThat((u: User) => u.name == "Bob"))
  }
}

// Stubbing exceptions
when(repo.findById(99)).thenThrow(new RuntimeException("not found"))
17

Funções de Ordem Superior e FP

Funções como Valores

Funções são valores em Scala—você pode armazená-las, passá-las e retorná-las. (A, B) => C é o tipo de função. Eta-expansion (multiply _) converte métodos em valores de função. Funções de ordem superior (recebem/retornam funções) permitem abstrações poderosas: map, filter, reduce são HOFs. Composição de funções (andThen, compose) constrói pipelines. Currying (adder(5) retorna uma função) aplicação parcial. Esta é a fundação da programação funcional em Scala.

scala
// Functions are first-class values
val add: (Int, Int) => Int = (a, b) => a + b
val square: Int => Int = x => x * x

// Apply
add(2, 3)  // 5
square(4)  // 16

// Method to function (eta-expansion)
def multiply(a: Int, b: Int): Int = a * b
val mul = multiply _  // or just 'multiply' in Scala 3
mul(3, 4)  // 12

// Higher-order: function taking function
def applyTwice(f: Int => Int, x: Int): Int = f(f(x))
applyTwice(square, 2)  // 16

// Higher-order: function returning function
def adder(n: Int): Int => Int = _ + n
val add5 = adder(5)
add5(10)  // 15

// Function composition
val f: Int => Int = _ + 1
val g: Int => Int = _ * 2
val h = f andThen g  // f then g: (x+1)*2
val h2 = f compose g  // g then f: (x*2)+1
h(3)  // 8
h2(3)  // 7

Currying e Aplicação Parcial

Currying divide uma função em múltiplas listas de parâmetros, permitindo aplicação parcial (fixe alguns argumentos, obtenha uma função para o resto). Isso auxilia a inferência de tipos (parâmetros anteriores restringem posteriores) e cria funções configuráveis (withDb(config)). O placeholder _ aplica parcialmente: sum(1, _) cria uma função. Múltiplas listas de parâmetros são o mecanismo de currying do Scala. Use currying quando alguns argumentos são 'configuração' e outros são 'dados'.

scala
// Curried function: multiple parameter lists
def add(a: Int)(b: Int): Int = a + b
val add5: Int => Int = add(5)  // partial application
add5(10)  // 15

// Multiple parameter lists for type inference
def map[A, B](list: List[A])(f: A => B): List[B] =
  list.map(f)
map(List(1, 2, 3))(x => x * 2)  // A, B inferred from first list

// Curried form (Function types)
val curriedAdd: Int => Int => Int = a => b => a + b
curriedAdd(5)(10)  // 15

// Uncurrying
def uncurriedAdd(a: Int, b: Int): Int = a + b

// Practical: configuration via currying
def withDb(config: DbConfig)(f: Connection => Result): Result = ???
val withMyDb = withDb(myConfig) _
withMyDb { conn => /* ... */ }

// Partial application with placeholder
val sum: (Int, Int) => Int = _ + _
val addOne: Int => Int = sum(1, _)

Funções Puras e Transparência Referencial

Funções puras sempre retornam a mesma saída para a mesma entrada e não têm efeitos colaterais. São fáceis de testar, raciocinar, paralelizar e compor. Transparência referencial significa que você pode substituir uma chamada de função por seu resultado sem mudar o comportamento. Scala não impõe pureza, mas bibliotecas como Cats Effect (IO monad) permitem isolar efeitos colaterais. Core puro + IO nas bordas é uma arquitetura FP comum: a lógica de negócios é pura, E/S é envolvida em IO.

scala
// Pure function: same input → same output, no side effects
def pureAdd(a: Int, b: Int): Int = a + b  // pure

// Impure: depends on external state
var counter = 0
def impureAdd(a: Int): Int = { counter += 1; a + counter }  // impure

// Impure: side effect
def impurePrint(a: Int): Int = { println(a); a }  // side effect

// Referential transparency: can replace call with result
val x = pureAdd(2, 3)
val y = x + x  // same as pureAdd(2, 3) + pureAdd(2, 3)

// Benefits of purity:
// 1. Easy to test (no setup/teardown)
// 2. Easy to reason about (no hidden state)
// 3. Parallelizable (no shared state)
// 4. Memoizable (cache results)
// 5. Composable (predictable)

// IO monad for side effects (Cats Effect)
import cats.effect.IO
val program: IO[Unit] = IO.println("hello")
val mapped: IO[String] = IO.pure("world").map(_.toUpperCase)
// Side effects captured in IO, run at edge of program

Dados Imutáveis e Coleções Persistentes

Estruturas de dados imutáveis retornam novas cópias em modificação, compartilhando estrutura internamente para eficiência (persistent data structures). As coleções do Scala são imutáveis por padrão. case class copy cria cópias modificadas. Para atualizações profundas, bibliotecas Lens (Monocle) fornecem accessors composáveis. Imutabilidade elimina classes inteiras de bugs (race conditions, mutações inesperadas) e torna o código mais fácil de raciocinar. O custo de desempenho é frequentemente aceitável devido a structural sharing (O(log n) não O(n)).

scala
// Immutable: operations return new collections
val list1 = List(1, 2, 3)
val list2 = list1 :+ 4  // List(1, 2, 3, 4)
// list1 is unchanged: List(1, 2, 3)

// Persistent data structures: share structure (efficient)
val map1 = Map("a" -> 1, "b" -> 2)
val map2 = map1 + ("c" -> 3)  // shares structure with map1
// O(log n) due to structural sharing, not O(n) copy

// Case classes: copy for modification
case class User(name: String, age: Int)
val alice = User("Alice", 30)
val older = alice.copy(age = 31)  // new instance, alice unchanged

// Lens (Monocle library) for nested updates
import monocle.macros.GenLens
val ageLens = GenLens[User](_.age)
val updated = ageLens.modify(_ + 1)(alice)  // User("Alice", 31)

// Benefits:
// - No bugs from shared mutation
// - Easy to reason about (values don't change)
// - Free concurrency (no locks needed)
// - Undo/redo trivial (keep old versions)

Recursão e Tail Calls

Tail recursion (onde a chamada recursiva é a última operação) é otimizada pelo compilador Scala para um loop—sem crescimento de pilha. @tailrec verifica isso em tempo de compilação. O padrão accumulator (passando resultado acumulado) converte recursão não-tail em tail recursion. foldLeft/foldRight encapsulam padrões comuns de recursão. Para recursão não-tail profunda, use trampolines (Cats) ou reescreva com folds. Prefira folds em vez de recursão explícita para clareza—são tail-recursive e idiomáticos.

scala
// Regular recursion (can stack overflow)
def factorial(n: Int): Int =
  if (n <= 1) 1 else n * factorial(n - 1)
factorial(10000)  // StackOverflowError!

// Tail recursion: compiler optimizes to loop
import scala.annotation.tailrec
@tailrec
def factorialTail(n: Int, acc: Int = 1): Int =
  if (n <= 1) acc else factorialTail(n - 1, n * acc)
factorialTail(10000)  // works (no stack overflow)

// @tailrec annotation: compiler verifies it's tail-recursive
// Error if not actually tail-recursive

// Trampoline for non-tail recursion
// (Cats' Trampoline or Free monads)
// Converts stack recursion to heap

// Fold as alternative to recursion
def sum(list: List[Int]): Int = list.foldLeft(0)(_ + _)
// foldLeft is tail-recursive internally

// Pattern: accumulator pattern
@tailrec
def reverse[A](list: List[A], acc: List[A] = Nil): List[A] =
  list match
    case Nil => acc
    case head :: tail => reverse(tail, head :: acc)
18

Case Classes e ADTs

Noções Básicas de Case Class

Case classes são a principal ferramenta de modelagem de dados do Scala. O compilador gera equals, hashCode, toString, copy, apply, unapply (para pattern matching) e métodos accessor. São imutáveis por padrão (campos val). Use case classes para DTOs, value objects, mensagens e entidades de domínio (quando imutáveis). O apply do companion object permite construir sem 'new'. copy habilita atualizações não-destrutivas. Case classes são a fundação de ADTs (Algebraic Data Types).

scala
// Case class: immutable data holder with boilerplate generated
case class Person(name: String, age: Int)

val alice = Person("Alice", 30)
val bob = Person("Bob", 25)

// Auto-generated methods:
alice.toString  // "Person(Alice,30)"
alice == Person("Alice", 30)  // true (structural equality)
alice.hashCode  // based on fields

// Pattern matching
alice match
  case Person(name, age) => s"$name is $age"

// Copy with modifications
val older = alice.copy(age = 31)  // Person("Alice", 31)

// Companion object with apply (no 'new' needed)
val p = Person("Charlie", 40)  // Person.apply called

// Fields accessed directly
alice.name  // "Alice"
alice.age   // 30

// Case class with default values
case class Point(x: Double = 0, y: Double = 0)
Point()  // Point(0.0, 0.0)
Point(y = 5)  // Point(0.0, 5.0)

Sealed Traits e ADTs

ADTs (Algebraic Data Types) modelam dados como um conjunto fechado de cases (sealed trait + case classes/objects). 'sealed' significa que todos os subtipos estão no mesmo arquivo, habilitando pattern matching exaustivo—o compilador avisa se você perder um case. Adicionar um novo case mostra todos os lugares que precisam de atualização (refatoração segura). Case objects são singletons (sem parâmetros). Option, List, Either são todos ADTs. Esta é a maneira funcional de modelar domínios—cada variante é um case, o comportamento está no pattern matching.

scala
// ADT: sealed trait + case classes/objects
sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Rectangle(width: Double, height: Double) extends Shape
case class Triangle(a: Double, b: Double, c: Double) extends Shape

// Exhaustive pattern matching (compiler checks all cases)
def area(shape: Shape): Double = shape match
  case Circle(r) => math.Pi * r * r
  case Rectangle(w, h) => w * h
  case Triangle(a, b, c) =>
    val s = (a + b + c) / 2
    math.sqrt(s * (s-a) * (s-b) * (s-c))

// Adding a new case: compiler warns about non-exhaustive matches
case class Square(side: Double) extends Shape
// area now needs a Square case!

// Case objects for singletons (no parameters)
sealed trait Status
case object Active extends Status
case object Inactive extends Status
case object Pending extends Status

// Option and List are ADTs:
// sealed trait Option[+A]
// case class Some[A](value: A) extends Option[A]
// case object None extends Option[Nothing]

Recursos de Case Class

Case classes podem ter métodos, apply customizado (para validação), implementar traits e ser genéricas. apply customizado no companion object pode validar antes da construção (torne estados ilegais irrepresentáveis). Case classes podem implementar traits para polimorfismo. O construtor privado (case class Email private) força usar o apply do companion para validação. Isso combina modelagem de dados com encapsulamento—construtores que não podem falhar silenciosamente.

scala
// Case class with methods
case class Vec2(x: Double, y: Double) {
  def +(other: Vec2): Vec2 = Vec2(x + other.x, y + other.y)
  def magnitude: Double = math.sqrt(x*x + y*y)
  def dot(other: Vec2): Double = x*other.x + y*other.y
}

Vec2(1, 2) + Vec2(3, 4)  // Vec2(4.0, 6.0)
Vec2(3, 4).magnitude  // 5.0

// Case class with custom apply (validation)
case class Email private (value: String)
object Email {
  def apply(value: String): Email = {
    require(value.contains("@"), "invalid email")
    new Email(value)  // bypass public apply
  }
}
// Email("notanemail")  // IllegalArgumentException
Email("[email protected]")  // works

// Case class implementing trait
trait Jsonable {
  def toJson: String
}
case class User(name: String, age: Int) extends Jsonable {
  def toJson: String = s"""{"name":"$name","age":$age}"""
}

// Case class with type parameters
case class Box[A](value: A) {
  def map[B](f: A => B): Box[B] = Box(f(value))
}

Pattern Matching Profundo

Pattern matching é a poderosa ferramenta de destructuring do Scala. Suporta: padrões literais (0, "hello"), padrões de tipo (s: String), padrões de case class (User(name, age)), guards (if condition), padrões aninhados e binding (@). O compilador verifica exaustividade para tipos sealed. Padrões são tentados de cima para baixo. Use @ para vincular o valor inteiro enquanto extrai partes. Pattern matching substitui cadeias if-else, verificações de tipo e destructuring por uma sintaxe unificada e legível.

scala
// Various pattern types
val x: Any = (1, "hello")

x match
  case (a: Int, b: String) => s"int $a, string $b"
  case (a, b) => s"pair: $a, $b"
  case _ => "other"

// Guards
def classify(n: Int): String = n match
  case n if n < 0 => "negative"
  case 0 => "zero"
  case n if n % 2 == 0 => "even positive"
  case _ => "odd positive"

// Case class patterns (nested)
case class Point(x: Int, y: Int)
case class Shape(center: Point, size: Int)

def isOrigin(s: Shape): Boolean = s match
  case Shape(Point(0, 0), _) => true
  case _ => false

// Named patterns (bind the whole while extracting)
case class User(name: String, age: Int)
def describe(u: User): String = u match
  case u @ User(name, age) if age < 18 => s"$name is a minor"
  case User(name, _) => s"$name is an adult"

// Type patterns
def handle(x: Any): String = x match
  case s: String => s"string: $s"
  case n: Int => s"int: $n"
  case list: List[_] => s"list of size ${list.size}"
  case None => "none"
  case _ => "unknown"

Extractors e Padrões Customizados

Extractors (unapply) habilitam pattern matching em qualquer tipo, não apenas case classes. Isso desacopla o padrão de matching da representação de dados. Regexes são extractors (grupos tornam-se bindings). Você pode escrever extractors para tipos externos (JSON, URLs) sem modificá-los. unapply retorna Option[(T1, T2, ...)] para extração, ou Boolean para matching simples. Extractors tornam pattern matching extensível a qualquer fonte de dados. Case classes geram unapply automaticamente; extractors customizados adicionam matching para tipos não-case-class.

scala
// Custom extractor via unapply
object Email {
  def unapply(str: String): Option[(String, String)] = {
    val parts = str.split("@")
    if (parts.length == 2) Some((parts(0), parts(1)))
    else None
  }
}

// Use in pattern matching
"[email protected]" match
  case Email(user, domain) => s"user: $user, domain: $domain"
  case _ => "not an email"

// Boolean extractor (no extracted values)
object Even {
  def unapply(n: Int): Boolean = n % 2 == 0
}

5 match
  case Even() => "even"
  case _ => "odd"

// Extractor with variable arity
object Pair {
  def unapply[A, B](t: (A, B)): Option[(A, B)] = Some(t._1, t._2)
}

// Regex as extractor
val Date = "(\\d{4})-(\\d{2})-(\\d{2})".r
"2024-01-15" match
  case Date(year, month, day) => s"$year/$month/$day"
  case _ => "not a date"

// Practical: parse without case classes
object Json {
  def unapply(s: String): Option[Any] =
    scala.util.Try(ujson.read(s)).toOption
}
19

Coleções Profundo

Coleções Imutáveis

Coleções Scala são imutáveis por padrão. List é uma lista encadeada (O(n) acesso aleatório). Vector é uma árvore com acesso O(log n). Map e Set são baseados em hash. Operações retornam novas coleções, compartilhando estrutura para eficiência.

scala
val list = List(1, 2, 3)
val vector = Vector(1, 2, 3)  // Fast random access
val set = Set(1, 2, 3)
val map = Map("a" -> 1, "b" -> 2)
// All immutable: operations return new collections
val updated = map + ("c" -> 3)  // New map

Operações de Coleção

map transforma elementos. filter seleciona. reduce combina. grouped divide em chunks. flatten mescla coleções aninhadas. flatMap mapeia e aplaina. Todos retornam novas coleções. Coleções lazy usam .view ou .iterator para eficiência.

scala
val nums = (1 to 10).toList
val doubled = nums.map(_ * 2)
val evens = nums.filter(_ % 2 == 0)
val sum = nums.reduce(_ + _)
val grouped = nums.grouped(3).toList  // List(List(1,2,3), List(4,5,6), ...)
val flat = List(List(1,2), List(3,4)).flatten  // List(1,2,3,4)

Pattern Matching em Coleções

Pattern matching funciona em coleções. :: (cons) corresponde head e tail. List(a, b, c) corresponde exatamente 3 elementos. Nil corresponde lista vazia. _ é wildcard. Útil para parsing e destructuring. Matching exaustivo previne bugs.

scala
val list = List(1, 2, 3)
list match {
    case head :: tail => println(s"Head: $head")
    case Nil => println("Empty")
}
List(1, 2, 3) match {
    case List(a, b, c) => println(s"$a, $b, $c")
    case _ => println("Other")
}

Coleções Mutáveis

Coleções mutáveis (ArrayBuffer, mutable.Set, mutable.Map) modificam in-place. Mais rápidas para atualizações frequentes, mas não thread-safe. Use quando o desempenho importa e a imutabilidade não é necessária. Converta para imutável com .toMap, .toSet.

scala
import scala.collection.mutable
val buffer = mutable.ArrayBuffer(1, 2, 3)
buffer += 4  // Add element
buffer -= 2  // Remove element
buffer(0) = 10  // Update
val mset = mutable.Set(1, 2, 3)
val mmap = mutable.Map("a" -> 1)
mmap("b") = 2

Coleções Lazy

view cria uma lazy view: operações são adiadas até forçadas. Útil para operações encadeadas em coleções grandes. LazyList (Scala 2.13+) é uma sequência lazy. Sequências infinitas são possíveis com lazy evaluation. Force com .toList, .toArray.

scala
val lazyView = (1 to 1000000).view.map(_ * 2).filter(_ > 100)
// No computation yet
val first = lazyView.head  // Only computes first
val result = lazyView.take(10).toList  // Only 10 elements processed
// LazyList (formerly Stream)
val fibs: LazyList[Int] = 0 #:: 1 #:: fibs.zip(fibs.tail).map(_ + _)
20

Implicits

Parâmetros Implícitos

Parâmetros implícitos são passados automaticamente quando no escopo. O compilador pesquisa um valor implícito correspondente. Usado para configuração, type classes e contexto. Pode ser sobrescrito explicitamente. Múltiplos implicits devem ter tipos distintos.

scala
def connect(url: String)(implicit timeout: Int): Unit = {
    println(s"Connecting to $url with timeout $timeout")
}
implicit val defaultTimeout: Int = 5000
connect("http://example.com")  // Uses 5000
connect("http://example.com")(3000)  // Explicit override

Conversões Implícitas

Conversões implícitas convertem tipos automaticamente. Podem ser perigosas (conversões inesperadas). implicit class adiciona extension methods. Scala 3 usa given/using e extension para clareza. Prefira extension methods em vez de conversões brutais.

scala
import scala.language.implicitConversions
implicit def intToString(n: Int): String = n.toString
val s: String = 42  // Converts via intToString
// Extension methods (Scala 3 preferred)
implicit class RichInt(val n: Int) extends AnyVal {
    def squared: Int = n * n
}
5.squared  // 25

Type Classes

Type classes são um padrão para polimorfismo ad-hoc. Uma trait define comportamento, instâncias fornecem implementações para tipos específicos. Resolução implícita encontra a instância. Mais flexível que herança. Comum em Cats e Shapeless.

scala
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) => a
    def apply[A](a: A)(implicit s: Show[A]): String = s.show(a)
}
Show(42)  // "42"
Show("hello")  // "hello"

Context Bounds

Context bounds [A: TypeClass] são syntactic sugar para parâmetros implícitos. A instância da type class é disponível via implicitly. Sintaxe mais limpa para restrições de type class. Comum com Ordering, Numeric e type classes customizadas.

scala
def max[A: Ordering](a: A, b: A): A = {
    val ord = implicitly[Ordering[A]]
    if (ord.gt(a, b)) a else b
}
// Equivalent to:
def max2[A](a: A, b: A)(implicit ord: Ordering[A]): A =
    if (ord.gt(a, b)) a else b

Given/Using (Scala 3)

Scala 3 substitui implicits por given/using para clareza. given define uma instância, using declara um parâmetro. extension substitui implicit class. Mais explícito e legível. Ferramentas de migração convertem implicits do Scala 2.

scala
// Scala 3 syntax
given defaultTimeout: Int = 5000
def connect(url: String)(using timeout: Int): Unit =
    println(s"Connecting with $timeout")
connect("http://example.com")  // Uses given
// Extension methods
extension (n: Int)
    def squared: Int = n * n
21

Concorrência

Future

Future representa uma computação assíncrona. onComplete trata conclusão. map/flatMap encadeiam operações. ExecutionContext fornece threads. Futures são imutáveis e one-shot. Use for-comprehension para múltiplos futures.

scala
import scala.concurrent.{Future, ExecutionContext}
import ExecutionContext.Implicits.global
val f: Future[Int] = Future {
    Thread.sleep(1000)
    42
}
f.onComplete {
    case Success(v) => println(v)
    case Failure(e) => println(e)
}
// Map/flatMap for chaining
val f2 = f.map(_ * 2).flatMap(x => Future(x + 1))

for-comprehension com Futures

for-comprehension desuga para flatMap/map. Sequencial: cada passo espera o anterior. Para execução paralela, inicie futures antes do for. Muito mais legível que callbacks aninhados. Funciona com qualquer monad (Future, Option, List).

scala
def getUser(id: Int): Future[User] = ...
def getOrders(user: User): Future[List[Order]] = ...
val result: Future[List[Order]] = for {
    user <- getUser(1)
    orders <- getOrders(user)
} yield orders
// Equivalent to flatMap/map chaining

Futures Paralelos

Iniciar futures antes do for-comprehension executa-os em paralelo. Future.sequence converte List[Future[T]] para Future[List[T]]. Future.traverse mapeia e sequencia em um passo. zip combina dois futures. Todos concluem quando o mais lento termina.

scala
val f1 = Future { compute1() }
val f2 = Future { compute2() }
val combined: Future[(Int, Int)] = for {
    r1 <- f1
    r2 <- f2
} yield (r1, r2)
// Or: Future.sequence(List(f1, f2))
// Or: Future.traverse(list)(compute)

Actors (Akka)

Akka actors encapsulam estado e comunicam-se via mensagens. Sem estado mutável compartilhado. Cada actor processa uma mensagem por vez. ! (tell) envia fire-and-forget. ? (ask) retorna um Future. Supervision trata falhas. Ideal para sistemas stateful concorrentes.

scala
import akka.actor.*
class Counter extends Actor {
    var count = 0
    def receive = {
        case "inc" => count += 1
        case "get" => sender() ! count
    }
}
val system = ActorSystem("mySystem")
val counter = system.actorOf(Props[Counter], "counter")
counter ! "inc"
counter ! "get"

Cats Effect IO

Cats Effect IO é uma IO monad funcional pura. Referentially transparent: IO(println("x")) é um valor. Compõe com for-comprehension. Cancellable e resource-safe. unsafeRunSync executa na borda do seu programa. Alternativa a Future com melhor semântica.

scala
import cats.effect.IO
val program: IO[Int] = for {
    _ <- IO(println("Start"))
    result <- IO.pure(42)
    _ <- IO(println(s"Got $result"))
} yield result
// Run at end of world
program.unsafeRunSync()
// Referentially transparent, cancellable
22

Pattern Matching Profundo

Case Classes

Case classes geram automaticamente equals, hashCode, toString e apply/unapply. Perfeitas para pattern matching. Pattern matching as destrutura. Imutáveis por padrão. Use para algebraic data types. copy() cria cópias modificadas.

scala
case class Point(x: Int, y: Int)
val p = Point(1, 2)
p match {
    case Point(0, 0) => "origin"
    case Point(0, _) => "on y-axis"
    case Point(x, 0) => s"on x-axis at $x"
    case Point(x, y) => s"at ($x, $y)"
}

Sealed Traits

sealed traits restringem subtipos ao mesmo arquivo. O compilador verifica exaustividade no pattern matching. Adicionar um novo subtipo causa avisos em todos os matches. Ideal para hierarquias de tipo fechadas. Combinado com case classes para ADTs.

scala
sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Square(side: Double) extends Shape
case class Rectangle(w: Double, h: Double) extends Shape
def area(s: Shape): Double = s match {
    case Circle(r) => math.Pi * r * r
    case Square(s) => s * s
    case Rectangle(w, h) => w * h
}  // Compiler warns if not exhaustive

Guards e Extractors

Guards (if) adicionam condições a padrões. Extractors customizados (unapply) habilitam pattern matching em qualquer tipo. unapply retorna Option para indicar correspondência. Extractors desacoplam matching do tipo. Poderoso para DSLs e parsing.

scala
def classify(n: Int): String = n match {
    case x if x < 0 => "negative"
    case 0 => "zero"
    case x if x % 2 == 0 => "even"
    case _ => "odd"
}
// Custom extractor
object Even {
    def unapply(n: Int): Option[Int] =
        if (n % 2 == 0) Some(n / 2) else None
}
4 match { case Even(half) => s"half is $half" }

Partial Functions

Partial functions são definidas apenas para algumas entradas. isDefinedAt verifica. collect aplica apenas onde definido. Útil para callbacks e routing. orElse combina partial functions. lift converte para função total retornando Option.

scala
val pf: PartialFunction[Int, String] = {
    case 1 => "one"
    case 2 => "two"
}
pf.isDefinedAt(1)  // true
pf.isDefinedAt(3)  // false
// Collect = filter + map
List(1, 2, 3, 1).collect(pf)  // List("one", "two", "one")

Pattern Matching de Tipos

Pattern matching funciona em tipos, mas type erasure afeta generics. List(a, b) corresponde a uma lista de 2 elementos. List(_*) corresponde a qualquer lista. Evite matching em tipos genéricos como List[Int] (apagado). Use type tags para informação de tipo em runtime.

scala
def describe(x: Any): String = x match {
    case i: Int => s"Int: $i"
    case s: String => s"String: $s"
    case List(a, b) => s"Two-element list: $a, $b"
    case List(_*) => "List with elements"
    case Some(v) => s"Some: $v"
    case None => "None"
    case _ => "Unknown"
}
23

Programação Funcional

Funções de Ordem Superior

Funções de ordem superior recebem ou retornam funções. Currying divide funções multi-arg em cadeias de arg único. Aplicação parcial fixa alguns argumentos. Habilita composição e reuso de funções. _ cria uma função parcialmente aplicada.

scala
def applyTwice(f: Int => Int, x: Int): Int = f(f(x))
applyTwice(_ + 3, 5)  // 11
// Currying
def add(a: Int)(b: Int): Int = a + b
val add5 = add(5) _  // Partial application
add5(3)  // 8

Option e Either

Option representa valores opcionais: Some ou None. Evita null. getOrElse fornece padrão. Either representa sucesso (Right) ou falha (Left). Melhor que exceções para erros esperados. Ambos são monads: map, flatMap, for-comprehension.

scala
def find(id: Int): Option[String] =
    if (id > 0) Some("Alice") else None
find(1).getOrElse("Unknown")  // "Alice"
find(-1).getOrElse("Unknown")  // "Unknown"
// Either for error handling
def parse(s: String): Either[String, Int] =
    try Right(s.toInt)
    catch { case _: Exception => Left(s"Not a number: $s") }

Composição de Funções

compose encadeia funções da direita para esquerda (como matemática). andThen encadeia da esquerda para direita (mais legível). Ambas criam novas funções. Útil para construir pipelines. Funções são valores first-class em Scala.

scala
val addOne: Int => Int = _ + 1
val double: Int => Int = _ * 2
// Compose (right to left)
val f = addOne compose double  // double then addOne
f(3)  // 7
// AndThen (left to right)
val g = addOne andThen double  // addOne then double
g(3)  // 8

Recursão e Tail Recursion

Tail recursion é otimizada para um loop pelo compilador. A chamada recursiva deve ser a última operação. A annotation @tailrec verifica isso em tempo de compilação. O padrão accumulator carrega estado. Previne stack overflow para recursão profunda.

scala
// Not tail-recursive: stack overflow for large n
def factorial(n: Int): Int =
    if (n <= 1) 1 else n * factorial(n - 1)
// Tail-recursive: optimized to loop
import scala.annotation.tailrec
@tailrec
def factorial(n: Int, acc: Int = 1): Int =
    if (n <= 1) acc else factorial(n - 1, n * acc)

Monads

Monads têm flatMap e unit (pure/pure). Elas encadeiam operações com contexto (Option: ausência, List: não-determinismo, Future: async). for-comprehension desuga para flatMap/map. Leis de Monad garantem composição correta. Cats fornece a type class Monad.

scala
// Monad laws: left identity, right identity, associativity
// Option is a monad:
Some(5).flatMap(x => Some(x + 1))  // Some(6)
None.flatMap(x => Some(x + 1))  // None
// List is a monad:
List(1, 2).flatMap(x => List(x, x * 10))  // List(1, 10, 2, 20)
// for-comprehension is monadic sugar:
for {
    x <- Some(5)
    y <- Some(x + 1)
} yield y  // Some(6)
24

Armadilhas Comuns

Null vs Option

Scala tem null para compatibilidade com Java, mas é desencorajado. Option representa ausência explicitamente. Pattern matching força tratamento de None. Use .toOption em Try para exceções. Evite null em código Scala; reserve para interop com Java.

scala
// BAD: null
def find(id: Int): String =
    if (id > 0) "Alice" else null
// GOOD: Option
def find(id: Int): Option[String] =
    if (id > 0) Some("Alice") else None
// Scala avoids null; use Option
// NullPointer exceptions are rare in idiomatic Scala

Var vs Val

val é imutável (valor), var é mutável (variável). Prefira val para código mais seguro e previsível. Estado mutável complica raciocínio e concorrência. Use var apenas para desempenho local ou quando realmente necessário. Coleções são imutáveis por padrão.

scala
var x = 1  // Mutable
x = 2  // OK
val y = 1  // Immutable
// y = 2  // Error
// Prefer val for immutability
// Use var only when necessary
// Mutable state causes bugs in concurrent code

Igualdade

Scala == chama equals (igualdade de valor), diferentemente do Java. eq verifica igualdade de referência. ne é a negação de eq. Sempre use == para comparação de valor. Case classes têm equals correto. Para classes customizadas, sobrescreva equals e hashCode.

scala
val a = List(1, 2)
val b = List(1, 2)
a == b  // true (value equality)
a eq b  // false (reference equality)
// In Java: a == b compares references
// In Scala: == calls equals (value)
// Use eq for reference equality (rarely needed)

Ambiguidade Implícita

Múltiplos implicits do mesmo tipo causam erros de ambiguidade. Mantenha o implicit scope limpo. Use tipos específicos (newtypes) para distinguir. given/using do Scala 3 é mais claro. Evite conversões implícitas; elas causam comportamento surpreendente.

scala
implicit val s1: String = "hello"
implicit val s2: String = "world"
def greet(implicit s: String) = println(s)
greet  // Error: ambiguous implicit values
// Fix: only one implicit in scope
// Or: pass explicitly
greet(s1)

By-Name vs By-Value

Parâmetros by-name (=> T) são avaliados lazymente, a cada uso. Parâmetros by-value são avaliados uma vez antes da chamada. By-name habilita estruturas de controle customizadas (unless, while). Pode causar múltiplas avaliações; faça cache com lazy val se necessário.

scala
// By-value: evaluated once
def log(msg: String) = println(msg)
log(expensiveComputation())  // Evaluated before call
// By-name: evaluated each use
def log(msg: => String) = println(msg)
log(expensiveComputation())  // Evaluated only if called
// Useful for lazy evaluation and control flow
def unless(cond: Boolean)(body: => Unit): Unit =
    if (!cond) body
25

Akka Actors

Actor System

ActorSystem é o container para actors. actorOf cria actors com um nome. ! (tell) envia uma mensagem assincronamente. ? (ask) envia e retorna um Future. Actors são identificados por path. terminate desliga o sistema graciosamente.

scala
import akka.actor.*
val system = ActorSystem("MySystem")
val actor = system.actorOf(Props[MyActor], "myActor")
actor ! "Hello"  // Fire-and-forget
val future = actor ? "Query"  // Ask pattern
system.terminate()

Lifecycle do Actor

preStart executa na criação, postStop no término. preRestart/postRestart tratam supervision. O actor é parado antes do restart, depois iniciado fresh. Estado interno é perdido no restart. Use preStart para inicialização e postStop para limpeza.

scala
class MyActor extends Actor {
    override def preStart(): Unit = println("Starting")
    override def postStop(): Unit = println("Stopped")
    override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
        println("Restarting")
        super.preRestart(reason, message)
    }
    def receive = {
        case "ping" => sender() ! "pong"
    }
}

Supervision

Supervision define como falhas são tratadas. Resume: continue com o mesmo estado. Restart: recrie com estado fresh. Stop: termine permanentemente. Escalate: deixe o pai tratar. OneForOneStrategy afeta apenas o filho falho. AllForOneStrategy afeta todos os filhos.

scala
class Supervisor extends Actor {
    override val supervisorStrategy = OneForOneStrategy() {
        case _: ArithmeticException => Resume  // Continue
        case _: NullPointerException => Restart  // Restart actor
        case _: Exception => Stop  // Stop actor
    }
    def receive = { case _ => }
}

Routers

Routers distribuem mensagens para múltiplos actors. Pool cria e gerencia workers. Group usa actors existentes. Estratégias: RoundRobin, Random, SmallestMailbox, ConsistentHashing, ScatterGatherFirst. Routers melhoram throughput paralelizando trabalho.

scala
// Pool router: creates workers
val router = system.actorOf(
    RoundRobinPool(5).props(Props[Worker]),
    "router"
)
router ! "work"  // Distributed to one of 5 workers
// Group router: uses existing actors
val group = system.actorOf(
    RoundRobinGroup(paths).props(),
    "group"
)

Persistência

PersistentActor salva eventos em um journal (event sourcing). Comandos são validados, depois persistidos como eventos. No restart, eventos são reproduzidos via receiveRecover. Estado é reconstruído a partir de eventos. Habilita recuperação de crashes. Use EventSourcedBehavior para Akka Typed.

scala
class Counter extends PersistentActor {
    var count = 0
    override def persistenceId = "counter-1"
    override def receiveCommand = {
        case "inc" => persist(Incremented) { event =>
            count += 1
        }
    }
    override def receiveRecover = {
        case Incremented => count += 1
    }
}

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.