En Go, las funciones son valores de primera clase. Esto significa que puedes hacer con las funciones las mismas cosas que puedes hacer con cualquier otro valor: asignar funciones a variables, pasarlas como argumentos a otras funciones o incluso devolver funciones desde otras funciones.
A continuación creamos dos funciones, engGreeting y espGreeting, y las asignamos a la variable greeting:
import "fmt"
func engGreeting(name string) string {
return fmt.Sprintf("Hello %s, nice to meet you!", name)
}
func espGreeting(name string) string {
return fmt.Sprintf("¡Hola %s, mucho gusto!", name)
}
greeting := engGreeting // greeting is a variable of type func(string) string
fmt.Println(greeting("Alice")) // Hello Alice, nice to meet you!
greeting = espGreeting
fmt.Println(greeting("Alice")) // ¡Hola Alice, mucho gusto!
Los valores de función ofrecen la oportunidad de parametrizar funciones no solo con datos, sino también con comportamiento.
En el siguiente ejemplo, pasamos comportamiento a la función dialog a través del parámetro greetingFunc:
func dialog(name string, greetingFunc func(string) string) {
fmt.Println(greetingFunc(name))
fmt.Println("I'm a dialog bot.")
}
func espGreeting(name string) string {
return fmt.Sprintf("¡Hola %s, mucho gusto!", name)
}
greeting := espGreeting
dialog("Alice", greeting)
// =>
// ¡Hola Alice, mucho gusto!
// I'm a dialog bot.
El valor de una variable de tipo función sin inicializar es nil.
Por lo tanto, llamar a un valor de función nil provoca un pánico.
var dutchGreeting func(string) string
dutchGreeting("Alice") // panic: call of nil function
Los valores de función se pueden comparar con nil.
Esto puede ser útil para evitar pánicos innecesarios en el programa.
var dutchGreeting func(string) string
if dutchGreeting != nil {
dutchGreeting("Alice") // safe to call dutchGreeting
}
Usar valores de función es posible gracias a los tipos de función de Go.
Un tipo de función denota el conjunto de todas las funciones con la misma secuencia de tipos de parámetro y la misma secuencia de tipos de resultado.
Se pueden declarar tipos definidos por el usuario a partir de tipos de función.
Por ejemplo, la función dialog de los ejemplos anteriores se puede actualizar del siguiente modo:
type greetingFunc func(string) string
func dialog(name string, f greetingFunc) {
fmt.Println(f(name))
fmt.Println("I'm a dialog bot.")
}
Otra herramienta potente de la que disponemos gracias a la compatibilidad con las funciones de primera clase son las funciones anónimas.
Las funciones anónimas se definen en su punto de uso, sin un nombre que siga a la palabra clave func.
Estas funciones tienen acceso a las variables de la función que las contiene.
Por ejemplo:
func fib() func() int {
var n1, n2 int
return func() int {
if n1 == 0 && n2 == 0 {
n1 = 1
} else {
n1, n2 = n2, n1 + n2
}
return n2
}
}
next := fib()
for i := range N {
fmt.Printf("F%d\t= %4d\n", i, next())
}
Una llamada a fib declara las variables n1 y n2 y devuelve una función anónima que, a su vez, cambia los valores de estas variables cada vez que se llama a la función.
La n-ésima llamada de la función anónima devuelve el n-ésimo número de la sucesión de Fibonacci empezando por 0. La función anónima interna tiene acceso a las variables locales (n1 y n2) de la función fib que la contiene.
Esta es una forma estupenda de que los valores de función conserven estado entre llamadas.
Decimos que la función anónima es un closure de las variables n1 y n2. Los closures se usan mucho en programación y es posible que veas otros lenguajes que los admiten.
Bob es asesor financiero y ayuda a la gente a gestionar sus gastos. Los clientes de Bob le envían registros de gastos para que los analice. Bob tiene registros de periodos anteriores, así que puede ver los cambios en el gasto. A Bob no le gustan los calendarios y usa la época de Bob en lugar de fechas. La época de Bob es el número de días transcurridos desde que el cliente de Bob comenzó su actividad.
En este ejercicio vas a crear un programa que ayude a Bob a gestionar y analizar los gastos de sus clientes.
Bob trabaja con Record y DaysPeriod.
Un Record representa un registro de gastos que contiene el día en que se hizo el gasto, el dinero gastado y la categoría del gasto.
// Record represents an expense record.
type Record struct {
Day int
Amount float64
Category string
}
Un DaysPeriod representa un rango de días e incluye todos los días desde el día From hasta el día To.
Ambos extremos están incluidos en el rango.
// DaysPeriod represents a period of days.
type DaysPeriod struct {
From int
To int
}
p := DaysPeriod{From: 1, To: 31}
// p represents all days from the day 1 to the day 31:
// - days 1, 20, 16 and 31 are examples of days that are included
// in the range of time specified by p
// - days 50 and 40 are examples of days that are not included
// in the range of time specified by p
Bob maneja muchos registros cada día, pero no todos son interesantes según el análisis que esté haciendo. Ayudemos a Bob a hacer un filtrado básico de registros.
Implementa la función genérica Filter para filtrar registros según un criterio dado por una función.
Esta función de filtrado acepta una colección de registros y una función predicado, y devuelve solo los registros de la colección que cumplen el predicado.
records := []Record{
{Day: 1, Amount: 15, Category: "groceries"},
{Day: 11, Amount: 300, Category: "utility-bills"},
{Day: 12, Amount: 28, Category: "groceries"},
}
// Day1Records only returns true for records that are from day 1
func Day1Records(r Record) bool {
return r.Day == 1
}
Filter(records, Day1Records)
// =>
// [
// {Day: 1, Amount: 15, Category: "groceries"}
// ]
Bob necesita con frecuencia filtrar los registros que están en un periodo de tiempo determinado.
Implementa la función ByDaysPeriod que ayudará a Bob a crear dichos filtros.
Esta función acepta un DaysPeriod y devuelve una función que toma un registro y dice si el registro está en el periodo de tiempo especificado por el DaysPeriod dado como argumento.
records := []Record{
{Day: 1, Amount: 15, Category: "groceries"},
{Day: 11, Amount: 300, Category: "utility-bills"},
{Day: 12, Amount: 28, Category: "groceries"},
{Day: 26, Amount: 300, Category: "university"},
{Day: 28, Amount: 1300, Category: "rent"},
}
period := DaysPeriod{From: 1, To: 15}
Filter(records, ByDaysPeriod(period))
// =>
// [
// {Day: 1, Amount: 15, Category: "groceries"},
// {Day: 11, Amount: 300, Category: "utility-bills"},
// {Day: 12, Amount: 28, Category: "groceries"},
// ]
Además de filtrar registros por un periodo de tiempo, Bob también necesita filtrar registros por su categoría.
Implementa la función ByCategory que ayudará a Bob a crear dichos filtros.
Esta función acepta una categoría y devuelve una función que toma un registro y dice si la categoría de ese registro es la misma que la categoría dada como argumento.
records := []Record{
{Day: 1, Amount: 15, Category: "groceries"},
{Day: 11, Amount: 300, Category: "utility-bills"},
{Day: 12, Amount: 28, Category: "groceries"},
{Day: 28, Amount: 1300, Category: "rent"},
}
Filter(records, ByCategory("groceries"))
// =>
// [
// {Day: 1, Amount: 15, Category: "groceries"},
// {Day: 12, Amount: 28, Category: "groceries"},
// ]
Bob también quiere saber el importe total de los gastos en un periodo de tiempo.
Implementa la función TotalByPeriod para devolver una suma de los gastos en el periodo de días.
records := []Record{
{Day: 15, Amount: 16, Category: "entertainment"},
{Day: 32, Amount: 20, Category: "groceries"},
{Day: 40, Amount: 30, Category: "entertainment"}
}
p1 := DaysPeriod{From: 1, To: 30}
p2 := DaysPeriod{From: 31, To: 60}
TotalByPeriod(records, p1)
// => 16
TotalByPeriod(records, p2)
// => 50
Para los informes más complejos que Bob hace a sus clientes, Bob necesita filtrar registros por categoría y por periodo de tiempo a la vez. Es decir, Bob quiere saber los gastos totales de los registros de una categoría en un periodo de tiempo determinado.
Implementa la función CategoryExpenses que devuelve el importe total de los gastos de una categoría en un periodo de días determinado.
La función también debe distinguir el caso en el que la categoría dada no está presente en los registros de gastos y el caso en el que no hay gastos de esa categoría en el periodo proporcionado.
Cuando la categoría no es la categoría de ninguno de los registros (independientemente del periodo de tiempo), la función debe devolver un error.
p1 := DaysPeriod{From: 1, To: 30}
p2 := DaysPeriod{From: 31, To: 60}
records := []Record{
{Day: 1, Amount: 15, Category: "groceries"},
{Day: 11, Amount: 300, Category: "utility-bills"},
{Day: 12, Amount: 28, Category: "groceries"},
{Day: 26, Amount: 300, Category: "university"},
{Day: 28, Amount: 1300, Category: "rent"},
}
CategoryExpenses(records, p1, "entertainment")
// => 0, error(unknown category entertainment)
CategoryExpenses(records, p1, "rent")
// => 1300, nil
CategoryExpenses(records, p2, "rent")
// => 0, nil
Regístrate en Exercism para aprender y dominar Go con 34 conceptos165 ejercicios y mentoría humana real, todo gratis.