Skip to content

Commit eb5c0a3

Browse files
authored
New first-class functions concept exercise (#2103)
1 parent d15438c commit eb5c0a3

File tree

14 files changed

+986
-0
lines changed

14 files changed

+986
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"authors": ["antklim"],
3+
"contributors": ["andrerfcsantos"],
4+
"blurb": "Functions in Go can be used as regular values of the language."
5+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# About
2+
3+
In Go, functions are first-class values. This means that you can do with functions the same things you can do with all other values - assign functions to variables, pass them as arguments to other functions or even return functions from other functions.
4+
5+
Below we are creating two functions, `engGreeting` and `espGreeting` and we are assigning them to the variable `greeting`:
6+
7+
```go
8+
import "fmt"
9+
10+
func engGreeting(name string) string {
11+
return fmt.Sprintf("Hello %s, nice to meet you!", name)
12+
}
13+
14+
func espGreeting(name string) string {
15+
return fmt.Sprintf("¡Hola %s, mucho gusto!", name)
16+
}
17+
18+
greeting := engGreeting // greeting is a variable of type func(string) string
19+
fmt.Println(greeting("Alice")) // Hello Alice, nice to meet you!
20+
21+
greeting = espGreeting
22+
fmt.Println(greeting("Alice")) // ¡Hola Alice, mucho gusto!
23+
```
24+
25+
Function values provide an opportunity to parametrize functions not only with data but with behavior too.
26+
In the following example, we are passing behaviour to the `dialog` function via the `greetingFunc` parameter:
27+
28+
```go
29+
func dialog(name string, greetingFunc func(string) string) {
30+
fmt.Println(greetingFunc(name))
31+
fmt.Println("I'm a dialog bot.")
32+
}
33+
34+
func espGreeting(name string) string {
35+
return fmt.Sprintf("¡Hola %s, mucho gusto!", name)
36+
}
37+
38+
greeting := engGreeting
39+
dialog("Alice", greeting)
40+
// Output:
41+
// ¡Hola Alice, mucho gusto!
42+
// I'm a dialog bot.
43+
```
44+
45+
The value of an uninitialized variable of function type is `nil`.
46+
Therefore, calling a `nil` function value causes a panic.
47+
48+
```go
49+
var dutchGreeting func(string) string
50+
dutchGreeting("Alice") // panic: call of nil function
51+
```
52+
53+
Function values can be compared with `nil`. This can be useful to avoid unnecessary program panics.
54+
55+
```go
56+
var dutchGreeting func(string) string
57+
if dutchGreeting != nil {
58+
dutchGreeting("Alice") // safe to call dutchGreeting
59+
}
60+
```
61+
62+
## Function types
63+
64+
Using function values is possible thanks to the function types in Go. A function type denotes the set of all functions with the same sequence of parameter types and the same sequence of result types. User-defined types can be declared on top of function types. For instance, the `dialog` function from the previous examples can be updated as following:
65+
66+
```go
67+
type greetingFunc func(string) string
68+
69+
func dialog(name string, f greetingFunc) {
70+
fmt.Println(f(name))
71+
fmt.Println("I'm a dialog bot.")
72+
}
73+
```
74+
75+
## Anonymous functions
76+
77+
Another powerful tool that is available thanks to first-class functions support is anonymous functions. Anonymous functions are defined at their point of use, without a name following the `func` keyword. Such functions have access to the variables of the enclosing function.
78+
79+
For example:
80+
81+
```go
82+
func fib() func() int {
83+
var n1, n2 int
84+
85+
return func() int {
86+
if n1 == 0 && n2 == 0 {
87+
n1 = 1
88+
} else {
89+
n1, n2 = n2, n1 + n2
90+
}
91+
return n2
92+
}
93+
}
94+
95+
next := fib()
96+
for i := 0; i < N; i++ {
97+
fmt.Printf("F%d\t= %4d\n", i, next())
98+
}
99+
```
100+
101+
A call to `fib` initializes the variables `n1` and `n2` and returns an anonymous function that, in turn, changes the values of these variables each time the function is called. Nth calls of the anonymous function return the Nth number of the Fibonacci sequence starting from 0. The anonymous inner function has access to the local variables (`n1` and `n2`) of the enclosing function `fib`. This is a great way to have function values keep state between calls. We say that the anonymous function is a closure of the variables `n1` and `n2`. [Closures][closure] are widely used in programming and you might see other languages supporting them.
102+
103+
[closure]: https://en.wikipedia.org/wiki/Closure_(computer_programming)
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Introduction
2+
3+
In Go, functions are first-class values. This means that you can do with functions the same things you can do with all other values - assign functions to variables, pass them as arguments to other functions or even return functions from other functions.
4+
5+
Below we are creating two functions, `engGreeting` and `espGreeting` and we are assigning them to the variable `greeting`:
6+
7+
```go
8+
import "fmt"
9+
10+
func engGreeting(name string) string {
11+
return fmt.Sprintf("Hello %s, nice to meet you!", name)
12+
}
13+
14+
func espGreeting(name string) string {
15+
return fmt.Sprintf("¡Hola %s, mucho gusto!", name)
16+
}
17+
18+
greeting := engGreeting // greeting is a variable of type func(string) string
19+
fmt.Println(greeting("Alice")) // Hello Alice, nice to meet you!
20+
21+
greeting = espGreeting
22+
fmt.Println(greeting("Alice")) // ¡Hola Alice, mucho gusto!
23+
```
24+
25+
Function values provide an opportunity to parametrize functions not only with data but with behavior too.
26+
In the following example, we are passing behaviour to the `dialog` function via the `greetingFunc` parameter:
27+
28+
```go
29+
func dialog(name string, greetingFunc func(string) string) {
30+
fmt.Println(greetingFunc(name))
31+
fmt.Println("I'm a dialog bot.")
32+
}
33+
34+
func espGreeting(name string) string {
35+
return fmt.Sprintf("¡Hola %s, mucho gusto!", name)
36+
}
37+
38+
greeting := engGreeting
39+
dialog("Alice", greeting)
40+
// Output:
41+
// ¡Hola Alice, mucho gusto!
42+
// I'm a dialog bot.
43+
```
44+
45+
The value of an uninitialized variable of function type is `nil`.
46+
Therefore, calling a `nil` function value causes a panic.
47+
48+
```go
49+
var dutchGreeting func(string) string
50+
dutchGreeting("Alice") // panic: call of nil function
51+
```
52+
53+
Function values can be compared with `nil`. This can be useful to avoid unnecessary program panics.
54+
55+
```go
56+
var dutchGreeting func(string) string
57+
if dutchGreeting != nil {
58+
dutchGreeting("Alice") // safe to call dutchGreeting
59+
}
60+
```
61+
62+
## Function types
63+
64+
Using function values is possible thanks to the function types in Go. A function type denotes the set of all functions with the same sequence of parameter types and the same sequence of result types. User-defined types can be declared on top of function types. For instance, the `dialog` function from the previous examples can be updated as following:
65+
66+
```go
67+
type greetingFunc func(string) string
68+
69+
func dialog(name string, f greetingFunc) {
70+
fmt.Println(f(name))
71+
fmt.Println("I'm a dialog bot.")
72+
}
73+
```
74+
75+
## Anonymous functions
76+
77+
Another powerful tool that is available thanks to first-class functions support is anonymous functions. Anonymous functions are defined at their point of use, without a name following the `func` keyword. Such functions have access to the variables of the enclosing function.
78+
79+
For example:
80+
81+
```go
82+
func fib() func() int {
83+
var n1, n2 int
84+
85+
return func() int {
86+
if n1 == 0 && n2 == 0 {
87+
n1 = 1
88+
} else {
89+
n1, n2 = n2, n1 + n2
90+
}
91+
return n2
92+
}
93+
}
94+
95+
next := fib()
96+
for i := 0; i < N; i++ {
97+
fmt.Printf("F%d\t= %4d\n", i, next())
98+
}
99+
```
100+
101+
A call to `fib` initializes the variables `n1` and `n2` and returns an anonymous function that, in turn, changes the values of these variables each time the function is called. Nth calls of the anonymous function return the Nth number of the Fibonacci sequence starting from 0. The anonymous inner function has access to the local variables (`n1` and `n2`) of the enclosing function `fib`. This is a great way to have function values keep state between calls. We say that the anonymous function is a closure of the variables `n1` and `n2`. [Closures][closure] are widely used in programming and you might see other languages supporting them.
102+
103+
[closure]: https://en.wikipedia.org/wiki/Closure_(computer_programming)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"url": "https://go.dev/ref/spec#Function_types",
4+
"description": "Go Language Spec: Function types"
5+
},
6+
{
7+
"url": "https://go.dev/tour/moretypes/24",
8+
"description": "Tour of Go: Function values"
9+
},
10+
{
11+
"url": "https://golangbot.com/first-class-functions/",
12+
"description": "Golang tutorial: First Class Functions"
13+
}
14+
]

config.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,21 @@
387387
],
388388
"status": "beta"
389389
},
390+
{
391+
"slug": "expenses",
392+
"name": "Expenses",
393+
"uuid": "7221b17b-7880-4228-a07a-121e5857c953",
394+
"concepts": [
395+
"first-class-functions"
396+
],
397+
"prerequisites": [
398+
"for-loops",
399+
"functions",
400+
"structs",
401+
"type-definitions"
402+
],
403+
"status": "beta"
404+
},
390405
{
391406
"slug": "animal-magic",
392407
"name": "Animal Magic",
@@ -1957,6 +1972,11 @@
19571972
"slug": "functions",
19581973
"uuid": "e737f47b-5d1a-423a-8d6a-cd201a6e4e44"
19591974
},
1975+
{
1976+
"name": "First class functions",
1977+
"slug": "first-class-functions",
1978+
"uuid": "66968174-701b-484c-a84d-f919b4756022"
1979+
},
19601980
{
19611981
"name": "Floating-point numbers",
19621982
"slug": "floating-point-numbers",
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Hints
2+
3+
## 1. Implement a general records filter
4+
5+
- Create a new `[]Record` to hold the filtered results
6+
- Iterate over the records and call the predicate function on each one
7+
- Add to the results slice only the records for which calling the predicate function on them returns `true`
8+
9+
## 2. Filter records within a period of time
10+
11+
- Make the function return an anonymous function that receives a `Record` and returns a `bool` indicating if the record is within the period of time that `ByDaysPeriod` receives as an argument.
12+
- Inside your anonymous function, you have access to the `DaysPeriod` argument that `ByDaysPeriod` receives. Together with the `Record` argument of the anonymous function, make a boolean expression that checks if the day in the record is inside the `DaysPeriod` that `ByDaysPeriod` receives. Return that boolean expression from the anonymous function.
13+
14+
## 3. Filter records by category
15+
16+
- This is very similar to the previous task, but now you are dealing with a category instead of a period of time.
17+
- Make the function return an anonymous function that receives a `Record` and returns a `bool` indicating if the record belongs to the category that `ByCategory` receives as an argument.
18+
- Inside your anonymous function, you have access to the category argument that `ByCategory` receives. Together with the `Record` argument of the anonymous function, make a boolean expression that checks if the category of the record is the same as the category that `ByCategory` receives as an argument. Return that boolean expression from the anonymous function.
19+
20+
## 4. Calculate the total amount of expenses in a period
21+
22+
- Use the `ByDaysPeriod` function you made in step 2 to make a period filter function.
23+
- Pass that filter function as the second argument of the `Filter` function you made in step 1. This allows you to filter the results by a period of time.
24+
- Iterate over the filtered records and sum up all their amounts.
25+
26+
## 5. Calculate the total expenses for records of a category in a period
27+
28+
- Use the `ByCategory` function you made in step 3 to make a category filter function.
29+
- Pass that filter function as the second argument of the `Filter` function you made in step 1. This allows you to filter the results by a particular category.
30+
- After filtering the records by category, check if you have any records for thatcategory. If you don't, you must return an error.
31+
- If you have records belonging to that category, compute the total expenses for the given period of time using the `TotalByPeriod` function you made in step 4.

0 commit comments

Comments
 (0)