Pular para o conteúdo

Guia 10 de 53

Records, maps e atualizações

Records, maps, projeções e caminhos de campos reutilizáveis.

Nesta página

Records, maps, projeções e caminhos de campos reutilizáveis.

Atualizações imutáveis aninhadas

A atualização with avalia a base uma vez e copia a raiz e os records alterados no caminho. Filhos intactos continuam compartilhados. O resultado preserva o tipo nominal; caminhos conflitantes e cópias de recursos lineares são rejeitados.

Pratique na By Example · Programa fonte

// Item 4: copy only the record paths being changed, keeping the same type.
struct Address { city: str, postal_code: str }
struct User { name: str, address: Address, roles: [str] }

fn main() {
    let original = User {
        name: "João",
        address: Address { city: "São Paulo", postal_code: "01000-000" },
        roles: ["reader"],
    }
    let changed = original with { name: "Ana", address.city: "Recife" }
    print(original.name)
    // expected: João
    print(changed.name)
    // expected: Ana
    print(original.address.city)
    // expected: São Paulo
    print(changed.address.city)
    // expected: Recife
    print(changed.address.postal_code)
    // expected: 01000-000

    // The untouched roles reference is shared, while address was copied.
    changed.roles.push("reviewer")
    print(original.roles.len())
    // expected: 2
    let next = changed with { address.postal_code: "50000-000" } with { name: "Beatriz" }
    print(next.name)
    // expected: Beatriz
}

Tipos de map explícitos

Escreva tipos de map como #{K: V}. Chaves com campos nomeados sempre descrevem um record, inclusive campos chamados int ou String. Chaves vazias representam um record vazio. Map<K, V> continua disponível.

Pratique na By Example · Programa fonte

// Map types and map values use the same explicit delimiter.
let counts: #{str: int} = #{ ok: 2, failed: 1 }
print(counts["ok"])
// expected: 2

// Field labels never turn a record annotation into a map.
let row: { str: int, User: str, type: bool } = #{ str: 7, User: "Ada", type: true }
print(row.str)
// expected: 7
print(row.User)
// expected: Ada
print(row.type)
// expected: true

type Lookup = #{str: [#{int: str}]}
let nested: Lookup = #{ first: [#{ [1]: "one" }] }
print(nested["first"][0][1])
// expected: one

Construção contextual

Quando um tipo struct exato é esperado, .{ campos } constrói esse struct sem repetir seu nome. Defaults, privacidade e campos obrigatórios continuam valendo. O compilador nunca adivinha uma identidade nominal pelos nomes dos campos.

Pratique na By Example · Programa fonte

// A known nominal context supplies the construction name.
struct Point { x: int = 0, y: int = 0 }
struct Segment { start: Point, end: Point }
fn origin() -> Point { .{} }
fn magnitude(point: Point) -> int { point.x + point.y }

fn main() {
    let x = 3
    let point: Point = .{ x, y: 4 }
    print(magnitude(.{ x: 5 })) // expected: 5
    print(point.y) // expected: 4
    let line: Segment = .{ start: .{ x: 1 }, end: .{ x: 7, y: 2 } }
    print(line.end.x) // expected: 7
    let shifted: Point = .{ x: point.x + 10, ..point }
    print(shifted.x) // expected: 13
}

Caminhos de campos tipados

field User::address.city cria um FieldPath<User, str> sem ler uma instância. get lê o campo; set devolve uma atualização imutável e compartilha filhos intactos. Caminhos preservam privacidade e o tipo exato; membros computados e armazenamento linear são rejeitados.

Pratique na By Example · Programa fonte

// Item 20: a typed field path can be reused and its getter is a callback.
struct Address { city: str, postal_code: str }
struct User { name: str, address: Address, roles: [str] }

fn city_path() -> FieldPath<User, str> = field User::address.city

fn main() {
    let city = city_path()
    let original = User {
        name: "Ana",
        address: Address { city: "São Paulo", postal_code: "01000-000" },
        roles: ["reader"],
    }
    let moved = city.set(original, "Recife")
    print(city.get(original))
    // expected: São Paulo
    print(city.get(moved))
    // expected: Recife
    print(moved.address.postal_code)
    // expected: 01000-000

    let cities = [original, moved].map(city.get)
    print(cities[0])
    // expected: São Paulo
    print(cities[1])
    // expected: Recife

    // The untouched collection is still shared by the immutable update.
    moved.roles.push("reviewer")
    print(original.roles.len())
    // expected: 2
}

Records anônimos independentes

Sem um tipo nominal esperado, .{ campos } infere um record anônimo com tipos exatos e acesso normal aos campos. Um struct esperado ainda seleciona construção nominal contextual. Um tipo esperado inválido gera diagnóstico, sem virar silenciosamente um tipo estrutural.

Pratique na By Example · Programa fonte

// Anonymous records keep the exact type of each field.
// A nominal annotation still selects that type's ordinary constructor.
fn main() {
    let count = 3
    let summary = .{ count, total: 12 }
    let next = summary with { total: 15 }
    print(summary.count, summary.total)
    print(next.total)
    // expected: 3 12
    // expected: 15
}
main()

Atualizações imutáveis calculadas

Uma atualização with aceita operadores compostos como value += 1. Ela lê o campo antigo uma vez e calcula a substituição sem alterar o original. Restrições de visibilidade, tipos, caminhos conflitantes e cópias lineares continuam valendo.

Pratique na By Example · Programa fonte

struct Counter { value: int, limit: int? }

let current = Counter { value: 2, limit: nil }
let next = current with { value += 1, limit ??= 10 }

print(current.value) // 2
print(next.value)    // 3
print(next.limit)    // 10

Compreensões de maps

Construa maps com entradas for e if dentro de #{ }. Chaves calculadas usam [expressão]: valor. Iteráveis, chaves e valores executam uma vez na ordem escrita; chaves repetidas seguem a substituição normal de maps. Bindings do loop e filtro ficam na própria entrada.

Pratique na By Example · Programa fonte

struct User { id: int, name: str, enabled: bool }

let users = [
    User { id: 1, name: "Ada", enabled: true },
    User { id: 2, name: "Lin", enabled: false },
]
let names = #{for user in users => [user.id]: user.name}
let active_names = #{for user in users => if user.enabled => [user.id]: user.name}

print(names[2])
print(active_names[1])

Projeção tipada de records

user.{ id, name } avalia user uma vez e cria um record anônimo com os campos físicos visíveis selecionados. Tipos exatos são preservados. Seleções repetidas, campos inacessíveis ou computados e cópias lineares proibidas são rejeitados.

Pratique na By Example · Programa fonte

// Typed projection selects visible physical fields into an anonymous record.
struct User { id: int, name: str, internal: str }
var acquired = 0
fn make() -> User { acquired += 1; User { id: acquired, name: "Ada", internal: "hidden" } }
let public_user = make().{ name, id }
print(acquired)
print(public_user.id)
print(public_user.name)
let updated = public_user with { id += 4 }
print(public_user.id)
print(updated.id)
let empty = make().{}
print(acquired)
struct Box<T> { value: T, id: int }
macro selected(value) { $value.{ value } }
let integer_box: Box<int> = Box { value: 7, id: 1 }
let text_box: Box<str> = Box { value: "text", id: 2 }
let integer = selected!(integer_box)
let text = selected!(text_box)
print(integer.value)
print(text.value)
fn first() -> int { let unused = ({ return 8; User { id: 0, name: "", internal: "" } }).{ id }; 0 }
print(first())
var total = 0
:outer for n in 0..3 {
    let value = ({ if n == 1 { continue :outer }; User { id: n, name: "", internal: "" } }).{ id }
    total += value.id
}
print(total)

Atualizações aninhadas agrupadas

Um grupo dentro de uma atualização with compartilha o prefixo do caminho: address { city: value, visits += 1 } atualiza address.city e address.visits. As regras de cópia, privacidade e conflitos continuam valendo. Os inicializadores executam na ordem escrita.

// Update several fields under the same path, leaving the original value intact.
struct Address { city: str, visits: int }
struct Person { name: str, address: Address }
let original = Person { name: "Ana", address: Address { city: "Recife", visits: 2 } }
let moved = original with {
    address { city: "Natal", visits += 1 },
}
print(original.address.city) // expect: Recife
print(moved.address.city) // expect: Natal
print(moved.address.visits) // expect: 3

Caminhos e aliases em projeções

value.{ campo, caminho.campo as nome } copia os campos armazenados selecionados para um record anônimo. A base é avaliada uma vez. Cada caminho deve ser visível e seguir as regras de cópia existentes. Acessores calculados não podem ser projetados, e os nomes de saída devem ser únicos.

// Prepare a public response without copying internal fields.
struct Address { city: str, delivery_code: int }
struct User { id: int, name: str, address: Address }
let user = User {
    id: 7,
    name: "Ada",
    address: Address { city: "Recife", delivery_code: 42 }
}
let response = user.{ id, name as display_name, address.city as city }
print("{response.id}: {response.display_name}, {response.city}")

Padrões de array com sufixo

Um ...rest pode aparecer entre os elementos fixos. O prefixo conta a partir do início; o sufixo, a partir do fim. O restante é um novo array e pode ser vazio. A entrada precisa ter espaço para o prefixo e o sufixo.

// Read a framed packet without copying the header into its payload.
let packet = [170, 4, 8, 12, 255]
match packet {
    [170, ...payload, 255] => {
        print("payload: {payload}")
        print("bytes: {payload.len()}")
    },
    _ => print("invalid frame")
}

Padrões de map

Cada chave selecionada precisa existir antes de seu valor ser verificado. As chaves são strings literais; os valores aceitam padrões aninhados. Entradas extras são permitidas, e ...options copia as não selecionadas para um novo map. Um valor nil remove a entrada, portanto conta como ausência. Um padrão de map não corresponde a um record anônimo.

// Decode a command and retain options that belong to the handler.
let command = #{"action": "rename", "name": "Ada", "mode": "safe"}
match command {
    #{"action": "rename", "name": name, ...options} => {
        print("rename to {name}")
        print("mode: {options["mode"]}")
    },
    _ => print("unsupported command")
}

Continue aprendendo

Documentação · Pratique na By Example

DOCS / OPINIÃO

Esta página deixou alguma pergunta?

Conte onde a explicação perdeu você. A documentação também faz parte da experiência da linguagem.

Índice global

Encontre seu caminho pela Zolo

Experimente uma ideia

Comece por aqui

9 resultados

9 resultados

enespt-br