Parte 7 — Paradigmas · ⏱️ Duración estimada: 90 min · Nivel: Intermedio ✅ Clase construida — 10 implementaciones del núcleo verificadas contra
casos.json.
El paradigma lógico lleva la idea declarativa a su forma más radical. Si en SQL describías una relación sobre datos, en programación lógica describes relaciones mediante hechos y reglas y luego formulas consultas; un motor de inferencia deduce las respuestas por ti. Sebesta dedica el capítulo 16 de Concepts of Programming Languages a este modelo, y su tesis es que aquí el programador no escribe un algoritmo: escribe proposiciones en la lógica de predicados —concretamente cláusulas de Horn— y el sistema aplica resolución, unificación y backtracking para encontrar los valores que hacen ciertas esas proposiciones.
La diferencia con todo lo anterior es profunda. Una regla como es_divisor(A, B) :- 0 is B mod A no es una función que se llama con argumentos y devuelve un valor; es una afirmación sobre cuándo la relación "A es divisor de B" se cumple. Preguntar ?- es_divisor(3, 12) no ejecuta pasos que tú dictaste: el motor de Prolog intenta unificar tu consulta con las cláusulas conocidas y deduce si hay una prueba. Van Roy y Haridi encuadran esto en su modelo relacional, donde la computación es búsqueda de soluciones que satisfacen restricciones, no ejecución de instrucciones.
El objetivo de hoy es asomarse a esa forma de pensar con un problema mínimo y comprobable: decidir si a divide a b. Como el núcleo de este curso no incluye un intérprete Prolog, las implementaciones expresan la relación con la aritmética disponible (b mod a == 0), pero la nota final muestra cómo se declararía y consultaría en Prolog auténtico. Verás que SQL, que estudiaste en la clase anterior, es el primo declarativo cercano del paradigma lógico: ambos describen condiciones y dejan la deducción al sistema.
Al finalizar, podrás:
| # | Tema | Por qué importa |
|---|---|---|
| 1 | Programación lógica | Hechos y reglas |
| 2 | Regla | Relación que se cumple o no |
| 3 | Consulta | Preguntar por la verdad de algo |
es_divisor(A,B) :- B mod A =:= 0.El corazón técnico del paradigma lógico es la unificación, que Sebesta explica en el capítulo 16 como el proceso de encontrar sustituciones de variables que hacen idénticos dos términos. Cuando consultas ?- es_divisor(3, 12), el motor unifica 3 con A y 12 con B, y a partir de ahí evalúa el cuerpo de la regla. Pero la unificación es más potente que el paso de parámetros de un lenguaje imperativo: es bidireccional y trabaja con variables lógicas. Podrías preguntar ?- es_divisor(A, 12) con A sin ligar, y el motor buscaría, por backtracking, todos los valores de A que satisfacen la relación. La misma regla sirve para verificar y para buscar, algo impensable en una función corriente que va estrictamente de argumentos a resultado.
El motor de inferencia usa resolución sobre cláusulas de Horn —cláusulas con a lo sumo un literal positivo, que son exactamente la forma de las reglas Prolog cabeza :- cuerpo—. La resolución es la regla de deducción que, combinada con la unificación, permite probar consultas encadenando reglas y hechos. Cuando una rama de la prueba falla, el backtracking deshace las últimas ligaduras de variables y prueba otra alternativa, explorando sistemáticamente el espacio de soluciones. Van Roy y Haridi describen este mecanismo como una búsqueda declarativa: tú declaras las restricciones (las reglas), y el sistema explora el árbol de posibilidades para satisfacerlas.
Aquí radica el parentesco con SQL que la clase subraya. SQL es declarativo pero opera sobre relaciones almacenadas en tablas; Prolog es lógico y opera sobre relaciones definidas por reglas que pueden ser recursivas y deducir hechos nuevos. Datalog, mencionado más abajo, es el punto medio: un subconjunto de Prolog pensado para consultar datos, que hoy sostiene motores de razonamiento y bases de datos deductivas. Los tres comparten el ADN declarativo —describir el qué— pero el lógico añade la capacidad de inferir, no solo de seleccionar.
En Prolog no dices cómo comprobar la divisibilidad: declaras la regla y preguntas es_divisor(3, 12). El motor responde. Es el estilo de los sistemas expertos y el razonamiento.
a b (dos enteros, a != 0)divisor=<true|false> (¿a divide a b?)Especificación y verificación en casos.json:
| stdin | esperado |
|---|---|
3 12 |
divisor=true |
5 12 |
divisor=false |
4 12 |
divisor=true |
REGLA es_divisor(a,b) SI b mod a == 0 ; CONSULTAR es_divisor(a,b)
Mismo algoritmo, forma idiomática en cada lenguaje. Todas producen la salida de casos.json.
Cada bloque es el archivo real de implementaciones/: el enlace de cada lenguaje abre su fuente, y el comando de al lado lo ejecuta.
python/main.py · python main.pyimport sys
a, b = map(int, sys.stdin.readline().split())
es_divisor = b % a == 0
print(f"divisor={'true' if es_divisor else 'false'}")
🧬 El mismo programa en la familia Scripting dinámico: Ruby · Perl · Lua · Tcl · R
javascript/main.mjs · node main.mjsimport { readFileSync } from "node:fs";
const [a, b] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);
console.log(`divisor=${b % a === 0 ? "true" : "false"}`);
🧬 El mismo programa en la familia JavaScript / web: Dart · ActionScript
typescript/main.ts · pnpm exec tsx main.tsimport { readFileSync } from "node:fs";
const [a, b]: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);
console.log(`divisor=${b % a === 0 ? "true" : "false"}`);
🧬 El mismo programa en la familia JavaScript / web: Dart · ActionScript
java/Main.java · java Main.javaimport java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
static boolean esDivisor(long a, long b) {
return b % a == 0;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] p = br.readLine().trim().split("\\s+");
long a = Long.parseLong(p[0]);
long b = Long.parseLong(p[1]);
System.out.println("divisor=" + (esDivisor(a, b) ? "true" : "false"));
}
}
🧬 El mismo programa en la familia JVM: Kotlin · Scala · Groovy · Clojure
csharp/Program.cs · dotnet runusing System;
string[] p = Console.In.ReadToEnd()
.Split(new[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
long a = long.Parse(p[0]);
long b = long.Parse(p[1]);
Console.WriteLine($"divisor={(b % a == 0 ? "true" : "false")}");
🧬 El mismo programa en la familia .NET: F# · VB.NET
go/main.go · go run main.gopackage main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
line, _ := bufio.NewReader(os.Stdin).ReadString('\n')
f := strings.Fields(line)
a, _ := strconv.Atoi(f[0])
b, _ := strconv.Atoi(f[1])
res := "false"
if b%a == 0 {
res = "true"
}
fmt.Printf("divisor=%s\n", res)
}
🧬 El mismo programa en la familia Sistemas: Zig · Nim · D
rust/main.rs · rustc main.rs -o main && ./mainuse std::io::Read;
fn main() {
let mut s = String::new();
std::io::stdin().read_to_string(&mut s).unwrap();
let v: Vec<i64> = s.split_whitespace().map(|x| x.parse().unwrap()).collect();
let res = if v[1] % v[0] == 0 { "true" } else { "false" };
println!("divisor={res}");
}
🧬 El mismo programa en la familia Sistemas: Zig · Nim · D
c/main.c · cc main.c -o main && ./main#include <stdio.h>
int main(void) {
long a, b;
if (scanf("%ld %ld", &a, &b) != 2) return 1;
printf("divisor=%s\n", b % a == 0 ? "true" : "false");
return 0;
}
🧬 El mismo programa en la familia C / llaves: C++ · Objective-C
sql/main.sql · sqlite3 :memory: < main.sql-- SQL (declarativo, primo del lógico): la condición como CASE.
WITH pares(a, b) AS (VALUES (3, 12), (5, 12), (4, 12))
SELECT printf('divisor=%s', CASE WHEN b % a = 0 THEN 'true' ELSE 'false' END) AS resultado FROM pares;
🧬 El mismo programa en la familia Lógica y declarativa: Prolog · Datalog
php/main.php · php main.php<?php
[$a, $b] = preg_split('/\s+/', trim(fgets(STDIN)));
echo "divisor=" . ((int) $b % (int) $a === 0 ? "true" : "false") . "\n";
🧬 El mismo programa en la familia Scripting dinámico: Ruby · Perl · Lua · Tcl · R
SQL es declarativo: no lee de stdin como los demás; su implementación muestra la misma idea sobre una tabla de casos, y el verificador la marca como ilustrativa.
| Clase de diferencia | Observación entre lenguajes |
|---|---|
| Sintáctica | En los del núcleo es un if (b % a == 0); en Prolog, una regla. |
| Semántica | El lógico deduce; los imperativos comprueban. |
| Paradigmática | SQL (declarativo) es primo del lógico: describe condiciones. |
En Prolog: es_divisor(A, B) :- 0 is B mod A. y la consulta ?- es_divisor(3, 12).. Datalog es un subconjunto para datos.
Los mismos casos para todas las implementaciones: casos.json. Verifica la equivalencia:
python scripts/verificar_equivalencia.py 118
Detalle en reto.md.
Libros de la parte:
Libros de los lenguajes del núcleo:
⏮️ Clase 117 · 📂 Parte · 📚 Índice · 🌐 Atlas · Clase 119 ⏭️