🕰️ 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


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

ParadigmaControl del flujoEstado mutableUso de funcionesEjemplo típico
ImperativoDirectoOpcionalC, Java
EstructuradoControladoRecomendadoPascal, C
FuncionalDeclarativoNoCentralHaskell, 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

ParadigmaBuclesVariables mutablesCondicionalesFunciones purasComentario
ImperativoNoControl total paso a paso
EstructuradoParcialmenteMás limpio y modular
FuncionalNoNoImplícitosDeclarativo, sin estado

Publicaciones Similares

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *