Clase 075 — Argumentos nombrados y de palabra clave

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


🎯 Objetivo

Aprender a pasar los argumentos diciendo a qué parámetro corresponde cada uno, en lugar de confiar en el orden en que van escritos. En la clase 073 vimos que en una llamada posicional «el orden es el significado»: restar(a, b) no es restar(b, a). El argumento nombrado rompe esa dependencia. Escribir punto(x=3, y=4) deja explícito que el 3 es la abscisa y el 4 la ordenada, y de paso permite reordenarlos —punto(y=4, x=3) significa lo mismo—. Lo que se gana no es potencia de cálculo sino claridad en el punto exacto donde más se necesita: la línea que invoca la función, que es la que un futuro lector encuentra sin ver la firma.

El motivo profundo es de legibilidad, y Robert Martin lo pone en el centro de Clean Code (cap. 3, «Functions»): los argumentos que más confunden son los que no dicen nada por sí mismos en la llamada. Su ejemplo clásico es el argumento booleano suelto —render(true), ¿qué es true?— que obliga a saltar a la definición para descifrarlo. El argumento nombrado ataca ese problema de raíz: crear(ancho=800, alto=600) no necesita comentario ni memoria; la llamada se documenta a sí misma. Cuando una función tiene varios parámetros del mismo tipo —dos enteros, dos cadenas—, nombrarlos es la diferencia entre una llamada que se lee y una que se adivina.

Como en la clase anterior, el reparto entre lenguajes es desigual y conviene tenerlo claro desde el principio. Python y C# los soportan de forma nativa; PHP los añadió en su versión 8; Kotlin, Ruby y Swift los tienen. Pero Java, Go, C y Rust no ofrecen argumentos nombrados en la firma, y cada uno recurre a un sustituto: Java se queda con el orden posicional, Go y Rust usan structs con campos nombrados, y en JavaScript el idioma equivalente —tan extendido que se siente nativo— es pasar un único objeto de opciones cuyas claves hacen las veces de nombres.

🧩 Situación

Imagina una función que crea una ventana: crear(800, 600, true, false). En la llamada, esos cuatro valores son un jeroglífico. ¿Es 800 el ancho o el alto? ¿Qué activa el primer true? Quien lee esta línea en una revisión de código no tiene forma de saberlo sin abrir la definición, y ahí es donde se cuelan los bugs: alguien escribe crear(600, 800, ...) con las dimensiones invertidas y el programa compila tan feliz porque ambos son enteros. Con argumentos nombrados la misma llamada se vuelve crear(ancho=800, alto=600, visible=true, modal=false) y el jeroglífico desaparece: cada valor lleva pegada su intención. Es la clase de claridad que no cuesta nada en tiempo de ejecución y ahorra horas de depuración. En esta clase practicamos la versión mínima de esa idea, construyendo punto(x=a, y=b), dos parámetros del mismo tipo donde nombrar es exactamente lo que evita confundir la abscisa con la ordenada.

🧮 Modelo

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

stdin esperado
3 4 punto(x=3, y=4)
0 -2 punto(x=0, y=-2)
5 5 punto(x=5, y=5)

📖 Definiciones y características

📐 Algoritmo (pseudocódigo neutral)

LEER a, b
ESCRIBIR punto(x=a, y=b)

🌐 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


def punto(x, y):
    return f"punto(x={x}, y={y})"


a, b = map(int, sys.stdin.readline().split())
print(punto(x=a, y=b))

🧬 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";

// JS simula argumentos nombrados con un objeto.
function punto({ x, y }) {
  return `punto(x=${x}, y=${y})`;
}

const [a, b] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);
console.log(punto({ x: a, y: b }));

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

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

import { readFileSync } from "node:fs";

function punto({ x, y }: { x: number; y: number }): string {
  return `punto(x=${x}, y=${y})`;
}

const [a, b]: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);
console.log(punto({ x: a, y: b }));

🧬 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 {
    // Java no tiene argumentos nombrados: se pasan por posición.
    static String punto(int x, int y) {
        return "punto(x=" + x + ", y=" + y + ")";
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] p = br.readLine().trim().split("\\s+");
        System.out.println(punto(Integer.parseInt(p[0]), Integer.parseInt(p[1])));
    }
}

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

C# · csharp/Program.cs · dotnet run

using System;

string Punto(int x, int y) => $"punto(x={x}, y={y})";

string[] p = Console.In.ReadToEnd()
    .Split(new[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
Console.WriteLine(Punto(x: int.Parse(p[0]), y: int.Parse(p[1])));

🧬 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"
)

// Go no tiene argumentos nombrados: se usan structs con campos nombrados.
type Punto struct {
    X, Y int
}

func (p Punto) String() string {
    return fmt.Sprintf("punto(x=%d, y=%d)", p.X, p.Y)
}

func main() {
    line, _ := bufio.NewReader(os.Stdin).ReadString('\n')
    f := strings.Fields(line)
    a, _ := strconv.Atoi(f[0])
    b, _ := strconv.Atoi(f[1])
    fmt.Println(Punto{X: a, Y: b})
}

🧬 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 punto(x: i64, y: i64) -> String {
    format!("punto(x={x}, y={y})")
}

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();
    println!("{}", punto(v[0], v[1]));
}

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

C · 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;
    /* C no tiene argumentos nombrados: posicionales. */
    printf("punto(x=%ld, y=%ld)\n", a, b);
    return 0;
}

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

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

-- SQL nombra columnas, análogo a nombrar argumentos.
WITH puntos(x, y) AS (VALUES (3, 4), (0, -2), (5, 5))
SELECT printf('punto(x=%d, y=%d)', x, y) AS resultado FROM puntos;

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

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

<?php
function punto($x, $y) {
    return "punto(x=$x, y=$y)";
}

[$a, $b] = preg_split('/\s+/', trim(fgets(STDIN)));
// PHP 8 admite argumentos nombrados.
echo punto(x: (int) $a, y: (int) $b) . "\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 = "3 4", esperado = "punto(x=3, y=4)") a través de tres lenguajes que resuelven de forma muy distinta el problema de «etiquetar» cada valor.

Python (nombrado nativo). La línea a, b = map(int, sys.stdin.readline().split()) lee "3 4", lo parte en ["3", "4"], lo convierte a enteros y desempaqueta a=3, b=4. Entonces la llamada punto(x=a, y=b) es la clave de la clase: no pasa a y b por posición, sino nombrando los parámetros de destino. El intérprete empareja x con el valor de a (que es 3) y y con el de b (que es 4), ejecuta el f-string f"punto(x={x}, y={y})" y produce punto(x=3, y=4). Como el emparejamiento es por nombre, punto(y=b, x=a) daría exactamente el mismo resultado: el orden en la llamada ya no manda.

Go (struct con campos nombrados). Go no tiene argumentos nombrados, y su sustituto es de otra naturaleza: en vez de nombrar en la llamada a una función, se define un tipo Punto struct { X, Y int } y se construye con un literal cuyos campos sí llevan nombre. Tras parsear a=3, b=4, la línea fmt.Println(Punto{X: a, Y: b}) crea el valor con X: 3 y Y: 4. El método String() definido sobre Punto es lo que fmt.Println invoca en silencio para obtener el texto punto(x=3, y=4). La claridad se logra, pero se ha desplazado del sitio de llamada al literal de struct: son los campos X: e Y:, no unos parámetros, los que llevan el nombre.

JavaScript (objeto de opciones). JavaScript tampoco tiene argumentos nombrados, pero su idioma es tan común que casi no se nota: la función se declara como function punto({ x, y }), desestructurando un objeto en el propio parámetro. Tras leer a=3, b=4, la llamada punto({ x: a, y: b }) pasa un único objeto { x: 3, y: 4 }; la desestructuración { x, y } extrae sus claves en variables locales y el template string produce punto(x=3, y=4). Como las claves del objeto no dependen del orden, punto({ y: b, x: a }) sería equivalente —el objeto emula justamente la libertad de orden que Python obtiene con nombres reales—.

🔬 Comparación

Clase de diferencia Observación entre lenguajes
Sintáctica Nombre directo en la llamada —punto(x=a, y=b) (Python), Punto(x: ..., y: ...) (C#), punto(x: ..., y: ...) (PHP 8)— frente a posicional puro (Java, C, Rust) o literal de struct/objeto (Go, JS).
Semántica Con nombres, el orden de los argumentos es libre y el emparejamiento es por identidad; sin ellos, la posición es el significado y equivocar el orden compila sin queja cuando los tipos coinciden.
Semántica Python distingue además el paso de una struct/objeto (un solo valor con campos) del paso por palabra clave (varios argumentos etiquetados); Go y JS solo tienen la primera vía, y por eso «nombran el dato», no «nombran el argumento».
Paradigmática Rust no tiene argumentos nombrados; el idioma es un struct con campos nombrados, a menudo construido con el patrón builder cuando hay muchas opciones.
Paradigmática SQL nombra columnas por naturaleza: SELECT ... x, y etiqueta cada dato de la fila, un paralelo declarativo de nombrar argumentos.

La síntesis vuelve a Clean Code: el valor del argumento nombrado no está en la máquina —al procesador le da igual— sino en el ser humano que lee la llamada meses después. Que Python lo consiga con palabras clave reales y JavaScript con un objeto de opciones es, para ese lector, casi lo mismo: en ambos casos cada valor llega con su etiqueta puesta. La diferencia práctica aparece cuando hay muchos parámetros opcionales del mismo tipo; ahí el lenguaje que obliga a posición pura (Java, C) es el que más se beneficiaría de nombres y el que, careciendo de ellos, empuja hacia objetos y structs contenedores.

🧬 El concepto en la familia

En Ruby los argumentos de palabra clave son de primera clase: def punto(x:, y:) los declara y punto(x: a, y: b) los pasa, con orden libre y hasta obligatorios si se quiere. Kotlin permite punto(x = a, y = b) y combina los nombres con los defectos de la clase 074 para saltarse argumentos intermedios sin ambigüedad. Swift lleva las etiquetas al extremo: por defecto exige el nombre en la llamada —punto(x: a, y: b)— salvo que el diseñador lo suprima con _, de modo que la legibilidad es la norma y no la excepción. Scala también los soporta y, como Kotlin, los mezcla con parámetros por defecto. Saber si un lenguaje trae nombres nativos, los exige o te obliga a un objeto contenedor te dice cuánta ceremonia necesitarás para que una llamada con muchos parámetros siga siendo legible.

⚠️ Errores comunes

❓ Preguntas frecuentes

✅ Prueba común

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

python scripts/verificar_equivalencia.py 075

🧪 Reto de transferencia

Detalle en reto.md.

🔗 Referencias

Libros de la parte:

Libros de los lenguajes del núcleo:


⏮️ Clase 074 · 📂 Parte · 📚 Índice · 🌐 Atlas · Clase 076 ⏭️