SkillGridAprende informática practicando

Funciones: una caja con nombre y una sola salida

Argumentos, valores de retorno, alcance y efectos secundarios — y por qué una caja del tamaño de una frase es la única que de verdad puedes probar.

14 min · 45 XP

Has escrito las mismas tres líneas dos veces, en dos sitios, con un número cambiado. Luego arreglas un fallo en una copia y te olvidas de la otra. Una función existe para evitar esto. Es una caja con nombre: metes valores, sale un valor, y el contenido lo escribes una sola vez.

Piénsalo como una máquina con ranuras. Las entradas se llaman parámetros cuando defines la caja y argumentos cuando las rellenas. La salida es lo que devuelve return. Nada más cruza la pared: la caja no puede ver el resto de tu programa salvo lo que tú le entregaste.

        inputs            box             output
     price, count  -->  totalPrice  -->  a number

     price = 2.50, count = 4   -->   10.00Dos entradas, una salida. La pared del medio es el sentido de toda la idea.

La misma caja en tres lenguajes: precio por cantidad.

def total_price(price, count):
    return price * count

total = total_price(2.50, 4)Python: def, dos puntos y el cuerpo con sangría.
function totalPrice(price, count) {
  return price * count;
}

const total = totalPrice(2.50, 4);JavaScript: function, llaves, y return con punto y coma.
func totalPrice(price float64, count int) float64 {
	return price * float64(count)
}

total := totalPrice(2.50, 4)Go: func, el tipo de cada entrada, y el tipo de la salida después del paréntesis de cierre.

Los argumentos se emparejan con los parámetros por posición, no por significado. El lenguaje no tiene ni idea de que el primer parámetro se llamaba price; solo sabe que el primer argumento va en la primera ranura.

total_price(2.50, 4)      arguments in the call
2.50  ->  price           parameters in the definition
4     ->  count

total_price(4, 2.50)      same shape, different meaningLos mismos dos números, intercambiados. Las dos líneas se ejecutan. Solo una es correcta.

Las funciones pequeñas son fáciles de probar, y esa es la razón práctica para escribirlas. En dev.testing viste que una prueba es una afirmación sobre lo que hace el código. Una función con dos entradas y una salida hace que esa afirmación sea trivial de escribir, porque no hay nada más que preparar: ni base de datos, ni navegador, ni orden de acontecimientos.

def discount(total):
    if total >= 20:
        return total * 0.9
    return total

assert discount(20) == 18.0
assert discount(10) == 10.0Dos líneas describen toda la función. Sin preparativos, sin estado, sin sorpresas.

Así que la regla no es estética. Una función que ocupa cuarenta líneas y lee tres variables globales no se puede probar sin construir medio mundo a su alrededor, así que nadie la prueba, así que se rompe. Una caja del tamaño de una frase se puede comprobar en una línea y arreglar en un minuto.

maintotalPriceresultprice = 2.50, count = 4price * count10.00total = 10.00main keeps running, the box is gone

El alcance es la respuesta a qué puede ver un nombre. Cuando llamas a una función, esta recibe su propia habitación pequeña. Los nombres creados dentro de esa habitación solo existen mientras la llamada se ejecuta, y desaparecen cuando return devuelve la respuesta. La función puede leer nombres de fuera, pero el exterior no puede ver el interior.

const rate = 0.21;

function vat(net) {
  const inner = net * rate;
  return inner;
}

vat(100)
innerrate se ve dentro de la función. inner no se ve fuera de ella.

Por último, una palabra honesta sobre los efectos secundarios. Una función pura solo mira sus argumentos y solo produce su valor de retorno. Llámala mil veces con los mismos argumentos y obtendrás mil veces la misma respuesta. Una función impura además cambia algo fuera de sí misma: imprime, escribe un archivo, envía una petición, actualiza un contador.

pure:      def vat(net): return net * 0.21
           vat(100) always 21.0

impure:    def vat(net):
               print("calculating")
               total = net * 0.21
               save_to_file(total)
               return totalLa primera puedes predecirla. La segunda solo puedes ejecutarla y esperar.

Práctica

  1. Se supone que una función te da el precio con impuestos. Imprime el número correcto, pero quien la llama no recibe nada de vuelta. ¿Cuál es el error?

    Pistas
    • Una caja devuelve un valor; imprimir un valor y devolverlo son dos acciones distintas.
    • Pregúntate qué recibe quien llama. Si la función muestra la respuesta en pantalla y no entrega nada, quien llama no recibe nada.
  2. Relaciona cada función con lo que le hace al mundo exterior a ella misma.

    Pistas
    • Una función pura es una caja que puedes predecir solo con sus entradas.
    • Los efectos secundarios no son malvados: un programa que nunca escribe ni imprime nada no hace nada útil. Lo importante es darse cuenta de ellos.
  3. Ordena los momentos de una llamada a una función, desde la llamada hasta la línea siguiente.

    1. 1Empieza la llamada y los argumentos aterrizan en las ranuras de los parámetros
    2. 2El cuerpo se ejecuta y lee sus propios nombres
    3. 3return entrega un solo valor de vuelta a quien llamó
    4. 4Quien llama calcula los valores de los argumentos
    5. 5La función se detiene y quien llamó continúa con la línea siguiente
    Pistas
    • La llamada no puede hacer su trabajo antes de que los argumentos tengan un valor.
    • Los nombres dentro de la caja se crean cuando empieza la llamada y desaparecen cuando return devuelve la respuesta.