Saltar al contenido

Guía 10 de 53

Records, maps y actualizaciones

Records, maps, proyecciones y caminos de campos reutilizables.

En esta página

Records, maps, proyecciones y caminos de campos reutilizables.

Actualizaciones inmutables anidadas

La actualización with evalúa la base una vez y copia la raíz y los records modificados del camino. Los hijos intactos siguen compartidos. El resultado conserva su tipo nominal; se rechazan caminos conflictivos y copias de recursos lineales.

Practica en By Example · Programa fuente

// 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

Escribe los tipos de map como #{K: V}. Las llaves con campos nombrados siempre describen un record, incluso campos llamados int o String. Las llaves vacías representan un record vacío. Map<K, V> sigue disponible.

Practica en By Example · Programa fuente

// 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

Construcción contextual

Cuando se espera un tipo struct exacto, .{ campos } construye ese struct sin repetir su nombre. Siguen aplicándose los valores por defecto, la privacidad y los campos obligatorios. El compilador nunca adivina una identidad nominal por los nombres de campos.

Practica en By Example · Programa fuente

// 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
}

Caminos de campos tipados

field User::address.city crea un FieldPath<User, str> sin leer una instancia. get lee el campo; set devuelve una actualización inmutable y comparte hijos intactos. Conserva privacidad y tipos exactos; se rechazan miembros calculados y almacenamiento lineal.

Practica en By Example · Programa fuente

// 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 independientes

Sin un tipo nominal esperado, .{ campos } infiere un record anónimo con tipos exactos y acceso normal a campos. Un struct esperado selecciona la construcción nominal contextual. Un tipo esperado inválido genera un diagnóstico y no se sustituye silenciosamente por un tipo estructural.

Practica en By Example · Programa fuente

// 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()

Actualizaciones inmutables calculadas

Una actualización with acepta operadores compuestos como value += 1. Lee el campo anterior una vez y calcula el reemplazo sin modificar el original. Siguen aplicándose restricciones de visibilidad, tipos, caminos conflictivos y copias lineales.

Practica en By Example · Programa fuente

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

Comprensiones de maps

Construye maps con entradas for e if dentro de #{ }. Las claves calculadas usan [expresión]: valor. Iterables, claves y valores se evalúan una vez en orden; las claves repetidas siguen el reemplazo normal de maps. Los enlaces de bucles y filtros quedan en su entrada.

Practica en By Example · Programa fuente

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])

Proyección tipada de records

user.{ id, name } evalúa user una vez y crea un record anónimo con los campos físicos visibles seleccionados. Conserva tipos exactos. Se rechazan selecciones repetidas, campos inaccesibles o calculados y copias lineales prohibidas.

Practica en By Example · Programa fuente

// 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)

Actualizaciones anidadas agrupadas

Un grupo dentro de una actualización with comparte el prefijo de la ruta: address { city: value, visits += 1 } actualiza address.city y address.visits. Se conservan las reglas de copia, privacidad y conflictos. Los inicializadores se ejecutan en el orden escrito.

// 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

Rutas y alias en proyecciones

value.{ campo, ruta.campo as nombre } copia los campos almacenados seleccionados a un record anónimo. La base se evalúa una vez. Cada ruta debe ser visible y seguir las reglas de copia existentes. Los accesores calculados no pueden proyectarse y los nombres de salida deben 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}")

Patrones de array con sufijo

Un ...rest puede aparecer entre los elementos fijos. El prefijo cuenta desde el inicio; el sufijo, desde el final. El resto es un nuevo array y puede estar vacío. La entrada debe tener espacio para el prefijo y el sufijo.

// 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")
}

Patrones de map

Cada clave seleccionada debe existir antes de comprobar su valor. Las claves son strings literales; los valores admiten patrones anidados. Se permiten entradas adicionales y ...options copia las no seleccionadas a un nuevo map. Un valor nil elimina la entrada, por lo que cuenta como ausencia. Un patrón de map no coincide con un 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")
}

Sigue aprendiendo

Documentación · Practica en By Example

DOCS / OPINIÓN

¿Esta página dejó alguna pregunta?

Cuéntanos dónde te perdió la explicación. La documentación también forma parte de la experiencia del lenguaje.

Índice global

Encuentra tu camino por Zolo

Prueba una idea

Empieza aquí

9 resultados

9 resultados

enespt-br