M3.C1 — Estructura de módulos + import / from import¶
Pre-requisitos: M2 completo. Sabés todo el lenguaje base — primitivos, vars, fns, types, match, Result.
Objetivo: dominar el sistema de módulos del lenguaje —
partir tu código en varios archivos .fitz, importar con
import foo o from foo import X, usar paths relativos,
nested modules, aliases, y entender cómo el loader resuelve
los paths.
Por qué importa: hasta acá todo vivió en un solo
src/main.fitz. Eso escala hasta ~200 LoC. Para programas
reales necesitás partir el código — un archivo por
"responsabilidad" (un type, una capa, un dominio). Sin
imports, no hay proyectos serios.
Mapa del cap¶
flowchart LR
A[Single file:<br/>src/main.fitz] --> B[Múltiples archivos]
B --> C[import util<br/>namespace]
B --> D[from util import X<br/>direct]
B --> E[import util as u<br/>alias namespace]
B --> F[from util import X as Y<br/>alias direct]
B --> G[Nested:<br/>from utils.math import cubo]
Paso 1 — ¿Qué es un módulo?¶
Un módulo en Fitz es un archivo .fitz. Todo lo que
declares top-level adentro (fns, types, lets) son los
exports del módulo.
mi-proyecto/
└── src/
├── main.fitz ← módulo "main" (el entry [bin].main)
├── util.fitz ← módulo "util"
└── users.fitz ← módulo "users"
Desde main.fitz podés importar cosas de los otros:
// src/main.fitz
from util import doblar
from users import create_user, User
let u = create_user(1, "Ada")
print(u)
print(doblar(21))
📚 Detalle exhaustivo: cap 16 — Módulos de la guía.
Paso 2 — Dos formas de importar¶
Forma 1: import <módulo> (namespace)¶
Importa el módulo entero como un namespace. Accedés a sus
exports con la sintaxis <módulo>.<X>:
Donde src/util.fitz tiene:
Forma 2: from <módulo> import <X>, <Y> (direct)¶
Importa símbolos específicos del módulo, accesibles directamente por su nombre:
¿Cuál usar?¶
| Caso | Preferí | Por qué |
|---|---|---|
| Usás 1-2 cosas del módulo | from X import a, b |
Más conciso, menos verbose |
| Usás 5+ cosas | import X |
Evita from X import a, b, c, d, e, f, g, ... |
| Hay colisión de nombres con otro módulo | import X as alias |
Evita conflictos |
| Estás escribiendo una lib pública | from X import ... |
Lectores ven qué se importa |
| Querés "qualificar todo" estilo Python | import X |
X.foo() es explícito |
No es regla rígida. Mezclá en el mismo archivo según conveniencia.
Paso 3 — Demo end-to-end¶
Crealo en tu mi-saludos/ (o un proyecto nuevo):
src/util.fitz¶
src/main.fitz — versión con from¶
from util import doblar, cuadrado, PI
print(doblar(21)) // 42
print(cuadrado(7)) // 49
print(PI) // 3.14159
src/main.fitz — versión con import namespace¶
Mismo output. Estilísticamente:
- Forma from: más corto en el call site.
- Forma import: más explícito sobre el origen de cada cosa.
Paso 4 — Paths relativos¶
El path del import es relativo al archivo donde está, NO al cwd:
mi-proyecto/
└── src/
├── main.fitz ← from util import X → resuelve src/util.fitz
├── util.fitz
└── subcarpeta/
├── helpers.fitz
└── inner.fitz ← from helpers import X → resuelve src/subcarpeta/helpers.fitz
Subcarpetas con .¶
Para módulos en subdirectorios, usá . como separador:
Esto resuelve src/utils/math.fitz.
Subir un nivel (no soportado directo)¶
Fitz no tiene notación ../ adentro de imports (a
diferencia de paths de shell). Para "ir al padre", el módulo
necesita estar en otro paquete con su propia dep declarada
(M3.C3).
Tabla de resolución¶
from X import Y en archivo.fitz |
Resuelve a |
|---|---|
from util import Y |
<dir-de-archivo>/util.fitz |
from utils.math import Y |
<dir-de-archivo>/utils/math.fitz |
from utils.math.advanced import Y |
<dir-de-archivo>/utils/math/advanced.fitz |
from foo import Y (foo es dep declarada en fitz.toml) |
El [lib].entry de la dep |
Paso 5 — Aliases con as¶
Renombrar el símbolo importado, útil para evitar colisiones:
Alias en from import¶
Alias en import namespace¶
Alias mixto¶
from util import doblar as d, cuadrado as q, PI
print(d(5)) // 10
print(q(3)) // 9
print(PI) // 3.14159 ← sin alias
Tabla de cuándo conviene¶
| Caso | Sintaxis |
|---|---|
| Dos módulos con el mismo nombre | import a; import b as b2 |
| Nombre del símbolo es muy largo | from x import really_long_name as short |
| Acortar el namespace | import super.deep.path as p |
Paso 6 — Compartir types entre módulos¶
Importante: los type también se exportan e importan como
cualquier otro símbolo:
src/models.fitz¶
src/users.fitz — usa los types¶
from models import User
fn create_user(id: Int, name: Str) -> User {
return User { id: id, name: name }
}
src/main.fitz — combina ambos¶
from models import User, Order
from users import create_user
let u = create_user(1, "Ada")
print(u)
let o = Order { id: 1, user_id: 1, total: 99.99 }
print(o)
typees identificado por nombre nominal.Userdefinido enmodels.fitzyUserimportado enusers.fitzson el mismo tipo —u: Usercumple ambos contratos.
Paso 7 — Tipos exportables¶
Cualquier declaración top-level del módulo es export implícito:
| Declaración | Exportable? | Sintaxis del importer |
|---|---|---|
fn name() { ... } |
✅ | from mod import name |
type Name { ... } |
✅ | from mod import Name |
let CONST = ... |
✅ | from mod import CONST |
let local_var = ... (no convención) |
✅ pero discouraged | Idem |
Statement top-level (print(), if, etc.) |
⚠️ Se ejecuta al cargar | (no se importa, pero el efecto sucede) |
Side effects al cargar: si un módulo tiene un
print("X")top-level, ese print se ejecuta la primera vez que algo lo importa. Cargas siguientes (otro archivo importa el mismo módulo) usan cache — el print NO se repite.
Paso 8 — Ciclos de imports¶
Fitz detecta ciclos y aborta:
Para evitar ciclos:
- Extraer el código compartido a un tercer módulo (shared.fitz).
- Refactorizar para que uno dependa del otro pero no viceversa.
Paso 9 — Cache de módulos¶
Los módulos se cargan UNA vez por sesión:
main.fitz importa util ← carga util por primera vez
main.fitz importa otra cosa
otra-cosa importa util ← devuelve el cached util (no re-ejecuta)
Esto significa que: - Side effects top-level corren UNA vez. - State top-level (vars globales) se comparte entre todos los importers. - El orden de evaluación del primer load es determinístico por path canónico.
Paso 10 — Multi-archivo: organización canónica¶
Estructuras típicas según el tamaño:
Mini (1-5 archivos)¶
Mediano (5-20 archivos) — por capa¶
src/
├── main.fitz
├── models.fitz ← types compartidos
├── repo.fitz ← persistencia
├── services.fitz ← lógica
└── handlers.fitz ← entry points (HTTP)
Grande (20+ archivos) — por dominio¶
src/
├── main.fitz
├── shared/
│ ├── models.fitz
│ └── errors.fitz
├── users/
│ ├── models.fitz
│ ├── repo.fitz
│ ├── service.fitz
│ └── handlers.fitz
└── orders/
├── models.fitz
├── repo.fitz
└── handlers.fitz
Imports cross-domain: from users.models import User.
No hay regla rígida sobre cómo organizar. Convención: empezá flat (mini), y partí cuando algún archivo pase 200 LoC.
Paso 11 — Aplicarlo a mi-saludos¶
Refactor a multi-archivo. Crealos en src/:
src/models.fitz¶
src/categorias.fitz¶
from models import Pueblo
fn altitud_cat(p: Pueblo) -> Str => match p.altitud_m {
0..=200 => "llanura",
201..=1000 => "media",
_ => "altura"
}
fn habitantes_cat(p: Pueblo) -> Str => match p.habitantes {
0..=999 => "aldea",
1_000..=9_999 => "pueblo",
10_000..=99_999 => "ciudad",
_ => "metrópolis"
}
src/main.fitz¶
from models import Pueblo
from categorias import altitud_cat, habitantes_cat
let pueblos = [
Pueblo { nombre: "El Chaltén", altitud_m: 405, habitantes: 2000 },
Pueblo { nombre: "Bariloche", altitud_m: 893, habitantes: 112000 },
Pueblo { nombre: "Ushuaia", altitud_m: 23, habitantes: 82000 },
]
print("Catálogo:")
for p in pueblos {
let alt = altitud_cat(p)
let hab = habitantes_cat(p)
print(" - {p.nombre}: {alt}, {hab}")
}
3 archivos, cada uno con su responsabilidad. Esto es el
patrón canónico de Fitz — un archivo por dominio, types
compartidos en un models.fitz común.
Paso 12 — Limitaciones MVP¶
| Feature | Estado | Workaround |
|---|---|---|
| Imports cross-module | ✅ | — |
Aliases (as X) |
✅ | — |
Subcarpetas con . |
✅ | — |
| Cache + detección de ciclos | ✅ | — |
Re-exports (pub use estilo Rust) |
❌ | Re-declarar en el módulo intermedio |
Wildcard (from X import *) |
❌ | Importar uno por uno |
| Conditional imports | ❌ | No hay |
| Importar dep externa | ✅ con [dependencies] |
M3.C3 |
Path absolutos (from /usr/...) |
❌ | No tiene sentido — usá deps |
| Tests cross-module | ✅ | tests/*.fitz importa de src/ |
Validación¶
- Creás
src/util.fitzconfn doblar(n: Int) -> Inty desdesrc/main.fitzlo importás confrom util import doblar. -
import utilnamespace +util.doblar(...)también funciona. -
import util as u+u.doblar(...)funciona. -
from util import doblar as d+d(...)funciona. - Path nested
from utils.math import Xresuelve asrc/utils/math.fitz. - Ciclo
a ↔ bda error claro. - Compartir un
typeentre módulos funciona — la instancia cumple el tipo en ambos lados.
Troubleshooting¶
error: no se pudo cargar el módulo 'X'¶
- Verificá el path. Es relativo al archivo importer, no al cwd.
- Para subcarpetas, usá
.no/:from utils.math import X, nofrom utils/math import X.
error: el símbolo 'X' no existe en el módulo 'Y'¶
- ¿Está declarado top-level en el módulo? Solo top-level se exporta.
- Typo en el nombre. El LSP autocomplete tras
from X importno está implementado todavía (deuda).
error: ciclo detectado en imports¶
Refactor: extraé el código compartido a un tercer módulo.
Mi import "no anda" pero el path parece correcto¶
- ¿El archivo termina en
.fitz? - ¿El nombre del módulo es válido como identifier? (
hyphen-namerompe; usáunderscore_name). - ¿Hay typo entre
from utils importy la carpetautil/?
El módulo se ejecuta varias veces¶
No debería — el loader cachea por path canónico. Si te parece que sí, abrí issue con repro.
Quiero importar de un proyecto externo¶
Eso es dependency. M3.C3 cubre [dependencies] con path
o git.
Lo que viene en C2¶
Vimos cómo partir UN proyecto en módulos. En el próximo cap
aprendemos a exponer tu proyecto como lib para que otros
proyectos puedan importarlo — [lib].entry en fitz.toml,
estructura recomendada, qué exponer y qué mantener privado.