# 03 — Declaraciones e instancias > [← 02 — Tipos](02-tipos.md) · [README](README.md) · [Siguiente: 04 — Acciones y errores →](04-acciones-y-errores.md) ## Formas de declaración Niell tiene **tres formas distintas** de introducir un nombre nuevo. La forma elegida — no una keyword aparte — determina la disciplina: si el nombre es rebindable, si el tipo se infiere o se restringe, y si lo que se construye es una instancia ligada al nombre. | Forma | Significado | Reasignable | | ---------------------------------- | ------------------------------------------------------------ | ----------- | | `nombre = expr` | Variable. Tipo inferido del RHS. | sí | | `nombre: Tipo = expr` | Variable con tipo explícito (restricción). | sí | | `nombre: Tipo` + bloque `... end` | *Instancia ligada* al nombre (el nombre no puede reapuntar a otra instancia; sólo los campos mutan). | no | Caso rechazado: | Forma | Por qué falla | | ---------------------------------- | ------------------------------------------------------------ | | `nombre: Tipo` sin `=` ni bloque | **Error de compilación.** Toda variable necesita valor inicial. | Las primeras dos formas son intercambiables salvo por el tipo como restricción explícita. La tercera es estructuralmente distinta: la presencia del bloque `... end` después de `nombre: Tipo` significa "construir esta instancia y ligarla permanentemente al nombre". La variable apunta para siempre a esa instancia dentro de su scope; lo único que puede cambiar son sus campos. ``` n = 5 # variable, inferida como number, rebindable n: number = 5 # variable, restringida a number, rebindable result = play_round # variable rebindable (play_round retorna un Outcome — ver archivo 04) result: Outcome = play_round # idem, con restricción explícita camera: Camera position = point with 0, 0 ... end # instancia ligada — camera no es reasignable n: number # error — falta valor inicial camera: Camera # error — falta bloque ... end o = expr ``` **La disciplina sigue a la forma sintáctica, no al tipo del valor.** Una variable creada con `=` puede contener una instancia de un type — `result = play_round` produce un `result: Outcome` rebindable, donde `result = otro_outcome` es legal (mismo tipo). La ligadura permanente solo aparece cuando usas la forma con bloque, donde la intención de "construir y ligar" es explícita. Esta es la base de dos reglas que aparecen más adelante: las instancias locales no son reasignables (solo sus campos mutan), y los campos opcionales que no se asignan en el bloque quedan en `nothing` (ver "Creación de instancias" más abajo). ## Creación de instancias `nombre: Tipo` declara e instancia un tipo. No existe separación entre declaración e inicialización — toda variable tiene un valor desde su creación. El bloque que sigue asigna los campos. El compilador rechaza la creación si algún campo obligatorio queda sin asignar. ``` background: Background style = gaussian_splatter infinite = true end ``` Cuatro categorías de campos: - **Obligatorio** — `has x: T`, sin default ni `or nothing`. Hay que asignarlo en la creación. - **Con default** — `has x: T = expr`. Si no se asigna, toma el valor de `expr`. El campo nunca es `nothing`. - **Opcional** — `has x: T or nothing`. Admite valor de tipo `T` o `nothing`. Default implícito `nothing`; default explícito posible (`has x: T or nothing = expr`, donde `expr` puede ser `T` o `nothing`). - **Calculado** — `derives from expr`. Nunca se asigna manualmente; el compilador lo rechaza. ``` Receipt has items_total: number has note: text or nothing has currency: text = "USD" tax derives from items_total * 0.21 end new_receipt: Receipt items_total = 100 # obligatorio # note queda en nothing (no se asigna; default implícito) # currency toma "USD" por default # tax lo calcula el compilador end other_receipt: Receipt items_total = 50 currency = "EUR" # override del default end ``` **Cuándo se evalúa el default.** La expresión del default se evalúa **al momento de cada instanciación**, no al momento de declarar el type. Esto importa cuando el default no es una constante: ``` Session has id: text has created_at: number = now # cada Session creada toma el momento actual end ``` **Qué puede referenciar.** El default es lógica de inicialización — se evalúa una sola vez por instanciación, así que admite efectos. La regla es más flexible que la de `derives from` (que es una relación continua y exige la whitelist sintáctica estricta del archivo 02): - Todo lo que admite `derives from`: literales, campos previamente declarados del mismo type, constantes del módulo, operadores, `if`/`match` como expresiones, funciones de `Math`, lecturas sobre colecciones. - **Adicionalmente**: llamadas a acciones del mismo módulo o de stdlib, incluso si tienen efectos. Es lo que permite ejemplos como `created_at: number = now` (la acción `now` lee el reloj del sistema), o `id: text = generate_id`. Lo que sigue fuera: acceso a otras instancias por nombre, y mutación directa de estado en el RHS. Si el default necesita lógica suficientemente compleja como para no caber legiblemente en una expresión, conviene mover la creación entera a una acción constructora del módulo y dejar el bloque de instanciación libre. **El default debe ser total.** Una acción usada como default no puede declarar `or fails with E` en su firma — el compilador rechaza el type. La razón es composicional: si el default pudiera fallar, todo bloque de instanciación heredaría un fallo implícito que el call-site tendría que manejar, contaminando con `on failure` cualquier `nombre: Tipo … end`. Cuando la inicialización legítimamente puede fallar (parsear un input, abrir un recurso), el patrón canónico es mover la lógica a una acción constructora del módulo (`action create_session with raw -> Session or fails with SessionError`) que el llamador invoca explícitamente y maneja el error donde corresponde. La restricción no aplica a `or nothing`: una acción que retorna `T or nothing` sí puede ser default si el campo declara también `or nothing`. **Orden de evaluación.** Los defaults se evalúan en **orden textual** de declaración. Un default puede referenciar campos del mismo type ya declarados arriba en el bloque, pero no campos posteriores. Si el programador escribe los defaults en orden de dependencia, ambos requisitos coinciden; si no, el compilador rechaza por "campo X usado antes de declararse". Coherente con la lectura lineal del resto del lenguaje, y especialmente importante cuando los defaults tienen efectos: el código se lee en el mismo orden en que se ejecuta. ``` Session has token: text = generate_token # OK — no depende de nada has token_hash: text = hash with token # OK — token ya declarado arriba end Bad has token_hash: text = hash with token # error — token aún no declarado has token: text = generate_token end ``` A diferencia de `derives from`, que se resuelve **topológicamente** (es relación pura y no admite efectos), los defaults priorizan predictibilidad lineal por sobre permisividad. El campo derivado se define con los campos propios del type (`items_total`), no con estado externo. Cuando un campo debería reaccionar a otra instancia (por ejemplo, un `Sprite` cuyo ángulo depende de la `Camera`), no se usa `derives from` — se usa `on changes` (ver "Relaciones reactivas entre instancias distintas" en el archivo 02). Aplica en cualquier contexto: nivel módulo o dentro de una acción. Dentro del bloque de instanciación, el lado izquierdo de cada asignación siempre es un campo del type. El lado derecho es una expresión del scope circundante — puede ser un parámetro, una variable local o cualquier valor accesible en ese punto. No hay ambigüedad aunque compartan nombre: ``` action open_account needs owner: text returns Account new_account: Account owner = owner # izquierda: campo del type — derecha: parámetro de la acción balance = 0 end return new_account end ``` Para chequear si un campo opcional tiene valor, se usa `is nothing`: ``` if sprite.label is nothing then show with "sin etiqueta" if sprite.label is nothing assign_default_label with sprite else show with sprite.label end ``` La forma negativa es `not X is nothing` — no existe un operador `is not nothing`. El paréntesis ayuda cuando la expresión se encadena con otros operandos lógicos: ``` if not (sprite.label is nothing) and sprite.visible then show with sprite.label ``` **Type narrowing:** cuando el compilador ve un guard `if X is nothing then ...` que interrumpe el flujo (con `return`, `fails with` o similar), entiende que el resto del bloque trata a `X` como el tipo concreto, no como `T or nothing`. No es necesario ningún cast explícito: ``` action describe_label needs sprite: Sprite returns text or fails with text if sprite.label is nothing then fails with "sin etiqueta" # a partir de aquí, sprite.label es text — el compilador lo sabe return "etiqueta: {sprite.label}" end ``` Si el guard no interrumpe el flujo, el narrowing aplica solo dentro de la rama: ``` if sprite.label is nothing assign_default_label with sprite else show with sprite.label # aquí sprite.label es text end ``` **Type narrowing en `match`.** El mismo análisis niell-sensitive aplica a `match`. Para una expresión `match x` donde `x: T or nothing`: - La rama `nothing -> ...` ve a `x` como `nothing` (poco útil pero consistente). - Las **otras ramas** ven a `x: T` — el compilador sabe que llegar a esa rama implica que `x` no es `nothing`. ``` match maybe_user nothing -> show with "no encontrado" else -> show with "Hola {maybe_user.name}" # maybe_user: User aquí end ``` **Por qué `nothing` aparece como pattern y no como `is nothing`.** En `if`/`when`, la condición es una expresión booleana — por eso se escribe `if maybe_user is nothing` (un test). En `match`, las ramas son **patrones** (valores o variantes), no expresiones booleanas. Por eso `nothing` aparece directamente como el pattern que matchea contra el valor. La asimetría sintáctica refleja la diferencia de mecanismo: condicional vs. pattern matching. La regla generaliza a tagged unions: cubrir una variante específica en una rama narrowea las demás ramas al complemento (excluyen esa variante). Para `T or nothing`, `nothing` funciona como una variante implícita: cubrirla habilita el narrowing a `T` en las demás. La regla no requiere que la rama interrumpa el flujo — `match` ya tiene exhaustividad como disciplina propia. ## Types recursivos y mutuamente recursivos Un type puede referenciarse a sí mismo, o referenciar a otro que a su vez lo referencia. La regla de construcción ("todo campo obligatorio debe asignarse") fuerza una restricción: **todo ciclo en el grafo de dependencias de campos obligatorios debe romperse por al menos un campo opcional o colección**. Una arista del grafo va de `T` a `U` cuando `T` tiene un campo `has x: U` *sin* `or nothing` y *sin* envolver en colección. Si un ciclo se compone exclusivamente de aristas de este tipo, el type no es construible — no existe forma de asignar todos los obligatorios — y el compilador lo rechaza al declararlo. Romper el ciclo significa que al menos una arista sea opcional (`or nothing`) o pase por una colección (`list of`, `set of`, `map of`), porque en ambos casos el campo admite el valor base (`nothing` o colección vacía) que termina la cadena. ``` # Válidos — el ciclo está roto por or nothing o por una colección Node has parent: Node or nothing, has children: list of Node Tree has root: Tree or nothing A has b: B or nothing, B has a: A or nothing A has b: B, B has a: list of A # roto por list of A ``` ``` # Inválidos — el compilador los rechaza al declararlos Node has next: Node # ciclo de un salto, todo obligatorio A has b: B, B has a: A # ciclo de dos saltos ``` La regla aplica al grafo entero, no solo a ciclos directos. Tres types `A → B → C → A` con todos los campos obligatorios se rechazan igual que un ciclo de dos. Para construirlos, basta que una arista del ciclo se vuelva opcional o pase por colección — no hace falta romper todas.