Esta pagina se ve mejor con JavaScript habilitado

Go first class functions

 ·  🎃 kr0m

En Go que las funciones sean de primera clase significa que es posible:

  • Asignación a variables: Las funciones pueden ser asignadas a variables.
  • Pasar como argumentos: Las funciones pueden ser pasadas como argumentos a otras funciones.
  • Retorno de funciones: Las funciones pueden ser retornadas desde otras funciones.
  • Almacenamiento en estructuras: Las funciones pueden ser almacenadas en estructuras de datos como slices, mapas, etc.

Este artículo es bastante extenso así que lo he organizado de la siguiente manera:


Si eres nuevo en el mundo de Go te recomiendo los siguientes artículos anteriores:


Anonymous functions:

En este ejemplo creamos una función anónima y la asignamos a la variable a. Esto puede resultar realmente útil en algunos tests unitarios donde es necesario “mockear” la salida de algunas funciones.

package main

import (
    "fmt"
)

func main() {
    a := func() {
        fmt.Println("Hello world first class function")
    }

    a()
    fmt.Printf("%T\n", a)
}
Hello world first class function
func()

Tipos de funciones personalizados:

Es posible definir un tipo nuevo de función, para luego crear una variable de este tipo, esto evita errores de programación ya que la variable tendrá que cumplir con el tipo o no podrá ser asignada, me recuerda un poco a los filtros impuestos por las interfaces.

package main

import (
    "fmt"
)

// Custom function type definition
type add func(a int, b int) int

func main() {
    // variable of defined custom type
    var a add = func(a int, b int) int {
        return a + b
    }

    s := a(5, 6)
    fmt.Println("Sum", s)
}
Sum 11

Higher-order functions:

Una función de orden superior es aquella que tiene una o mas funciones como entrada y/o valor de retorno.

En este ejemplo vemos como la función simple() acepta una función como parámetro de entrada, esta función será asignada a la variable f pudiendo utilizar dicha variable como función. Por otro lado en la función main() definimos una función anónima que asignamos a la variable f para acto seguido llamar a la función simple(f).

package main

import (
    "fmt"
)

func simple(f func(a, b int) int) {
    fmt.Println(f(60, 7))
}

func main() {
    f := func(a, b int) int {
        return a + b
    }
    simple(f)
}
67

Retornar una función es muy similar a permitir funciones como parámetros de entrada, en la función simple() se indica los parámetros de entrada y retorno de la función que retornaremos, dentro de simple() creamos una función anónima y la asignamos a una variable, finalmente retornamos dicha variable. En la función main() tan solo debemos asignar el valor de retorno de simple() a una variable y luego utilizarla como función.

package main

import (
	"fmt"
)

func simple() func(a, b int) int {
    f := func(a, b int) int {
        return a + b
    }
    return f
}

func main() {
    f := simple()
    fmt.Println(f(60, 7))
}
67

Closures:

En Go, un closure es una función que referencia variables fuera de su propio alcance, estas variables externas son capturadas por la función, lo que significa que la función recuerda el entorno en el que fue creada, incluso después de que el entorno haya terminado. Un closure es útil cuando necesitas una función que mantenga su propio estado interno a lo largo de múltiples invocaciones.

En este ejemplo podemos ver que la función anónima asignada a la variable c hace uso de la variable t que fué definida fuera del alcance de la función, al retornar la variable c estamos retornando la función mas un puntero a la variable t.

func appendStr() func(string) string {
    t := "Hello"
    c := func(b string) string {
        t = t + " " + b
        return t
    }
    return c
}

Podemos ver un ejemplo entero aquí.

package main

import (
    "fmt"
)

func appendStr() func(string) string {
    t := "Hello"
    c := func(b string) string {
        t = t + " " + b
        return t
    }
    return c
}

func main() {
    a := appendStr()
    fmt.Println(a("World"))
}
Hello World

Los cambios que se hagan sobre esa variable serán recordados por la función anónima, es decir t no valdrá siempre “Hello”, conforme se vaya alterando su valor, irá cambiando. En este ejemplo hacemos una segunda llamada y vemos que los cambios sobre la variable t se van acumulando.

package main

import (
    "fmt"
)

func appendStr() func(string) string {
    t := "Hello"
    c := func(b string) string {
        t = t + " " + b
        return t
    }
    return c
}

func main() {
    a := appendStr()
    fmt.Println(a("World"))
    fmt.Println(a("Gophers"))
}
Hello World
Hello World Gophers

Ejemplo práctico:

En este ejemplo tenemos una función lamada filter() que espera como parámetros de entrada un slice de alumnos y una función con la que comprobar si un alumno es válido, además retornará un slice de alumnos con los alumnos aptos.
En la función main() generamos el slice de alumnos y la función con la que comprobar si un alumno es válido para finalmente llamar a la función filter() con dichos parámetros.

package main

import (
    "fmt"
)

type student struct {
    firstName string
    lastName  string
    grade     string
    country   string
}

func filter(students []student, checkStudent func(student) bool) []student {
    var validStudents []student
    for _, student := range students {
        if checkStudent(student) == true {
            validStudents = append(validStudents, student)
        }
    }
    return validStudents
}

func main() {
    student1 := student{
        firstName: "Naveen",
        lastName:  "Ramanathan",
        grade:     "A",
        country:   "India",
    }

    student2 := student{
        firstName: "Samuel",
        lastName:  "Johnson",
        grade:     "B",
        country:   "USA",
    }

    studentSlice := []student{student1, student2}

    checkStudent := func(student student) bool {
        if student.grade == "B" {
            return true
        }
        return false
    }

    filteredStudents := filter(studentSlice, checkStudent)
    fmt.Println(filteredStudents)
}
[{Samuel Johnson B USA}]
Si te ha gustado el artículo puedes invitarme a un RedBull aquí