Elementos Fundamentales de los Lenguajes de Programación
Distinguir los elementos fundamentales de los lenguajes de programación, considerando el entorno de desarrollo, su estructura y sintaxis, para utilizarlos en la creación de programas de cómputo.
Qué vamos a recorrer
Cinco subtemas, de los nombres que usa un programa hasta los tipos de datos que puede manipular. Tres procesos internos —difíciles de imaginar solo con texto— se muestran aquí con animaciones paso a paso.
Un nombre (identificador) es una etiqueta textual. Un objeto es la entidad real en memoria. La asociación entre ambos se llama binding (ligadura), y no siempre es uno a uno.
Varios nombres pueden ligarse al mismo objeto — esto se llama aliasing. En lenguajes de semántica de referencia (Python, Java para objetos), una variable guarda una referencia, no el objeto mismo.
a = [1, 2, 3] b = a # b y a son dos NOMBRES para el MISMO objeto b.append(4) print(a) # [1, 2, 3, 4] — el objeto cambió, visible desde ambos nombres print(id(a) == id(b)) # True: misma identidad de objeto
Confundir "reasignar un nombre" con "mutar un objeto". b = [9] rompe el alias (b apunta a un objeto nuevo); b.append(9) muta el objeto compartido. Esta distinción es la base de la animación de paso de parámetros en 2.4.
Un entorno mapea, en un punto dado de la ejecución, cada nombre visible a su binding. El alcance de un nombre es la región del programa donde ese nombre es visible.
Alcance estático (léxico)
Determinado por la estructura textual del código. Python, Java, C, Haskell lo usan. Se resuelve siguiendo la regla LEGB: Local → Enclosing → Global → Built-in.
Alcance dinámico
Determinado por la secuencia de llamadas en ejecución. Usado en Lisp clásico, variables de Bash, special de Common Lisp. Dificulta razonar sobre el código leyéndolo solamente.
| Criterio | Estático | Dinámico |
|---|---|---|
| Determinado por | Estructura textual | Secuencia de llamadas |
| Predictibilidad | Alta | Baja |
| Lenguajes | Python, Java, C, Haskell | Bash, Common Lisp (special) |
interna() busca a x siguiendo la cadena léxica LEGB.Una expresión se evalúa y produce un valor. Un comando (sentencia) se ejecuta por su efecto. Sobre esa base se construyen las cuatro estructuras de control fundamentales.
Secuencia
Comandos ejecutados uno tras otro, en orden textual.
Selección
if n < 0: return "negativo" elif n == 0: return "cero" else: return "positivo"
Iteración
for i in range(1, n + 1): acumulado += i
Recursión
Un subprograma que se invoca a sí mismo. Requiere caso base y caso recursivo que progrese hacia él.
Python no optimiza la recursión de cola (sin TCO): cada llamada consume un nuevo marco de pila, incluso si es "de cola". El límite por defecto son ~1000 llamadas anidadas (sys.getrecursionlimit()).
factorial(4)Un subprograma es una unidad de código nombrada e invocable. La forma en que se comunican argumento y parámetro define su mecanismo de paso.
| Mecanismo | Qué se copia/liga | Efecto sobre el original | Dónde aparece |
|---|---|---|---|
| Por valor | El valor del argumento | Modificar el parámetro NO afecta al original | C (primitivos), Java (primitivos) |
| Por referencia | La misma localización/variable | Modificar el parámetro SÍ afecta al original | C++ (&), Pascal (var) |
| Por referencia de objeto | Copia de la referencia al objeto | Mutar SÍ se refleja; reasignar NO | Python, Java (objetos), JavaScript |
Python usa "pass by object reference": el parámetro recibe una copia de la referencia. Si el objeto es mutable y se muta con un método, el cambio es visible fuera. Si el parámetro se reasigna, solo cambia el binding local.
Entorno del llamador
Entorno de la función
Un tipo de dato define un conjunto de valores posibles y las operaciones válidas sobre ellos.
Primitivos
int, float, bool, char — no descomponibles.
Compuestos
Arreglos, registros, listas, tuplas, diccionarios — construidos a partir de otros tipos.
| Criterio | Estático | Dinámico | Fuerte | Débil |
|---|---|---|---|---|
| Cuándo se verifica | Compilación | Ejecución | — | |
| Coerción implícita | — | No permitida | Permitida | |
| Ejemplos | Java, C, Haskell | Python, JS | Python, Java | C, JavaScript |
Confundir "estático/dinámico" (cuándo se verifica) con "fuerte/débil" (qué tan estrictas son las conversiones). Python es dinámico y fuerte: "5" + 3 lanza TypeError, no concatena silenciosamente.
x = 5 x = "ahora soy texto" # válido: dinámico "5" + 3 # TypeError — Python es FUERTE, no coerciona
Resumen, actividad y reflexión
Resumen
- Nombres ≠ objetos; el binding los liga.
- El alcance estático se resuelve por la estructura del código (LEGB en Python).
- Stack para llamadas, heap para memoria dinámica; GC automatiza su liberación.
- Toda recursión necesita caso base + caso recursivo.
- Python pasa "por referencia de objeto": mutar se ve fuera, reasignar no.
- Estático/dinámico y fuerte/débil son ejes independientes del sistema de tipos.
Actividad en clase
- Escribe una función anidada que ilustre LEGB.
- Escribe una función recursiva con 2 casos base.
- Escribe una función que reciba una lista y la modifique por mutación y por reasignación; imprime el resultado en ambos casos.
Preguntas de reflexión
- ¿Qué problemas causa el alcance dinámico en programas grandes?
- ¿Por qué la recursión es central en el paradigma funcional?
- ¿Qué riesgos trae la inferencia de tipos frente a la anotación explícita?
- ¿Por qué
incrementar(x)nunca modificaxsix = 5?
Lecturas recomendadas
- Sebesta, R. (2015). Concepts of Programming Languages (11th ed.), cap. 5, 6, 9-10. Pearson.
- Pratt, T., & Zelkowitz, M. (2001). Programming Languages: Design and Implementation (4th ed.), cap. 3-5. Prentice Hall.
- Scott, M. (2015). Programming Language Pragmatics (4th ed.), cap. 3. Morgan Kaufmann.