Skip to content

Genéricos

Genéricos permitem escrever código que funciona para múltiplos tipos sem perder as garantias do type checker. Em Soyuz, a sintaxe usa colchetes [].


fn identidade[T](x: T) -> T {
return x
}
fn primeiro[T](lista: List[T]) -> Option[T] {
return lista.first()
}
fn trocar[A, B](par: (A, B)) -> (B, A) {
val (a, b) = par
return (b, a)
}

O compilador infere os tipos na chamada — você raramente precisa ser explícito:

val x = identidade(42) // T = Int, inferido
val y = identidade("oi") // T = String, inferido
val z = identidade[Bool](true) // explícito quando necessário

record Par[A, B] {
primeiro: A
segundo: B
}
val coords = Par[Int, Int] { primeiro: 10, segundo: 20 }
val info = Par { primeiro: "nome", segundo: 42 } // inferência

class Repositorio[T] {
var items: List[T] = []
fn adicionar(self, item: T) {
self.items.add(item)
}
fn buscar(self, predicado: (T) -> Bool) -> Option[T] {
return self.items.find(predicado)
}
}
val userRepo = Repositorio[Usuario] { items: [] }

interface Comparavel[T] {
fn comparar(self, outro: T) -> Int // -1, 0, 1
}
interface Iteravel[T] {
fn proximo(self) -> Option[T]
}

Constraints limitam quais tipos podem ser usados como parâmetro. A sintaxe é T : Interface:

fn ordenar[T : Comparavel[T]](lista: List[T]) -> List[T] {
// pode usar T.comparar porque T implementa Comparavel
return lista.sort(fn(a: T, b: T) => a.comparar(b))
}
fn processar[T : Persistivel + Serializavel](item: T) -> Result[String] {
val json = item.toJson()
item.salvar()
return json // auto-wrap Ok()
}
class Cache[T : Serializavel] {
var dados: Map[String, String] = [:]
fn armazenar(self, chave: String, valor: T) {
self.dados[chave] = valor.toJson()
}
}

Soyuz usa monomorphization — para cada uso concreto de um genérico, o compilador gera código especializado. Isso significa:

  • Zero custo de abstração — sem boxing, sem despacho dinâmico.
  • Binários otimizados para cada tipo.
  • Erros de tipo em tempo de compilação, não em runtime.
// O compilador gera versões especializadas:
val a = identidade(42) // versão para Int
val b = identidade("oi") // versão para String

enum Arvore[T] {
Folha
No(valor: T, esq: Arvore[T], dir: Arvore[T])
}
fn profundidade[T](arvore: Arvore[T]) -> Int {
return match arvore {
Folha => 0
No(_, e, d) => 1 + max(profundidade(e), profundidade(d))
}
}