Ingeniería iOS

Declarativo vs. imperativo: entender SwiftUI

La primera línea de la documentación introductoria de SwiftUI menciona inevitablemente un «framework declarativo». React y Jetpack Compose también se describen como declarativos.

4 min de lectura
Imagen de portada de Declarativo vs. imperativo: entender SwiftUI

La primera línea de la documentación introductoria de SwiftUI menciona inevitablemente un «framework declarativo». React y Jetpack Compose también se describen como declarativos.

Sin embargo, cuando alguien pregunta «¿qué significa declarativo?», la respuesta no resulta sencilla. «Que el código es limpio» no es correcto.

La diferencia entre declarativo e imperativo se resume en una frase. ¿Escribes «How» o «What»?

Este artículo aborda el criterio con ejemplos cotidianos, lo comprueba con código de UIKit y SwiftUI, y explica que lo declarativo no es gratis.

Este es el resumen clave.

  1. Imperativo: indicar paso a paso el procedimiento para alcanzar el resultado deseado — How
  2. Declarativo: describir el resultado deseado y dejar el procedimiento al sistema — What
  3. SQL, HTML, map/filter y SwiftUI son ejemplos representativos de programación declarativa
  4. La esencia de la UI declarativa: declarar «si el estado es este, la pantalla es así» y dejar que el framework actualice la pantalla cuando cambia el estado

Una analogía con un taxi

Lo imperativo consiste en indicar directamente el camino al conductor: «gira a la derecha, sigue 300 metros, gira a la izquierda en el semáforo…». Indicas cada paso, y su conjunto produce el resultado de llegar al destino.

Lo declarativo es decir: «Llévame a la estación de Gangnam». Solo indicas el destino (What) y dejas la elección de la ruta (How) al conductor y al navegador.

Ambos llegan al destino. La diferencia es si yo poseo el procedimiento o solo la descripción del resultado.


En el código, la misma tarea se expresa de dos formas

Escribamos de dos formas el código que selecciona los números pares y los eleva al cuadrado.

// Imperativo: indico cómo iterar y dónde almacenar el resultado
var result: [Int] = []
for n in numbers {
    if n % 2 == 0 {
        result.append(n * n)
    }
}

// Declarativo: describo solo lo que quiero
let result = numbers.filter { $0 % 2 == 0 }.map { $0 * $0 }

La versión imperativa expone las «piezas del procedimiento», como la variable de bucle, el array intermedio y el orden. La declarativa conserva únicamente la intención: «filtrar los pares y elevarlos al cuadrado». El método de iteración queda oculto dentro de filter y map.

Probablemente ya utilizas formas declarativas más conocidas. En SQL escribes «dame las filas que cumplan estas condiciones», no cómo buscar en el índice. En HTML declaras la estructura —«aquí un título, aquí un párrafo»—, no el procedimiento de renderizado.


En la UI, la diferencia se vuelve enorme

La UI imperativa (UIKit) utiliza procedimientos para cambiar la pantalla.

// UIKit: Indicar directamente las operaciones de la pantalla cada vez que cambia el estado
func updateBadge(count: Int) {
    if count > 0 {
        badgeLabel.isHidden = false
        badgeLabel.text = "\(count)"
    } else {
        badgeLabel.isHidden = true
    }
}

La dificultad es que el desarrollador debe seguir continuamente «en qué estado se encuentra la pantalla actual». Si hay varias rutas de actualización, es fácil olvidar una y provocar el bug clásico: «los datos cambiaron, pero la pantalla sigue igual».

La UI declarativa (SwiftUI) declara cómo debe verse la pantalla para un estado dado.

// SwiftUI: countDeclarar que, cuando el estado es este, la pantalla es así
struct BadgeView: View {
    let count: Int
    var body: some View {
        if count > 0 {
            Text("\(count)").badgeStyle()
        }
    }
}

Cuando cambia count, no escribes cómo corregir la pantalla. SwiftUI compara la declaración anterior con la nueva y actualiza solo las partes necesarias. Convierte la UI en una «función del estado». Al delegar el procedimiento de actualización al framework, no queda código de actualización que podamos olvidar.

En lo imperativo, yo escribo el procedimiento de actualización; en lo declarativo, el framework calcula la diferencia
En lo imperativo, yo escribo el procedimiento de actualización; en lo declarativo, el framework calcula la diferencia

Lo declarativo no es gratis

Para mantener el equilibrio, también hay que mirar el otro lado.

Ocultar el procedimiento resulta frustrante cuando lo necesitas. Una petición imperativa como «mueve el scroll exactamente a este offset» puede requerir un rodeo en un framework declarativo.

La depuración tiene un carácter diferente. En lo imperativo sigues el procedimiento; en lo declarativo debes rastrear las decisiones del framework, con preguntas como «¿por qué se volvió a dibujar?». No puedes ignorar por completo el How oculto.

La optimización del rendimiento también acaba exigiendo entender el interior. Si SwiftUI recalcula vistas en exceso, debes saber cómo funcionan el diffing y el seguimiento de dependencias para resolverlo.

Por eso, la perspectiva correcta no es «lo declarativo es superior», sino elegir el nivel de abstracción. Delegar la propiedad del procedimiento simplifica el código alrededor de la intención, pero deja como retos el control detallado y la comprensión interna.

La contrapartida de delegar la propiedad del procedimiento es renunciar al control detallado
La contrapartida de delegar la propiedad del procedimiento es renunciar al control detallado

Resumen

  • Lo imperativo indica el procedimiento para alcanzar un resultado (How), mientras que lo declarativo describe el resultado deseado (What)
  • SQL, HTML y map/filter son formas declarativas que ya conocemos
  • La UI imperativa escribe los procedimientos para cambiar la pantalla y tiende a producir errores de incoherencia entre el estado y la pantalla
  • La UI declarativa declara «si el estado es este, la pantalla es así» y deja las actualizaciones al framework (UI = función del estado)
  • El coste de lo declarativo: el control detallado del procedimiento es difícil y, al final, es necesario entender el funcionamiento interno del framework
  • La esencia no es la superioridad, sino elegir quién posee el procedimiento