🕰️ Historia de la Programación: De los Algoritmos Mecánicos a la Era de la Inteligencia Artificial
📜 Esta sección trazará el desarrollo de la programación desde sus orígenes mecánicos hasta los lenguajes modernos, siguiendo las clasificaciones que ya trabajamos: niveles de abstracción, paradigmas, tipado, orientación de uso, etc.
Introducción
La programación no nació con los ordenadores, sino con la necesidad humana de automatizar procesos. Desde los telares del siglo XIX hasta los lenguajes que hoy impulsan la inteligencia artificial, la evolución de la programación ha sido una historia de abstracción, eficiencia y creatividad.
🧮 Siglo XIX: El Origen Mecánico
Joseph Marie Jacquard (1801)
- Invención: Telar programable con tarjetas perforadas.
- Clasificación: Bajo nivel, orientado a hardware.
- Impacto: Primer sistema que usó instrucciones codificadas para automatizar tareas.
Ada Lovelace (1842)
- Contribución: Primer algoritmo diseñado para ser ejecutado por una máquina (la Máquina Analítica de Babbage).
- Paradigma: Algorítmico, precursor de la programación estructurada.
- Reconocimiento: Considerada la primera programadora de la historia.
🧠 1930–1950: Fundamentos Teóricos
Alan Turing (1936)
- Aporte: Máquina de Turing, modelo teórico de computación.
- Paradigma: Lógico, base de la computación moderna.
- Legado: Inspiró la arquitectura de Von Neumann y la idea de almacenar programas como datos.
Primeros códigos binarios
- Uso: Programación directa de máquinas como ENIAC.
- Clasificación: Lenguaje de máquina, extremadamente bajo nivel.
🧪 Años 50: Primeros Lenguajes de Alto Nivel
FORTRAN (1957)
- Creador: John Backus (IBM).
- Paradigma: Imperativo, estructurado.
- Tipado: Estático.
- Uso: Cálculo científico.
- Importancia: Primer lenguaje compilado de alto nivel.
LISP (1958)
- Creador: John McCarthy.
- Paradigma: Funcional.
- Tipado: Dinámico.
- Uso: Inteligencia artificial.
COBOL (1959)
- Creadora: Grace Hopper.
- Paradigma: Imperativo, orientado a negocios.
- Tipado: Estático.
- Uso: Finanzas, administración pública.
🧩 Años 60–70: Estructura y Educación
ALGOL (1960)
- Paradigma: Estructurado.
- Influencia: Base para Pascal, C y otros lenguajes modernos.
BASIC (1964)
- Creadores: Kemeny y Kurtz.
- Paradigma: Imperativo.
- Tipado: Débil.
- Uso: Educación, accesibilidad.
Pascal (1970)
- Creador: Niklaus Wirth.
- Paradigma: Estructurado.
- Tipado: Estático.
- Uso: Enseñanza, desarrollo de software.
C (1972)
- Creador: Dennis Ritchie.
- Paradigma: Estructurado, bajo nivel.
- Tipado: Estático.
- Uso: Sistemas operativos, UNIX.
🧱 Años 80: Orientación a Objetos y Multiparadigma
Smalltalk (1980)
- Paradigma: Orientado a objetos puro.
- Tipado: Dinámico.
- Uso: Interfaces gráficas, educación.
C++ (1983)
- Creador: Bjarne Stroustrup.
- Paradigma: Multiparadigma (estructurado + OO).
- Tipado: Estático.
- Uso: Software de alto rendimiento, videojuegos.
🌐 Años 90: Internet y Web
Python (1991)
- Creador: Guido van Rossum.
- Paradigma: Multiparadigma.
- Tipado: Dinámico.
- Uso: Web, ciencia de datos, automatización.
Java (1995)
- Creador: James Gosling.
- Paradigma: Orientado a objetos.
- Tipado: Estático.
- Uso: Aplicaciones empresariales, Android.
JavaScript (1995)
- Creador: Brendan Eich.
- Paradigma: Multiparadigma.
- Tipado: Dinámico.
- Uso: Frontend web, luego backend con Node.js.
PHP (1995)
- Creador: Rasmus Lerdorf.
- Paradigma: Imperativo.
- Tipado: Dinámico.
- Uso: Backend web.
🤖 2000–2020: Nuevas Necesidades y Paradigmas
C# (2000)
- Creador: Microsoft.
- Paradigma: Orientado a objetos.
- Tipado: Estático.
- Uso: Aplicaciones Windows, videojuegos.
Ruby + Rails (1995/2005)
- Paradigma: Orientado a objetos.
- Tipado: Dinámico.
- Uso: Desarrollo web ágil.
Scala (2003)
- Paradigma: Funcional + OO.
- Tipado: Estático.
- Uso: Big Data, backend.
Go (2009)
- Creador: Google.
- Paradigma: Imperativo, concurrente.
- Tipado: Estático.
- Uso: Backend, sistemas distribuidos.
Rust (2010)
- Creador: Mozilla.
- Paradigma: Multiparadigma, seguro.
- Tipado: Estático.
- Uso: Sistemas, rendimiento.
Julia (2012)
- Paradigma: Científico, funcional.
- Tipado: Dinámico.
- Uso: IA, simulaciones.
TypeScript (2012)
- Creador: Microsoft.
- Paradigma: Superset de JavaScript.
- Tipado: Estático.
- Uso: Frontend robusto.
🧠 2020–Hoy: Inteligencia Artificial, Cloud y Concurrencia
- Lenguajes dominantes: Python, JavaScript, Go, Rust, Julia.
- Paradigmas emergentes: Funcional reactivo, programación concurrente, declarativa para IA.
- Tendencias:
- Automatización con Python.
- Infraestructura con Go y Rust.
- Web moderna con TypeScript y React.
- Ciencia de datos con Julia y R.
📚 Bibliografía Consultada
- Wikipedia – Historia de los lenguajes de programación
- DevCamp – Historia de la programación
- Epitech – Evolución de los lenguajes de programación
- Computer Hoy – Historia de los lenguajes de programación
- MSN – Grace Hopper y el origen de COBOL
Vamos a aclarar todo esto punto por punto, con ejemplos y contexto histórico. Te explico primero dónde encaja el lenguaje ensamblador en la historia, luego los conceptos de imperativo, estructurado y multiparadigma, y por último el porqué del “bajo nivel” en C.
🧵 ¿Dónde encaja el lenguaje ensamblador en la historia?
El lenguaje ensamblador (también llamado Assembly) es uno de los primeros lenguajes de programación que se crearon tras el uso directo del código máquina (binario). Surgió en los años 40 y 50, cuando los primeros ordenadores como el ENIAC y el UNIVAC necesitaban instrucciones precisas para operar.
📌 Características del ensamblador:
- Muy bajo nivel: Cada instrucción corresponde directamente a una operación del procesador.
- Dependiente del hardware: Cada arquitectura (Intel, ARM, etc.) tiene su propio conjunto de instrucciones.
- No portable: El código ensamblador para una máquina no funciona en otra.
- Usos típicos: BIOS, microcontroladores, drivers, optimización extrema.
🧠 ¿Por qué se creó?
Porque escribir directamente en binario era inhumano. El ensamblador permitió usar mnemónicos como MOV
, ADD
, JMP
en lugar de cadenas de 0s y 1s. Fue el primer paso hacia la abstracción.
🧬 Otros lenguajes de bajo nivel similares:
- C: Aunque más abstracto que ensamblador, permite manipular memoria directamente.
- Forth: Usado en sistemas embebidos, muy cercano al hardware.
- PL/M: Usado en microprocesadores Intel en los 70.
- BCPL: Antecesor de C, también muy cercano al hardware.
🧩 ¿Qué es un lenguaje imperativo?
Un lenguaje imperativo es aquel en el que el programador indica paso a paso cómo debe ejecutarse una tarea. Es como dar instrucciones a un robot: “haz esto, luego esto, luego aquello”.
🔧 Ejemplos:
int suma(int a, int b) {
int resultado = a + b;
return resultado;
}
Aquí estás diciendo explícitamente qué hacer: declarar una variable, sumar, devolver.
🧠 Características:
- Control total del flujo de ejecución.
- Uso de variables, bucles, condicionales.
- Ejemplos: C, Pascal, BASIC, Python (aunque también soporta otros paradigmas).
🧱 ¿Qué es un lenguaje estructurado?
La programación estructurada es una subcategoría de la imperativa, que se enfoca en organizar el código en bloques lógicos: funciones, bucles, condicionales, evitando el uso de goto
.
🧠 Objetivo:
- Mejor legibilidad.
- Menor complejidad.
- Código más mantenible.
🔧 Ejemplo en C:
void imprimirNumeros() {
for (int i = 0; i < 10; i++) {
printf("%d\n", i);
}
}
Este estilo evita saltos arbitrarios y favorece estructuras claras.
🔀 ¿Qué significa que un lenguaje sea multiparadigma?
Un lenguaje multiparadigma permite combinar varios estilos de programación. Por ejemplo, puedes escribir código imperativo, orientado a objetos y funcional en el mismo lenguaje.
🔧 Ejemplo en Python:
# Imperativo
x = 5
y = 10
print(x + y)
# Funcional
print(sum([1, 2, 3]))
# Orientado a objetos
class Persona:
def __init__(self, nombre):
self.nombre = nombre
🧠 Lenguajes multiparadigma:
- Python
- JavaScript
- Rust
- Scala
- C++
🧬 ¿Por qué se considera C un lenguaje de bajo nivel?
Aunque C es un lenguaje de alto nivel comparado con ensamblador, se le considera de bajo nivel relativo por varias razones:
🔍 Características que lo acercan al hardware:
- Punteros: Acceso directo a direcciones de memoria.
- Manipulación de bits: Operaciones bit a bit (
&
,|
,<<
,>>
). - Sin gestión automática de memoria: Tú decides cuándo y cómo liberar memoria (
malloc
,free
). - Compilación directa a código máquina: Muy eficiente.
🧠 ¿Usa ensamblador dentro?
No exactamente. Pero el compilador de C traduce el código a instrucciones de ensamblador antes de generar el binario. Además, puedes incluir código ensamblador dentro de C usando asm
:
asm("movl %eax, %ebx");
Esto se usa en casos muy específicos donde necesitas optimización extrema o acceso a instrucciones del procesador.
Los tres paradigmas pueden parecer similares, sobre todo cuando se expresan con funciones, pero tienen fundamentos distintos. Vamos a desentrañarlos con claridad y ejemplos contrastados para que veas cómo se diferencian en filosofía, estilo y propósito.
🧠 ¿Por qué todos usan funciones?
Porque las funciones son una herramienta común en muchos lenguajes modernos. Pero no definen el paradigma, sino cómo se usan y qué principios siguen.
🔧 Paradigma Imperativo
Idea central: El programa es una secuencia de instrucciones que modifican el estado del sistema paso a paso.
- Tú mandas: Le dices al ordenador qué hacer en cada momento.
- Usa variables, bucles, condicionales.
- Ejemplo típico: C, JavaScript, Python (modo imperativo).
int suma(int a, int b) {
int resultado = a + b;
return resultado;
}
Aquí estás controlando el flujo y gestionando el estado (la variable resultado
).
🧱 Paradigma Estructurado
Idea central: Es imperativo, pero con disciplina. Se basa en estructuras de control bien definidas (if, for, while) y evita saltos arbitrarios como goto
.
- Organiza el código en bloques lógicos.
- Favorece la legibilidad y mantenibilidad.
- Ejemplo típico: Pascal, C (bien escrito), Ada.
void imprimirNumeros() {
for (int i = 0; i < 10; i++) {
printf("%d\n", i);
}
}
Aquí no hay saltos locos ni manipulación directa del flujo. Todo está estructurado.
🧪 Paradigma Funcional
Idea central: El programa es una composición de funciones puras, sin modificar el estado ni usar variables mutables.
- No hay bucles ni variables cambiantes.
- Se basa en expresiones y recursión.
- Ejemplo típico: Haskell, Lisp, OCaml, también Python o JavaScript en modo funcional.
def suma_lista(lista):
return sum(lista)
O más puro aún:
sumaLista xs = foldl (+) 0 xs
Aquí no hay estado mutable, ni bucles. Solo funciones que transforman datos.
🔀 ¿Por qué parecen similares?
Porque muchos lenguajes modernos (como Python, JavaScript, incluso C++) son multiparadigma. Puedes escribir código imperativo, estructurado o funcional en el mismo lenguaje. Pero el paradigma se define por cómo piensas y estructuras tu solución, no por el lenguaje en sí.
🧭 Comparación rápida
Paradigma | Control del flujo | Estado mutable | Uso de funciones | Ejemplo típico |
---|---|---|---|---|
Imperativo | Directo | Sí | Opcional | C, Java |
Estructurado | Controlado | Sí | Recomendado | Pascal, C |
Funcional | Declarativo | No | Central | Haskell, Lisp |
Vamos a resolver el mismo problema en tres paradigmas distintos: imperativo, estructurado y funcional, con comentarios detallados para que entiendas cada paso. El problema será:
Sumar los números pares de una lista.
🧮 1. Imperativo (en C)
#include <stdio.h>
int suma_pares(int lista[], int tamaño) {
int suma = 0; // Acumulador para la suma
for (int i = 0; i < tamaño; i++) { // Recorremos la lista
if (lista[i] % 2 == 0) { // Si el número es par
suma += lista[i]; // Lo sumamos
}
}
return suma; // Devolvemos el resultado
}
🧠 ¿Qué ocurre aquí?
- Usamos un bucle
for
para recorrer la lista. - Usamos una variable mutable
suma
que va acumulando el resultado. - Usamos una condición
if
para filtrar los pares. - Todo se hace paso a paso: es imperativo puro.
🧱 2. Estructurado (en C, estilo limpio)
#include <stdio.h>
int es_par(int n) {
return n % 2 == 0; // Función clara para saber si es par
}
int suma_pares(int lista[], int tamaño) {
int suma = 0;
for (int i = 0; i < tamaño; i++) {
if (es_par(lista[i])) { // Usamos función estructurada
suma += lista[i];
}
}
return suma;
}
🧠 ¿Qué cambia aquí?
- Separamos la lógica en funciones pequeñas (
es_par
). - Evitamos repetir código.
- El flujo sigue siendo imperativo, pero estructurado y más legible.
🧪 3. Funcional (en Python estilo funcional)
def es_par(n):
return n % 2 == 0 # Función pura: no cambia nada externo
numeros = [1, 2, 3, 4, 5, 6]
# Usamos filter para quedarnos solo con los pares
# Usamos sum para sumar directamente
suma = sum(filter(es_par, numeros))
print(suma)
🧠 ¿Qué ocurre aquí?
filter(es_par, numeros)
→ devuelve solo los números pares.sum(...)
→ suma los elementos.- No hay bucles ni variables mutables.
- Todo se hace con funciones puras que no modifican estado.
🧬 4. Funcional puro (en Haskell)
-- Definimos una función que suma los pares de una lista
sumaPares :: [Int] -> Int
sumaPares xs = sum (filter even xs)
🧠 Explicación paso a paso:
sumaPares
es el nombre de la función.:: [Int] -> Int
significa: recibe una lista de enteros y devuelve un entero.filter even xs
→ filtra los elementos pares (even
es función estándar).sum (...)
→ suma los elementos filtrados.
🔍 ¿Dónde están los bucles?
No hay. En Haskell, los bucles se reemplazan por funciones de orden superior como:
map
→ transforma cada elemento.filter
→ selecciona elementos.foldl
/foldr
→ acumulan resultados (como bucles invisibles).
🧭 Comparación final
Paradigma | Bucles | Variables mutables | Condicionales | Funciones puras | Comentario |
---|---|---|---|---|---|
Imperativo | Sí | Sí | Sí | No | Control total paso a paso |
Estructurado | Sí | Sí | Sí | Parcialmente | Más limpio y modular |
Funcional | No | No | Implícitos | Sí | Declarativo, sin estado |