Declaración de funciones

Encuentra el código de la lección:

Abstract

Las funciones son una herramienta esencial para construir software reutilizable, legible y organizado. Kotlin facilita su declaración mediante una sintaxis concisa y expresiva, que incorpora características como parámetros con valores por defecto, funciones de una sola expresión e inferencia de tipos.

En esta lección aprenderás a declarar funciones en Kotlin, comprender cómo utilizar vararg para aceptar múltiples argumentos y cuándo conviene especificar tipos de retorno. También explorarás funciones estándar como map y fold , y conocerás las diferencias entre colecciones como Array y List .

No es necesario dominar todos los detalles desde el principio: a medida que avancemos en el curso, retomaremos y profundizaremos los conceptos más relevantes.

Funciones en Kotlin

Una función en Kotlin es un bloque de código reutilizable que encapsula una tarea específica. Su sintaxis básica es la siguiente:

Estructura general
fun functionName(param1: Type1, param2: Type2 = defaultValue, ...): ReturnType {
    // Cuerpo de la función
    return result
}

Explicación de la sintaxis

  • fun : Palabra clave para declarar una función.
  • functionName : Nombre que identifica a la función; debe ser claro y representativo de su propósito.
  • Parámetros ( param1 , param2 ): Lista de argumentos con sus respectivos tipos.
    • Una función puede no tener parámetros.
    • Es posible asignar valores por defecto, lo que permite omitir argumentos al llamarla.
  • ReturnType : Tipo del valor que retorna la función.
    • Si no devuelve un valor significativo, se utiliza Unit , aunque puede omitirse por ser implícito.
  • Cuerpo de la función: Conjunto de instrucciones que se ejecutan cuando se llama.

Ejemplo: Sumar dos números

Supongamos que queremos crear una función que sume dos números enteros. La declaración básica sería:

Versión con cuerpo de bloque
functions/src/main/kotlin/cl/ravenhill/math/add.kt
fun add(a: Int, b: Int): Int {
    return a + b
}

Si la función contiene solo una expresión, puedes escribirla de forma más concisa utilizando asignación directa:

Versión con asignación directa y tipo explícito
functions/src/main/kotlin/cl/ravenhill/math/add.kt
fun add(a: Int, b: Int): Int = a + b

Kotlin incluso permite inferir el tipo de retorno automáticamente:

Versión con inferencia de tipo de retorno
functions/src/main/kotlin/cl/ravenhill/math/add.kt
fun add(a: Int, b: Int) = a + b

Este estilo hace que el código sea más breve, pero conviene usarlo con moderación: puede dificultar la lectura si el tipo de retorno no es evidente, y volver el código más frágil frente a cambios accidentales.

Si vienes de Scala...

En Kotlin, la palabra clave return es obligatoria cuando utilizas el bloque clásico { ... } . Esto contrasta con Scala, donde la última expresión de una función se retorna automáticamente sin necesidad de usar return .

En Kotlin, ese comportamiento implícito solo se aplica cuando se utiliza la sintaxis de una sola expresión ( = ... ).

Inferencia de tipos

Kotlin es un lenguaje con inferencia de tipos, lo que significa que el compilador puede deducir el tipo de una variable o expresión a partir del contexto. En el ejemplo anterior, dado que la función contiene una sola expresión, el tipo de retorno se infiere automáticamente.

No abuses de la inferencia

Aunque la inferencia puede hacer el código más limpio y conciso, no siempre es recomendable omitir los tipos. En funciones públicas, con lógica compleja o que formen parte de una API, declarar el tipo explícitamente mejora la legibilidad, actúa como documentación autoexplicativa y facilita la evolución del código a largo plazo.

Estilo de nombres

En Kotlin, las funciones y variables deben nombrarse usando la convención camelCase. Esto implica lo siguiente:

  • El nombre comienza con una letra minúscula.
  • Cada palabra siguiente se escribe sin espacios, iniciando con mayúscula.

Ejemplos correctos:

  • calculateTotal
  • printMessage
  • main

Usar un estilo de nombres consistente mejora la legibilidad y asegura que tu código esté alineado con las prácticas idiomáticas de Kotlin.

Estilos incorrectos

Evita estilos heredados de otros lenguajes o que no son válidos en Kotlin:

  • CalculateTotal PascalCase, reservado para clases y tipos.
  • calculate_total snake_case, típico en Python, no se utiliza en Kotlin.
  • calculate-total kebab-case, inválido como identificador.
  • CALCULATE_TOTAL UPPER_SNAKE_CASE, reservado para constantes y valores inmutables en tiempo de compilación.

Funciones variádicas ( vararg )

En Kotlin puedes definir funciones que aceptan una cantidad variable de argumentos usando la palabra clave vararg . Esto permite invocar la función con cero, uno o más valores del mismo tipo, de manera similar a *args en Python o ...args en JavaScript.

Ejemplo: sumar múltiples números
functions/src/main/kotlin/cl/ravenhill/math/sum.kt
fun sumAll(vararg nums: Int): Int =
    nums.sum()

sum()

La función sum() es una extensión que suma todos los elementos de una colección o arreglo.

En el caso de IntArray , su firma es:

fun IntArray.sum(): Int

Esto equivale a realizar: nums[0] + nums[1] + ... + nums[n-1] .

También existen variantes para otros tipos de colecciones numéricas:

fun Array<out Int>.sum(): Int
fun Iterable<Double>.sum(): Double
// ...
  • Array<out T> es una colección covariante, lo que significa que puedes pasar cualquier subtipo de T sin modificar la colección.
  • Las versiones para List , Set y otros Iterable<T> funcionan del mismo modo.
Invocación de la función
functions/src/main/kotlin/cl/ravenhill/math/sum.kt
sumAll(1, 2, 3, 4)  // devuelve 10
sumAll()            // devuelve 0

Si ya tienes un arreglo existente, puedes desempaquetar sus elementos usando el prefijo * al pasarlo como argumento:

Desempaquetar un arreglo
functions/src/main/kotlin/cl/ravenhill/math/sum.kt
val extras = intArrayOf(5, 6)
sumAll(1, 2, *extras) // devuelve 14

* solo funciona con arreglos

El operador * para desempaquetar argumentos funciona exclusivamente con arreglos ( Array o tipos primitivos como IntArray ). Si tienes una List , primero debes convertirla a un arreglo usando list.toTypedArray() :

val extrasList = listOf(5, 6) 
sumAll(1, 2, *extrasList.toTypedArray()) // devuelve 14

Ten en cuenta que toTypedArray() crea una copia completa de la lista en memoria. En colecciones grandes, esto implica una asignación y recorrido adicionales, lo que puede afectar el rendimiento y el consumo de memoria.

Hack: Uno o más argumentos

Si necesitas que una función reciba al menos un argumento obligatorio, declara ese argumento como un parámetro normal y colócalo antes de un vararg .

Una forma sencilla de recordarlo es compararlo con las expresiones regulares:

  • representa “cero o más repeticiones”
  • representa “una o más repeticiones”

En este contexto:

  • El parámetro obligatorio equivale a + .
  • El vararg equivale a * .

Combinados, expresan la idea de “uno o más argumentos”.

Al menos un argumento requerido
functions/src/main/kotlin/cl/ravenhill/math/sum.kt
fun sumTo(first: Int, vararg rest: Int): Int =
    rest.fold(first) { acc, i -> acc + i }

fold()

La función fold() es una extensión que aplica una operación acumulativa sobre los elementos de una colección, comenzando desde un valor inicial.

En este caso, para IntArray :

fun IntArray.fold(initial: Int, operation: (acc: Int, Int) -> Int): Int

Esto equivale a: initial + this[0] + this[1] + ... .

fold() también se puede usar con:

  • Arreglos primitivos ( DoubleArray , FloatArray , etc.)
  • Arreglos genéricos ( Array<T> )
  • Colecciones ( List<T> , Set<T> , etc.)

Versiones generalizadas:

fun <T, R> Array<out T>.fold(initial: R, operation: (acc: R, T) -> R): R
fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R

Aquí R es el tipo del acumulador, T el tipo de los elementos, y out indica covarianza: puedes pasar subtipos de T como elementos.

Ejemplos de uso
functions/src/main/kotlin/cl/ravenhill/math/sum.kt
sumTo(1)                                        // devuelve 1
sumTo(1, 2, 3, 4)                               // devuelve 10
sumTo(first = 1, rest = intArrayOf(2, 3, 4))    // devuelve 10

Este patrón también sirve para funciones con dos o más parámetros obligatorios, seguidos por una cantidad variable de argumentos opcionales.

vararg vs. Array

En Kotlin, vararg es una forma conveniente de permitir que una función reciba una cantidad variable de argumentos. Internamente, el compilador convierte esos argumentos en un arreglo ( Array<T> , IntArray , etc.).

Esto significa que:
  • Usar vararg es
    azúcar sintáctico[*]
    sobre una función que acepta un arreglo.
  • Puedes seguir llamando a la función con un arreglo existente usando el operador * para desempaquetarlo.
Usando vararg vs. pasando un arreglo
functions/src/main/kotlin/cl/ravenhill/math/sum.kt
fun sumAll(vararg nums: Int): Int = nums.sum()

val valores = intArrayOf(1, 2, 3)
sumAll(*valores)  // ✅ correcto: desempaquetado con *            

Kotlin optimiza los tipos primitivos ( Int , Double , etc.) usando IntArray , DoubleArray , etc., en lugar de Array<Int> o Array<Double> . Esto mejora el rendimiento y evita el boxing innecesario.

Array<T> vs. List<T>

Tanto Array<T> como List<T> representan colecciones de tamaño fijo e inmutables en cuanto a estructura (no puedes agregar ni quitar elementos). Sin embargo, tienen propósitos diferentes: un Array<T> es mutable en sus elementos y está más orientado a la eficiencia y la interoperabilidad con Java, mientras que List<T> es más expresiva y está pensada para un estilo idiomático y funcional.

  • Array<T> es una estructura de bajo nivel, más cercana al funcionamiento de los arreglos en lenguajes como Java.
  • List<T> es parte de la API de colecciones de Kotlin, más expresiva y flexible para la programación funcional.
Comparativa entre Array<T> y List<T> en Kotlin
Característica Array<T> List<T>
Mutable Sí ( array[i] = ... ) No (estructura inmutable, pero sus elementos pueden ser mutables)
Tamaño fijo Sí ( List , no en MutableList )
Posiciones accesibles Por índice ( array[i] ) Por índice ( list[i] )
Métodos funcionales Limitados Amplia API funcional
Uso común Eficiencia, interoperabilidad con Java Estilo idiomático, programación funcional
Conversión array.toList() list.toTypedArray()

En resumen

  • Usa List<T> para la mayoría de los casos, especialmente si buscas inmutabilidad lógica y una API más rica y expresiva para manipulación de datos.
  • Usa Array<T> cuando necesites interoperar con código Java o cuando el rendimiento en acceso/modificación por índice sea crítico.

Ejercicio: Duplicar niveles de poder

Vamos a practicar la declaración de funciones en Kotlin aplicando una transformación sobre una lista. Supón que tienes una lista de niveles de poder ( Int ) y quieres duplicar cada uno de ellos usando map() .

Como recordatorio, map() aplica una función a cada elemento de una colección y devuelve una nueva colección con los resultados:

Firma de map()
fun <T, R> List<T>.map(transform: (T) -> R): List<R>
fun <T, R> Array<out T>.map(transform: (T) -> R): List<R>

Tu tarea es definir una función llamada doublePowers que reciba una lista de enteros ( List<Int> ) o bien una cantidad variable de argumentos ( vararg ) y devuelva una nueva lista con cada valor duplicado.

Ejemplos de uso

Ambas versiones son equivalentes en funcionalidad, pero difieren en cómo se pasan los argumentos al invocar la función.

Solución

Conclusiones

En esta lección aprendimos la sintaxis básica para declarar funciones en Kotlin, un componente esencial para escribir código reutilizable, expresivo y mantenible. También exploramos variantes idiomáticas como la inferencia de tipos, las funciones variádicas y las diferencias clave entre colecciones como Array<T> y List<T> .

Puntos clave

  • Las funciones se declaran con la palabra clave fun y pueden tener parámetros con valores por defecto.
  • El tipo de retorno puede inferirse automáticamente si la función se reduce a una sola expresión.
  • vararg permite definir funciones que aceptan una cantidad variable de argumentos.
  • Es posible requerir al menos un argumento junto a vararg , combinándolos en la firma.
  • Array<T> y List<T> parecen similares, pero tienen diferencias clave en mutabilidad y uso.
  • Kotlin favorece un estilo conciso, pero también explícito y claro cuando la función es pública o compleja.

¿Qué nos llevamos?

Esta lección fue tu primer paso en el sistema de funciones de Kotlin.

No es necesario memorizar cada detalle ahora: usa este material como referencia rápida cuando lo necesites.

Retomaremos estos conceptos más adelante, profundizando según lo requiera cada unidad.