Clase 088 — Importar, exportar y organizar un proyecto

Parte 5 — Funciones y modularidad · ⏱️ Duración estimada: 90 min · Nivel: IntermedioClase construida — 10 implementaciones del núcleo verificadas contra casos.json.


🎯 Objetivo

Cerrar la parte con la contracara de escribir funciones: saber traer las que ya existen. Organizar un proyecto no es solo repartir el código propio en módulos; es también decidir, ante cada necesidad, si se reescribe o se importa. La respuesta casi siempre es importar. Todos los lenguajes traen una biblioteca estándar —un conjunto de módulos ya escritos, probados y optimizados— y cada import, use, #include o using es una declaración explícita de que tu código se apoya en ese trabajo previo. Esta clase toma el ejemplo más humilde posible, el valor absoluto, para practicar el gesto de importar y para ver que detrás de abs(n) hay una decisión de organización, no solo de sintaxis.

La idea de fondo la defienden Hunt y Thomas en The Pragmatic Programmer bajo el principio DRY (Don't Repeat Yourself): cada pieza de conocimiento debe tener una única representación autorizada en el sistema. Reescribir un abs propio con un if x < 0 no solo añade líneas: crea una segunda versión de una lógica que ya vivía, correcta, en la biblioteca; y dos versiones de la misma idea son dos sitios donde corregir el mismo bug. Importar la función estándar es aplicar DRY hacia fuera —no repetir lo que el lenguaje ya resolvió por ti—.

Hay además una dimensión de estructura. Los import de un archivo son su lista de dependencias visibles: dicen, de un vistazo, de qué se apoya este código. McConnell insiste en Code Complete en que la organización de un programa —qué depende de qué y en qué dirección— es tan importante como la corrección de cada rutina. Por eso cada lenguaje acompaña sus import con una herramienta de proyecto (package.json, go.mod, Cargo.toml, pom.xml, pyproject.toml, composer.json) que declara las dependencias externas de forma que cualquiera pueda reconstruir el proyecto. Importar bien es el primer eslabón de esa cadena.

🧩 Situación

Un programador novato necesita el valor absoluto y escribe, en cada uno de los cinco archivos donde le hace falta, un pequeño if x < 0: x = -x. Funciona. Meses después descubre que en uno de los cinco copió mal el signo, y lleva semanas devolviendo resultados negativos en un informe. El problema no era saber calcular un valor absoluto —es trivial—: era haber creado cinco copias de una lógica que el lenguaje ya ofrecía correcta y en un solo lugar. Bastaba import, o ni siquiera eso en los lenguajes donde abs es global. Esta es la lección práctica de organizar un proyecto: antes de escribir una función de utilidad, conviene preguntarse si ya existe en la biblioteca estándar. Casi siempre existe, casi siempre está mejor probada que la que uno escribiría, y usarla concentra el conocimiento en un único sitio en lugar de esparcirlo en copias. Aquí ensayamos ese reflejo con abs(n).

🧮 Modelo

Especificación y verificación en casos.json:

stdin esperado
-5 abs=5
3 abs=3
0 abs=0

📖 Definiciones y características

📐 Algoritmo (pseudocódigo neutral)

IMPORTAR abs de la biblioteca
LEER n ; ESCRIBIR "abs=" abs(n)

🌐 Implementaciones idiomáticas — el código a la vista

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 · python/main.py · python main.py

import sys

n = int(sys.stdin.readline())
print(f"abs={abs(n)}")

🧬 El mismo programa en la familia Scripting dinámico: Ruby · Perl · Lua · Tcl · R

JavaScript · javascript/main.mjs · node main.mjs

import { readFileSync } from "node:fs";

const n = parseInt(readFileSync(0, "utf8").trim(), 10);
console.log(`abs=${Math.abs(n)}`);

🧬 El mismo programa en la familia JavaScript / web: Dart · ActionScript

TypeScript · typescript/main.ts · pnpm exec tsx main.ts

import { readFileSync } from "node:fs";

const n: number = parseInt(readFileSync(0, "utf8").trim(), 10);
console.log(`abs=${Math.abs(n)}`);

🧬 El mismo programa en la familia JavaScript / web: Dart · ActionScript

Java · java/Main.java · java Main.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        long n = Long.parseLong(br.readLine().trim());
        System.out.println("abs=" + Math.abs(n));
    }
}

🧬 El mismo programa en la familia JVM: Kotlin · Scala · Groovy · Clojure

C# · csharp/Program.cs · dotnet run

using System;

long n = long.Parse(Console.In.ReadToEnd().Trim());
Console.WriteLine($"abs={Math.Abs(n)}");

🧬 El mismo programa en la familia .NET: F# · VB.NET

Go · go/main.go · go run main.go

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
)

func main() {
    line, _ := bufio.NewReader(os.Stdin).ReadString('\n')
    n, _ := strconv.ParseInt(strings.TrimSpace(line), 10, 64)
    if n < 0 {
        n = -n
    }
    fmt.Printf("abs=%d\n", n)
}

🧬 El mismo programa en la familia Sistemas: Zig · Nim · D

Rust · rust/main.rs · rustc main.rs -o main && ./main

use std::io::Read;

fn main() {
    let mut s = String::new();
    std::io::stdin().read_to_string(&mut s).unwrap();
    let n: i64 = s.trim().parse().unwrap();
    println!("abs={}", n.abs());
}

🧬 El mismo programa en la familia Sistemas: Zig · Nim · D

C · c/main.c · cc main.c -o main && ./main

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    long n;
    if (scanf("%ld", &n) != 1) return 1;
    printf("abs=%ld\n", labs(n));
    return 0;
}

🧬 El mismo programa en la familia C / llaves: C++ · Objective-C

SQL · sql/main.sql · sqlite3 :memory: < main.sql

-- SQL: abs() incorporado.
WITH nums(n) AS (VALUES (-5), (3), (0))
SELECT printf('abs=%d', abs(n)) AS resultado FROM nums;

🧬 El mismo programa en la familia Lógica y declarativa: Prolog · Datalog

PHP · php/main.php · php main.php

<?php
$n = (int) trim(fgets(STDIN));
echo "abs=" . abs($n) . "\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.

🔬 Ejemplo trabajado — del stdin a la salida

Sigamos el primer caso de casos.json (stdin = "-5", esperado = "abs=5") por tres lenguajes que exponen el valor absoluto en tres lugares distintos de su biblioteca estándar.

Python. Aquí no hace falta importar nada para el cálculo: abs es una función builtin, siempre disponible en el espacio global del lenguaje. El único import sys es para leer la entrada, no para el valor absoluto. n = int(sys.stdin.readline()) toma "-5\n" y produce n=-5; el f-string f"abs={abs(n)}" invoca la función global con el argumento -5, que devuelve 5, y print emite abs=5. Que abs sea global y no viva en math es una decisión de diseño de Python: las operaciones más universales se ponen al alcance sin ceremonia; las especializadas (sqrt, floor) sí requieren import math.

C. C es el contraste didáctico: aquí sí hay que declarar de dónde viene la función, y además elegir la variante correcta. La línea #include <stdlib.h> trae labs, que es el valor absoluto para long; existe también abs para int (en el mismo header) y fabs para double (en math.h). Con n=-5, labs(n) devuelve 5, y printf("abs=%ld\n", ...) lo imprime como abs=5. Nótese la disciplina que C exige: hay que importar el header adecuado y usar el nombre que corresponde al tipo, porque C no sobrecarga abs sobre distintos tipos numéricos como hacen los lenguajes más nuevos.

Rust. Rust ofrece el valor absoluto como un método del propio tipo entero: n.abs(), no abs(n). Con n de tipo i64 e igual a -5, la expresión n.abs() devuelve 5, y println!("abs={}", ...) produce abs=5. El use std::io::Read del principio importa el trait necesario para leer stdin, no el valor absoluto —abs viene incluido en el tipo entero sin importar nada—. Los tres llegan al mismo abs=5, pero por caminos que revelan la filosofía de cada biblioteca: función global sin importar (Python), función libre que exige el header y el nombre exactos (C), o método del propio valor (Rust).

🔬 Comparación

Lenguaje Cómo se obtiene el valor absoluto Import necesario
Python abs(n), función builtin global Ninguno (está siempre)
JavaScript Math.abs(n), del objeto global Math Ninguno (Math es global)
TypeScript Math.abs(n), igual que JavaScript Ninguno para Math
Java Math.abs(n), método estático de java.lang.Math java.lang es implícito
C# Math.Abs(n), del espacio System using System;
Go math.Abs es para float64; con enteros se usa un condicional import "math" (aquí, no)
Rust n.abs(), método del tipo entero Ninguno para abs
C abs/labs (entero) o fabs (flotante), según el tipo #include <stdlib.h> / <math.h>
SQL abs(n), función escalar incorporada Ninguno
PHP abs($n), función global Ninguno

La síntesis es doble. Primero, DRY: en los diez lenguajes el valor absoluto ya está resuelto, y escribir el propio sería reintroducir una copia de conocimiento que ya tiene su representación autorizada —justo lo que Hunt y Thomas advierten evitar—. Segundo, la organización interna de cada biblioteca revela una filosofía: unos lo ofrecen como función global sin fricción (Python, PHP, SQL), otros como miembro de un objeto o clase de matemáticas (JavaScript, Java, C#), otros como método del propio valor (Rust), y C obliga a elegir la variante exacta según el tipo. El caso de Go es el más instructivo: su math.Abs opera sobre float64, de modo que para enteros lo idiomático es un condicional propio —un recordatorio de que «está en la estándar» no siempre significa «para tu tipo exacto»—. Saber leer estas diferencias es parte de organizar un proyecto: elegir la herramienta correcta empieza por conocer dónde la guarda cada lenguaje.

🧬 El concepto en la familia

En Ruby, como en Rust, el valor absoluto es un método del número: n.abs, sin importar nada, porque los enteros son objetos con métodos. En Go, ya lo vimos, math.Abs está pensado para float64 y convertir un entero a flotante y de vuelta arriesga imprecisión en valores grandes, por lo que la comunidad prefiere un if n < 0 { n = -n } o una pequeña función propia para enteros. En Haskell, abs es un método de la clase de tipos Num, disponible para cualquier tipo numérico sin import explícito y sin sintaxis de método. Reconocer si un lenguaje ofrece la operación como función libre, método del valor o método de una clase de tipos aclara de inmediato cómo se escribirá la llamada.

✅ Prueba común

Los mismos casos para todas las implementaciones: casos.json. Verifica la equivalencia:

python scripts/verificar_equivalencia.py 088

🧪 Reto de transferencia

Detalle en reto.md.

⚠️ Errores comunes

❓ Preguntas frecuentes

🔗 Referencias

Libros de la parte:

Libros de los lenguajes del núcleo:


⏮️ Clase 087 · 📂 Parte · 📚 Índice · 🌐 Atlas · Clase 089 ⏭️