Unidad II · Tema 2.3 Disponible

Expresiones, comandos y control

Expresiones, comandos, secuencia, selección, iteración y recursión.

Objetivo de aprendizaje

Al concluir este tema, el estudiante será capaz de:

  1. Distinguir una expresión de un comando y explicar por qué esa frontera varía entre lenguajes.
  2. Predecir el resultado de expresiones lógicas con evaluación de cortocircuito y reconocer cuándo una subexpresión no llega a evaluarse.
  3. Aplicar la secuencia, la selección y la iteración, y seguir un ciclo mediante una tabla de seguimiento.
  4. Diseñar una función recursiva identificando el caso base, el caso recursivo y el progreso hacia el caso base.
  5. Relacionar la recursión con la iteración y reconocer los límites de la recursión en Python (profundidad de la pila).

Ruta de trabajo del tema

Etapa Contenido Producto del estudiante
1 Planteamiento inicial Hipótesis escrita
2 Expresiones y comandos Clasificación de construcciones
3 Evaluación de expresiones y cortocircuito Predicciones verificadas
4 Secuencia y selección Lectura de condicionales
5 Iteración y tabla de seguimiento Tabla de seguimiento propia
6 Recursión Diseño de una función recursiva
7 Recursión frente a iteración Comparación de costos
8 Verificación y reformulación Cuestionario y respuesta revisada

1. Planteamiento inicial

Todo programa combina cálculos y acciones: se calculan valores, se decide qué camino seguir y se repiten pasos. Las preguntas interesantes aparecen cuando el orden en que se evalúan las partes importa, es decir, cuando evaluar algo produce un efecto además de un valor.

Considere el siguiente programa en Python:

def a():
    print("se evaluó a")
    return False

def b():
    print("se evaluó b")
    return True

print(a() and b())

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Esta hipótesis se retomará al final de la sección 3.


2. Expresiones y comandos

Definición — Expresión: construcción que se evalúa y produce un valor: 3 + 4, len(lista), x > 0 and y < 5.

Definición — Comando (sentencia): construcción que se ejecuta por su efecto: modificar el estado (asignación), mostrar algo, o alterar el flujo del programa (condicionales, ciclos, return).

La frontera entre ambas no es la misma en todos los lenguajes:

Lenguaje Asignación Condicional
Python Comando (x = 5 no produce un valor utilizable) Comando if; existe además la expresión condicional a if c else b
C Expresión: y = (x = 5) asigna 5 a x y luego a y Comando if
Lenguajes funcionales (Scheme, Haskell) No existe asignación destructiva en el núcleo Todo es expresión: if produce un valor

Caso de estudio. En C, la expresión x = 5 modifica a x y además vale 5. Eso permite escribir y = x = 5;, pero también cometer el error clásico if (x = 5) en lugar de if (x == 5), que asigna y siempre es verdadero. En Python, la asignación no es una expresión, de modo que if x = 5: es un error de sintaxis: la decisión de diseño elimina una clase de errores (aumenta la fiabilidad), a costa de cierta expresividad.

Aplicación práctica. Clasifique cada construcción. Observe los casos que dependen del lenguaje.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


3. Evaluación de expresiones y cortocircuito

Evaluar una expresión compuesta exige decidir el orden: primero se respeta la precedencia y la asociatividad de los operadores (como en el tema 1.2.1) y después el orden de evaluación de los operandos, que en Python es de izquierda a derecha.

Definición — Evaluación de cortocircuito: en las operaciones lógicas and y or, el segundo operando solo se evalúa si el primero no basta para determinar el resultado. En a and b, si a es falso el resultado ya es falso; en a or b, si a es verdadero el resultado ya es verdadero.

Caso de estudio. En a() and b(), si a() devuelve falso, b() no se llama: no se muestra su mensaje. El cortocircuito es más que una optimización: permite escribir n != 0 and 10 / n > 2 sin riesgo de dividir entre cero, pues la división solo se evalúa cuando n no es cero.

En Python, and y or devuelven uno de sus operandos, no necesariamente un valor lógico: 0 or "x" produce "x", y "" and 5 produce "". Se consideran «falsos» el cero, el texto vacío, las colecciones vacías, None y False.

Aplicación práctica. Anticipe cuáles llamadas se realizan en cada línea. Observe el orden de las salidas.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Contraste con el planteamiento inicial

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


4. Secuencia y selección

Definición — Secuencia: ejecución de comandos uno después de otro en el orden del texto. Es la estructura de control por omisión de los lenguajes imperativos.

Definición — Selección: estructura que elige entre alternativas según una condición: if / elif / else, o switch o match según el lenguaje.

Dos reglas de lectura: las condiciones de una cadena if / elif / else se evalúan en orden y solo se ejecuta el bloque de la primera que resulta verdadera; el bloque else se ejecuta únicamente si ninguna lo fue.

Lenguaje Selección múltiple Observación
Python if / elif / else; match (desde Python 3.10) Los bloques se delimitan por sangría
Java y C++ if / else if / else; switch En el switch clásico, sin break la ejecución continúa en la alternativa siguiente (fall-through)

Aplicación práctica.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


5. Iteración

Definición — Iteración: repetición de un bloque de comandos mientras se cumpla una condición (while) o para cada elemento de una secuencia (for). break termina el ciclo; continue salta a la siguiente repetición.

Para razonar sobre un ciclo se utiliza una tabla de seguimiento (corrida de escritorio): una columna por variable y una fila por repetición, que muestra cómo evoluciona el estado.

Repetición n suma
Inicio 5 0
1 4 5
2 3 9

Definición — Invariante de ciclo: propiedad que es verdadera antes de cada repetición del ciclo. En el cálculo de una suma, «suma contiene la suma de los números ya procesados» es un invariante; permite demostrar que, al terminar, suma contiene la suma total.

Caso de estudio. El ciclo while n > 0 con suma = suma + n y n = n - 1 termina porque n disminuye en cada repetición hasta dejar de cumplir la condición. Un ciclo cuya condición nunca se vuelve falsa es un ciclo infinito: es indispensable que cada repetición se acerque a la terminación.

Aplicación práctica. Marque «Tabla de seguimiento» para ver cómo cambian las variables paso a paso. Use la predicción de cada asignación para anticipar los valores.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


6. Recursión

Definición — Recursión: técnica en la que una función se invoca a sí misma (de manera directa) o por medio de una cadena de llamadas que regresa a ella (de manera indirecta). Una definición recursiva correcta tiene tres partes:

  1. Caso base: una situación que se resuelve sin nueva llamada.
  2. Caso recursivo: una llamada a sí misma con un problema más pequeño.
  3. Progreso: cada llamada se acerca al caso base.

Caso de estudio. La suma de los primeros n números se define como: suma(0) = 0 (caso base) y suma(n) = n + suma(n − 1) (caso recursivo). Cada llamada pendiente conserva su marco con su valor de n y espera el resultado de la siguiente; cuando se llega al caso base, los resultados regresan multiplicándose o sumándose en el camino de vuelta. Es exactamente la pila de marcos del tema 2.2.

Importante: sin caso base, o sin progreso, la recursión no termina. En Python, el intérprete detiene el programa con un RecursionError cuando la pila alcanza su límite (1000 llamadas anidadas de manera predeterminada).

Aplicación práctica. Siga la ejecución de la suma recursiva y compare con la versión iterativa.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


7. Recursión frente a iteración

Todo cálculo que puede expresarse con recursión puede expresarse con iteración (y viceversa): ambas formas son equivalentes en poder de cálculo. Lo que cambia es el costo y la claridad.

Definición — Recursión de cola (tail recursion): recursión en la que la llamada recursiva es lo último que hace la función, sin trabajo pendiente después. Algunos lenguajes (Scheme, por ejemplo) garantizan una optimización que reutiliza el marco en estos casos y ejecutan la recursión de cola en espacio constante. Python no la realiza: cada llamada ocupa un marco nuevo, sea o no de cola.

Formulación Marcos de pila Claridad Cuándo conviene
Iterativa Uno Directa para repeticiones simples Cálculos repetitivos, estructuras lineales
Recursiva Uno por llamada pendiente Muy clara para estructuras recursivas (árboles, divide y vencerás) Problemas con estructura recursiva natural
Recursiva de cola Uno por llamada en Python; uno en lenguajes con optimización Intermedia Lenguajes funcionales

Existe además un riesgo de eficiencia: una recursión que hace dos llamadas por nivel (como Fibonacci en su forma ingenua) repite una cantidad de trabajo que crece de manera exponencial con n.

Aplicación práctica. El laboratorio siguiente mide la profundidad máxima de la pila y el número de llamadas de cuatro formulaciones. Antes de ejecutar, anticipe si se agotará la pila. Este modelo limita la profundidad a 200 llamadas (CPython: 1000).

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


8. Errores conceptuales frecuentes

Error común: creer que en a and b se evalúan siempre ambos operandos. El segundo solo se evalúa si es necesario.

Error común: pensar que and y or siempre devuelven True o False. En Python devuelven uno de sus operandos.

Error común: olvidar el caso base, o escribir una llamada recursiva que no se acerca a él.

Error común: suponer que una recursión de cola es «gratuita» en cualquier lenguaje. En Python consume un marco por llamada.

Error común: escribir la condición de un while sin asegurar que alguna variable cambie hacia la terminación.


9. Verificación del tema

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


10. Libreta de trabajo

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


11. Preguntas de reflexión

  1. ¿Por qué la recursión es central en el paradigma funcional pero opcional en el imperativo? Relacione su respuesta con la presencia o ausencia de variables que cambian.
  2. Proponga una expresión en la que el cortocircuito evite un error y otra en la que cambie un efecto observable. ¿Es buena idea depender de ese efecto?
  3. ¿Qué ventajas y desventajas tiene que la asignación sea una expresión (como en C)?
  4. Escriba una función recursiva para invertir un texto. Identifique su caso base, su caso recursivo y su progreso.
  5. ¿Cuándo preferiría una versión recursiva aunque consuma más marcos de pila? Justifique con los criterios de la pragmática.

12. Lecturas recomendadas

  • Sebesta, R. (2015). Concepts of programming languages (11th ed.), capítulos sobre expresiones y sentencias de asignación, y sobre estructuras de control a nivel de sentencia. Pearson.
  • Python Software Foundation. The Python Language Reference, secciones «Expressions» y «Compound statements». Acceso libre en docs.python.org.
  • Abelson, H., & Sussman, G. J. (1996). Structure and interpretation of computer programs (2nd ed.), sección sobre procesos recursivos e iterativos. MIT Press. Acceso libre en mitpress.mit.edu/sicp.