Em Go, as funções são valores de primeira classe. Isto significa que podes fazer com as funções as mesmas coisas que fazes com todos os outros valores: atribuir funções a variáveis, passá-las como argumentos a outras funções ou até devolver funções de outras funções.
Abaixo, criamos duas funções, engGreeting e espGreeting, e atribuímo-las à variável 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!
Os valores de função dão a oportunidade de parametrizar funções não só com dados, mas também com comportamento.
No exemplo seguinte, passamos comportamento à função dialog através do 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.
O valor de uma variável não inicializada de tipo função é nil.
Por isso, chamar um valor de função nil provoca um panic.
var dutchGreeting func(string) string
dutchGreeting("Alice") // panic: call of nil function
Os valores de função podem ser comparados com nil.
Isto pode ser útil para evitar panics desnecessários no programa.
var dutchGreeting func(string) string
if dutchGreeting != nil {
dutchGreeting("Alice") // safe to call dutchGreeting
}
Usar valores de função é possível graças aos tipos de função em Go.
Um tipo de função designa o conjunto de todas as funções com a mesma sequência de tipos de parâmetros e a mesma sequência de tipos de resultado.
É possível declarar tipos definidos pelo utilizador sobre tipos de função.
Por exemplo, a função dialog dos exemplos anteriores pode ser atualizada da seguinte forma:
type greetingFunc func(string) string
func dialog(name string, f greetingFunc) {
fmt.Println(f(name))
fmt.Println("I'm a dialog bot.")
}
Outra ferramenta poderosa que está disponível graças ao suporte de funções de primeira classe são as funções anónimas.
As funções anónimas são definidas no ponto em que são usadas, sem um nome a seguir à palavra-chave func.
Estas funções têm acesso às variáveis da função que as envolve.
Por exemplo:
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())
}
Uma chamada a fib declara as variáveis n1 e n2 e devolve uma função anónima que, por sua vez, altera os valores destas variáveis cada vez que a função é chamada.
A enésima chamada da função anónima devolve o enésimo número da sequência de Fibonacci, começando em 0. A função anónima interna tem acesso às variáveis locais (n1 e n2) da função fib que a envolve.
É uma ótima forma de os valores de função manterem estado entre chamadas.
Dizemos que a função anónima é uma closure das variáveis n1 e n2. Os closures são muito usados em programação e é possível que vejas outras linguagens a suportá-los.
O Bob é consultor financeiro e ajuda as pessoas a gerir as suas despesas. Os clientes do Bob enviam-lhe registos de despesas para ele analisar. O Bob tem registos dos períodos anteriores, por isso consegue ver as alterações nos gastos. O Bob não gosta de calendários e usa a Bob epoch em vez de datas. A Bob epoch é o número de dias decorridos desde que o cliente do Bob começou a sua atividade.
Neste exercício, vais criar um programa para ajudar o Bob a gerir e a analisar as despesas dos seus clientes.
O Bob trabalha com Records e DaysPeriods.
Um Record representa um registo de despesa que contém o dia em que a despesa foi feita, o dinheiro gasto e a categoria da despesa.
// Record represents an expense record.
type Record struct {
Day int
Amount float64
Category string
}
Um DaysPeriod representa um intervalo de dias e inclui todos os dias desde o dia From até ao dia To.
Ambos os extremos estão incluídos no intervalo.
// 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
O Bob lida com muitos registos todos os dias, mas nem todos são interessantes, dependendo da análise que o Bob está a fazer. Vamos ajudar o Bob a fazer uma filtragem básica de registos.
Implementa a função genérica Filter para filtrar registos de acordo com um critério dado por uma função.
Esta função de filtragem aceita uma coleção de registos e uma função de predicado e devolve apenas os registos da coleção que satisfazem o 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"}
// ]
O Bob precisa frequentemente de filtrar registos que estão num determinado período de tempo.
Implementa a função ByDaysPeriod que vai ajudar o Bob a criar esses filtros.
Esta função aceita um DaysPeriod e devolve uma função que recebe um registo e indica se o registo está no período de tempo especificado pelo 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"},
// ]
Além de filtrar registos por um período de tempo, o Bob também precisa de filtrar registos pela sua categoria.
Implementa a função ByCategory que vai ajudar o Bob a criar esses filtros.
Esta função aceita uma categoria e devolve uma função que recebe um registo e indica se a categoria deste registo é igual à categoria 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"},
// ]
O Bob também quer saber o montante total das despesas num período de tempo.
Implementa a função TotalByPeriod para devolver a soma das despesas no período de dias.
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 os relatórios mais complexos que o Bob faz aos seus clientes, o Bob precisa de filtrar registos por categoria e período de tempo ao mesmo tempo. Isto significa que o Bob quer saber o total de despesas dos registos de uma categoria num determinado período de tempo.
Implementa a função CategoryExpenses que devolve o montante total das despesas de uma categoria num determinado período de dias.
A função deve também distinguir o caso em que a categoria dada não está presente nos registos de despesas do caso em que não há despesas da categoria no período indicado.
Quando a categoria não é a categoria de nenhum dos registos (independentemente do período de tempo), a função deve devolver um erro.
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
Inscreve-te no Exercism para aprenderes e dominares Go com 34 conceitos165 exercícios, e mentoria humana real, tudo grátis.