Estados paralelos: dimensiones ortogonales a la vez
La segunda adición de Harel es la ortogonalidad: un estado paralelo contiene varias regiones que están todas activas simultáneamente, cada una con su propia submáquina independiente. Es la cura directa de la explosión multiplicativa, porque `k` dimensiones binarias pasan de exigir dos elevado a `k` estados combinados a solo dos por `k` nodos repartidos en regiones. Esta lección explica cómo se declara una región paralela, por qué la configuración pasa a tener varias hojas activas a la vez, y cómo la difusión de eventos permite que un solo evento dispare transiciones en varias regiones sin cablearlas entre sí.
La jerarquía resolvió la explosión aditiva, pero deja intacta la otra explosión, la multiplicativa: la que surge cuando un sistema tiene dimensiones genuinamente independientes. Un texto puede estar en negrita o no, en cursiva o no, subrayado o no, y esas tres decisiones no se estorban entre sí. Modelarlas con estados exclusivos obliga a inventar un estado por cada combinación, y las combinaciones crecen como potencias de dos. La ortogonalidad de Harel ataca esto de raíz: en lugar de un estado que sea la mezcla de todas las dimensiones, se declaran varias regiones que viven a la vez, cada una gobernando una sola dimensión. El sistema deja de estar en un estado y pasa a estar en varios simultáneamente, uno por región, y el producto cartesiano nunca llega a materializarse.
- Declarar un estado paralelo con regiones ortogonales independientes.
- Leer la configuración como un conjunto de hojas activas, una por región.
- Cuantificar cómo la ortogonalidad convierte crecimiento exponencial en lineal.
- Usar la difusión para que un evento dispare transiciones en varias regiones.
Varias regiones activas a la vez
Un estado paralelo se marca con type: 'parallel' y no tiene initial, porque no elige un hijo: los activa todos. Cada hijo es una región, y cada región es a su vez un estado compuesto con su propia submáquina. Modelemos el formato de un fragmento de texto con tres dimensiones independientes.
import { createMachine } from 'xstate'
const formato = createMachine({
id: 'formato',
type: 'parallel', // todas las regiones activas a la vez, sin initial
states: {
negrita: {
initial: 'off',
states: {
off: { on: { TOGGLE_NEGRITA: 'on' } },
on: { on: { TOGGLE_NEGRITA: 'off' } },
},
},
cursiva: {
initial: 'off',
states: {
off: { on: { TOGGLE_CURSIVA: 'on' } },
on: { on: { TOGGLE_CURSIVA: 'off' } },
},
},
subrayado: {
initial: 'off',
states: {
off: { on: { TOGGLE_SUBRAYADO: 'on' } },
on: { on: { TOGGLE_SUBRAYADO: 'off' } },
},
},
},
})
La configuración ya no es un camino único a una hoja, sino un abanico: una hoja activa por región. En XState se refleja como un objeto con una clave por región, y todas conviven.
const actor = createActor(formato).start()
actor.send({ type: 'TOGGLE_NEGRITA' })
actor.send({ type: 'TOGGLE_SUBRAYADO' })
actor.getSnapshot().value // { negrita: 'on', cursiva: 'off', subrayado: 'on' }
stateDiagram-v2
state Formato {
state Negrita {
[*] --> NegOff
NegOff --> NegOn: toggle
NegOn --> NegOff: toggle
}
--
state Cursiva {
[*] --> CurOff
CurOff --> CurOn: toggle
CurOn --> CurOff: toggle
}
--
state Subrayado {
[*] --> SubOff
SubOff --> SubOn: toggle
SubOn --> SubOff: toggle
}
}Las dos rayas horizontales que separan las regiones en el diagrama son la notación de Harel para la concurrencia: dicen que todo lo que hay a un lado y al otro está activo a la vez. No hay flechas entre regiones porque son independientes; cada una evoluciona por su cuenta.
De exponencial a lineal
El ahorro no es estético, es de complejidad. Cuenta los estados. Con tres dimensiones binarias, una FSM plana necesita dos elevado a tres, ocho estados combinados, y cada uno con tres aristas de conmutación: veinticuatro transiciones que enumerar y mantener. El statechart paralelo tiene tres regiones de dos estados: seis nodos y seis transiciones. La diferencia se dispara al crecer: una cuarta dimensión lleva la FSM plana a dieciséis estados y el statechart a ocho nodos; una quinta, a treinta y dos frente a diez.
FSM plana: producto
Con dimensiones de tamaños a, b, c, el número de estados es su producto. Cada dimensión nueva multiplica el total. El crecimiento es exponencial en el número de dimensiones.
Paralelo: suma
Con las mismas dimensiones como regiones, el número de nodos es su suma. Cada dimensión nueva añade sus pocos estados. El crecimiento es lineal.
Sin estados imposibles
No se enumeran combinaciones, así que no hay riesgo de olvidar una ni de crear una que no debería existir. Las combinaciones válidas emergen del producto de regiones.
Legibilidad local
Para entender la negrita basta mirar su región; no hay que razonar sobre las otras dimensiones. Cada preocupación queda aislada en su propio recuadro.
El nombre engaña a quien viene de la concurrencia de sistemas. Las regiones ortogonales no corren en hilos distintos ni introducen condiciones de carrera. Todo el statechart procesa un evento a la vez, de forma determinista y en un solo turno; lo que la ortogonalidad expresa es independencia lógica de dimensiones, no ejecución simultánea física. Ortogonal significa aquí perpendicular en el sentido geométrico: ejes que no se proyectan uno sobre otro. La negrita y la cursiva son ejes ortogonales del formato, igual que las coordenadas de un plano. Confundir esta ortogonalidad conceptual con el paralelismo de hilos lleva a temer carreras donde no las hay.
Difusión: un evento para todas las regiones
Las regiones son independientes, pero no incomunicadas. Cuando llega un evento, el intérprete lo ofrece a todas las regiones activas a la vez, y cada una lo resuelve por su cuenta con el burbujeo de la lección anterior. La consecuencia es potente: un mismo evento puede provocar transiciones simultáneas en varias regiones. Un QUITAR_FORMATO declarado en las tres regiones apaga las tres de un golpe, en un solo turno.
// el mismo evento manejado en cada region: una difusion, tres transiciones
negrita: {
initial: 'off',
states: {
off: { on: { TOGGLE_NEGRITA: 'on', QUITAR_FORMATO: 'off' } },
on: { on: { TOGGLE_NEGRITA: 'off', QUITAR_FORMATO: 'off' } },
},
},
// ... cursiva y subrayado declaran igualmente QUITAR_FORMATO: 'off'
Esta difusión es el canal de coordinación entre regiones que Harel incluyó en el formalismo. Una región puede además emitir un evento interno con raise, y ese evento se difunde también a todas las regiones en el mismo ciclo, permitiendo que un cambio en una dimensión desencadene ajustes en otra sin cablear transiciones directas entre ellas. Un estado paralelo se considera completado solo cuando todas sus regiones han alcanzado un estado final, momento en el que dispara su onDone: la conjunción de todas las dimensiones terminadas.
La idea honda de las regiones paralelas es que el espacio de estados de un sistema real casi nunca es un bloque monolítico, sino un producto de subespacios independientes, y modelarlo como un bloque es tan absurdo como escribir un polinomio sin factorizar. Una FSM plana lista las ocho combinaciones de tres conmutadores igual que quien expande un producto en sus ocho términos; la ortogonalidad los factoriza de nuevo en tres factores de dos, recuperando la estructura que la expansión había triturado. Y factorizar no solo ahorra símbolos: revela la independencia. Cuando ves tres regiones separadas, sabes de un vistazo que negrita, cursiva y subrayado no se estorban, un hecho que en la lista de ocho estados combinados estaba presente pero invisible, ahogado entre transiciones. Por eso la ortogonalidad no es una optimización, sino un acto de modelado honesto: hace explícita en la estructura del programa una verdad que ya existía en el dominio. El corolario práctico es una heurística de diseño infalible. Ante un estado que parece tener demasiadas variantes, pregúntate si esas variantes son en realidad el producto de unas pocas dimensiones independientes; si lo son, no estás ante un estado complejo, sino ante varios estados simples que alguien mezcló, y separarlos en regiones te devuelve la simplicidad que el producto cartesiano te había robado.
- Implementa el formato con tres regiones paralelas y comprueba que
getSnapshot().valuees un objeto con una clave por región. - Conmuta negrita y subrayado, deja cursiva intacta, y verifica que las tres dimensiones son independientes.
- Cuenta los estados y las aristas que exigiría una FSM plana equivalente y compáralos con los del statechart paralelo.
- Añade una cuarta dimensión —tachado— y observa que el statechart crece en dos nodos mientras la FSM plana duplicaría su tamaño.
- Declara un
QUITAR_FORMATOen las tres regiones y confirma que un solo evento apaga las tres a la vez por difusión. - Toma un componente real de tu app con muchas variantes y decide si son un producto de dimensiones ortogonales; si lo son, redíbujalo en regiones.