This commit is contained in:
wagslane
2023-05-01 15:25:27 -06:00
parent f8912668b8
commit 9be3074de6
868 changed files with 58698 additions and 2 deletions

View File

@@ -0,0 +1,49 @@
package main
import "fmt"
func getFormattedMessages(messages []string, formatter func) []string {
formattedMessages := []string{}
for _, message := range messages {
formattedMessages = append(formattedMessages, formatter(message))
}
return formattedMessages
}
// don't touch below this line
func addSignature(message string) string {
return message + " Kind regards."
}
func addGreeting(message string) string {
return "Hello! " + message
}
func test(messages []string, formatter func(string) string) {
defer fmt.Println("====================================")
formattedMessages := getFormattedMessages(messages, formatter)
if len(formattedMessages) != len(messages) {
fmt.Println("The number of messages returned is incorrect.")
return
}
for i, message := range messages {
formatted := formattedMessages[i]
fmt.Printf(" * %s -> %s\n", message, formatted)
}
}
func main() {
test([]string{
"Thanks for getting back to me.",
"Great to see you again.",
"I would love to hang out this weekend.",
"Got any hot stock tips?",
}, addSignature)
test([]string{
"Thanks for getting back to me.",
"Great to see you again.",
"I would love to hang out this weekend.",
"Got any hot stock tips?",
}, addGreeting)
}

View File

@@ -0,0 +1,49 @@
package main
import "fmt"
func getFormattedMessages(messages []string, formatter func(string) string) []string {
formattedMessages := []string{}
for _, message := range messages {
formattedMessages = append(formattedMessages, formatter(message))
}
return formattedMessages
}
// don't touch below this line
func addSignature(message string) string {
return message + " Kind regards."
}
func addGreeting(message string) string {
return "Hello! " + message
}
func test(messages []string, formatter func(string) string) {
defer fmt.Println("====================================")
formattedMessages := getFormattedMessages(messages, formatter)
if len(formattedMessages) != len(messages) {
fmt.Println("The number of messages returned is incorrect.")
return
}
for i, message := range messages {
formatted := formattedMessages[i]
fmt.Printf(" * %s -> %s\n", message, formatted)
}
}
func main() {
test([]string{
"Thanks for getting back to me.",
"Great to see you again.",
"I would love to hang out this weekend.",
"Got any hot stock tips?",
}, addSignature)
test([]string{
"Thanks for getting back to me.",
"Great to see you again.",
"I would love to hang out this weekend.",
"Got any hot stock tips?",
}, addGreeting)
}

View File

@@ -0,0 +1,10 @@
* Thanks for getting back to me. -> Thanks for getting back to me. Kind regards.
* Great to see you again. -> Great to see you again. Kind regards.
* I would love to hang out this weekend. -> I would love to hang out this weekend. Kind regards.
* Got any hot stock tips? -> Got any hot stock tips? Kind regards.
====================================
* Thanks for getting back to me. -> Hello! Thanks for getting back to me.
* Great to see you again. -> Hello! Great to see you again.
* I would love to hang out this weekend. -> Hello! I would love to hang out this weekend.
* Got any hot stock tips? -> Hello! Got any hot stock tips?
====================================

View File

@@ -0,0 +1,37 @@
# First Class and Higher Order Functions
A programming language is said to have "first-class functions" when functions in that language are treated like any other variable. For example, in such a language, a function can be passed as an argument to other functions, can be returned by another function and can be assigned as a value to a variable.
A function that returns a function or accepts a function as input is called a Higher-Order Function.
Go supports [first-class](https://developer.mozilla.org/en-US/docs/Glossary/First-class_Function) and higher-order functions. Another way to think of this is that a function is just another type -- just like `int`s and `string`s and `bool`s.
For example, to accept a function as a parameter:
```go
func add(x, y int) int {
return x + y
}
func mul(x, y int) int {
return x * y
}
// aggregate applies the given math function to the first 3 inputs
func aggregate(a, b, c int, arithmetic func(int, int) int) int {
return arithmetic(arithmetic(a, b), c)
}
func main(){
fmt.Println(aggregate(2,3,4, add))
// prints 9
fmt.Println(aggregate(2,3,4, mul))
// prints 24
}
```
## Assignment
Textio is launching a new email messaging product, "Mailio"!
Fix the compile-time bug in the `getFormattedMessages` function. The function body is correct, but the function signature is not.

View File

@@ -0,0 +1,8 @@
{
"question": "What is a higher-order function?",
"answers": [
"A function that takes another function as an argument",
"A function with superior logic",
"A function that is first in the call stack"
]
}

View File

@@ -0,0 +1,29 @@
# Why First-class and Higher-Order Functions?
At first, it may seem like dynamically creating functions and passing them around as variables adds unnecessary complexity. Most of the time you would be right. There are cases however when functions as values make a lot of sense. Some of these include:
* [HTTP API](https://en.wikipedia.org/wiki/Web_API) handlers
* [Pub/Sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) handlers
* Onclick callbacks
Any time you need to run custom code at *a time in the future*, functions as values might make sense.
## Definition: First-class Functions
A first-class function is a function that can be treated like any other value. Go supports first-class functions. A function's type is dependent on the types of its parameters and return values. For example, these are different function types:
```go
func() int
```
```go
func(string) int
```
## Definition: Higher-Order Functions
A higher-order function is a function that takes a function as an argument or returns a function as a return value. Go supports higher-order functions. For example, this function takes a function as an argument:
```go
func aggregate(a, b, c int, arithmetic func(int, int) int) int
```

View File

@@ -0,0 +1,8 @@
{
"question": "What is a first-class function?",
"answers": [
"A function that is treated like any other variable",
"A function that has been deemed most important by the architect",
"A function that takes another function as an argument"
]
}

View File

@@ -0,0 +1,29 @@
# Why First-class and Higher-Order Functions?
At first, it may seem like dynamically creating functions and passing them around as variables adds unnecessary complexity. Most of the time you would be right. There are cases however when functions as values make a lot of sense. Some of these include:
* [HTTP API](https://en.wikipedia.org/wiki/Web_API) handlers
* [Pub/Sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) handlers
* Onclick callbacks
Any time you need to run custom code at *a time in the future*, functions as values might make sense.
## Definition: First-class Functions
A first-class function is a function that can be treated like any other value. Go supports first-class functions. A function's type is dependent on the types of its parameters and return values. For example, these are different function types:
```go
func() int
```
```go
func(string) int
```
## Definition: Higher-Order Functions
A higher-order function is a function that takes a function as an argument or returns a function as a return value. Go supports higher-order functions. For example, this function takes a function as an argument:
```go
func aggregate(a, b, c int, arithmetic func(int, int) int) int
```

View File

@@ -0,0 +1,47 @@
package main
import (
"errors"
"fmt"
)
// getLogger takes a function that formats two strings into
// a single string and returns a function that formats two strings but prints
// the result instead of returning it
func getLogger(formatter func(string, string) string) func(string, string) {
// ?
}
// don't touch below this line
func test(first string, errors []error, formatter func(string, string) string) {
defer fmt.Println("====================================")
logger := getLogger(formatter)
fmt.Println("Logs:")
for _, err := range errors {
logger(first, err.Error())
}
}
func colonDelimit(first, second string) string {
return first + ": " + second
}
func commaDelimit(first, second string) string {
return first + ", " + second
}
func main() {
dbErrors := []error{
errors.New("out of memory"),
errors.New("cpu is pegged"),
errors.New("networking issue"),
errors.New("invalid syntax"),
}
test("Error on database server", dbErrors, colonDelimit)
mailErrors := []error{
errors.New("email too large"),
errors.New("non alphanumeric symbols found"),
}
test("Error on mail server", mailErrors, commaDelimit)
}

View File

@@ -0,0 +1,49 @@
package main
import (
"errors"
"fmt"
)
// getLogger takes a function that formats two strings into
// a single string and returns a function that formats two strings but prints
// the result instead of returning it
func getLogger(formatter func(string, string) string) func(string, string) {
return func(first, second string) {
fmt.Println(formatter(first, second))
}
}
// don't touch below this line
func test(first string, errors []error, formatter func(string, string) string) {
defer fmt.Println("====================================")
logger := getLogger(formatter)
fmt.Println("Logs:")
for _, err := range errors {
logger(first, err.Error())
}
}
func colonDelimit(first, second string) string {
return first + ": " + second
}
func commaDelimit(first, second string) string {
return first + ", " + second
}
func main() {
dbErrors := []error{
errors.New("out of memory"),
errors.New("cpu is pegged"),
errors.New("networking issue"),
errors.New("invalid syntax"),
}
test("Error on database server", dbErrors, colonDelimit)
mailErrors := []error{
errors.New("email too large"),
errors.New("non alphanumeric symbols found"),
}
test("Error on mail server", mailErrors, commaDelimit)
}

View File

@@ -0,0 +1,10 @@
Logs:
Error on database server: out of memory
Error on database server: cpu is pegged
Error on database server: networking issue
Error on database server: invalid syntax
====================================
Logs:
Error on mail server, email too large
Error on mail server, non alphanumeric symbols found
====================================

View File

@@ -0,0 +1,40 @@
# Currying
Function currying is the practice of writing a function that takes a function (or functions) as input, and returns a new function.
For example:
```go
func main() {
squareFunc := selfMath(multiply)
doubleFunc := selfMath(add)
fmt.Println(squareFunc(5))
// prints 25
fmt.Println(doubleFunc(5))
// prints 10
}
func multiply(x, y int) int {
return x * y
}
func add(x, y int) int {
return x + y
}
func selfMath(mathFunc func(int, int) int) func (int) int {
return func(x int) int {
return mathFunc(x, x)
}
}
```
In the example above, the `selfMath` function takes in a function as its parameter, and returns a function that itself returns the value of running that input function on its parameter.
## Assignment
The Mailio API needs a very robust error-logging system so we can see when things are going awry in the back-end system. We need a function that can create a custom "logger" (a function that prints to the console) given a specific formatter.
Complete the `getLogger` function. It should `return` *a new function* that prints the formatted inputs using the given `formatter` function. The inputs should be passed into the formatter function in the order they are given to the logger function.

View File

@@ -0,0 +1,91 @@
package main
import (
"fmt"
"sort"
)
const (
logDeleted = "user deleted"
logNotFound = "user not found"
logAdmin = "admin deleted"
)
func logAndDelete(users map[string]user, name string) (log string) {
user, ok := users[name]
if !ok {
delete(users, name)
return logNotFound
}
if user.admin {
return logAdmin
}
delete(users, name)
return logDeleted
}
// don't touch below this line
type user struct {
name string
number int
admin bool
}
func test(users map[string]user, name string) {
fmt.Printf("Attempting to delete %s...\n", name)
defer fmt.Println("====================================")
log := logAndDelete(users, name)
fmt.Println("Log:", log)
}
func main() {
users := map[string]user{
"john": {
name: "john",
number: 18965554631,
admin: true,
},
"elon": {
name: "elon",
number: 19875556452,
admin: true,
},
"breanna": {
name: "breanna",
number: 98575554231,
admin: false,
},
"kade": {
name: "kade",
number: 10765557221,
admin: false,
},
}
fmt.Println("Initial users:")
usersSorted := []string{}
for name := range users {
usersSorted = append(usersSorted, name)
}
sort.Strings(usersSorted)
for _, name := range usersSorted {
fmt.Println(" -", name)
}
fmt.Println("====================================")
test(users, "john")
test(users, "santa")
test(users, "kade")
fmt.Println("Final users:")
usersSorted = []string{}
for name := range users {
usersSorted = append(usersSorted, name)
}
sort.Strings(usersSorted)
for _, name := range usersSorted {
fmt.Println(" -", name)
}
fmt.Println("====================================")
}

View File

@@ -0,0 +1,91 @@
package main
import (
"fmt"
"sort"
)
const (
logDeleted = "user deleted"
logNotFound = "user not found"
logAdmin = "admin deleted"
)
func logAndDelete(users map[string]user, name string) (log string) {
defer delete(users, name)
user, ok := users[name]
if !ok {
return logNotFound
}
if user.admin {
return logAdmin
}
return logDeleted
}
// don't touch below this line
type user struct {
name string
number int
admin bool
}
func test(users map[string]user, name string) {
fmt.Printf("Attempting to delete %s...\n", name)
defer fmt.Println("====================================")
log := logAndDelete(users, name)
fmt.Println("Log:", log)
}
func main() {
users := map[string]user{
"john": {
name: "john",
number: 18965554631,
admin: true,
},
"elon": {
name: "elon",
number: 19875556452,
admin: true,
},
"breanna": {
name: "breanna",
number: 98575554231,
admin: false,
},
"kade": {
name: "kade",
number: 10765557221,
admin: false,
},
}
fmt.Println("Initial users:")
usersSorted := []string{}
for name := range users {
usersSorted = append(usersSorted, name)
}
sort.Strings(usersSorted)
for _, name := range usersSorted {
fmt.Println(" -", name)
}
fmt.Println("====================================")
test(users, "john")
test(users, "santa")
test(users, "kade")
fmt.Println("Final users:")
usersSorted = []string{}
for name := range users {
usersSorted = append(usersSorted, name)
}
sort.Strings(usersSorted)
for _, name := range usersSorted {
fmt.Println(" -", name)
}
fmt.Println("====================================")
}

View File

@@ -0,0 +1,19 @@
Initial users:
- breanna
- elon
- john
- kade
====================================
Attempting to delete john...
Log: admin deleted
====================================
Attempting to delete santa...
Log: user not found
====================================
Attempting to delete kade...
Log: user deleted
====================================
Final users:
- breanna
- elon
====================================

View File

@@ -0,0 +1,46 @@
# Defer
The `defer` keyword is a fairly unique feature of Go. It allows a function to be executed automatically *just before* its enclosing function returns.
The deferred call's arguments are evaluated immediately, but the function call is not executed until the surrounding function returns.
Deferred functions are typically used to close database connections, file handlers and the like.
For example:
```go
// CopyFile copies a file from srcName to dstName on the local filesystem.
func CopyFile(dstName, srcName string) (written int64, err error) {
// Open the source file
src, err := os.Open(srcName)
if err != nil {
return
}
// Close the source file when the CopyFile function returns
defer src.Close()
// Create the destination file
dst, err := os.Create(dstName)
if err != nil {
return
}
// Close the destination file when the CopyFile function returns
defer dst.Close()
return io.Copy(dst, src)
}
```
In the above example, the `src.Close()` function is not called until after the `CopyFile` function was called but immediately before the `CopyFile` function returns.
Defer is a great way to **make sure** that something happens at the end of a function, even if there are multiple return statements.
## Assignment
There is a bug in the `logAndDelete` function, fix it!
This function should *always* delete the user from the user's map, which is a map that stores the user's name as keys. It also returns a `log` string that indicates to the caller some information about the user's deletion.
To avoid bugs like this in the future, instead of calling `delete` before each `return`, just `defer` the delete once at the beginning of the function.

View File

@@ -0,0 +1,46 @@
package main
import "fmt"
func adder() func(int) int {
// ?
}
// don't touch below this line
type emailBill struct {
costInPennies int
}
func test(bills []emailBill) {
defer fmt.Println("====================================")
countAdder, costAdder := adder(), adder()
for _, bill := range bills {
fmt.Printf("You've sent %d emails and it has cost you %d cents\n", countAdder(1), costAdder(bill.costInPennies))
}
}
func main() {
test([]emailBill{
{45},
{32},
{43},
{12},
{34},
{54},
})
test([]emailBill{
{12},
{12},
{976},
{12},
{543},
})
test([]emailBill{
{743},
{13},
{8},
})
}

View File

@@ -0,0 +1,50 @@
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
// don't touch below this line
type emailBill struct {
costInPennies int
}
func test(bills []emailBill) {
defer fmt.Println("====================================")
countAdder, costAdder := adder(), adder()
for _, bill := range bills {
fmt.Printf("You've sent %d emails and it has cost you %d cents\n", countAdder(1), costAdder(bill.costInPennies))
}
}
func main() {
test([]emailBill{
{45},
{32},
{43},
{12},
{34},
{54},
})
test([]emailBill{
{12},
{12},
{976},
{12},
{543},
})
test([]emailBill{
{743},
{13},
{8},
})
}

View File

@@ -0,0 +1,17 @@
You've sent 1 emails and it has cost you 45 cents
You've sent 2 emails and it has cost you 77 cents
You've sent 3 emails and it has cost you 120 cents
You've sent 4 emails and it has cost you 132 cents
You've sent 5 emails and it has cost you 166 cents
You've sent 6 emails and it has cost you 220 cents
====================================
You've sent 1 emails and it has cost you 12 cents
You've sent 2 emails and it has cost you 24 cents
You've sent 3 emails and it has cost you 1000 cents
You've sent 4 emails and it has cost you 1012 cents
You've sent 5 emails and it has cost you 1555 cents
====================================
You've sent 1 emails and it has cost you 743 cents
You've sent 2 emails and it has cost you 756 cents
You've sent 3 emails and it has cost you 764 cents
====================================

View File

@@ -0,0 +1,37 @@
# Closures
A closure is a function that references variables from outside its own function body. The function may access and *assign* to the referenced variables.
In this example, the `concatter()` function returns a function that has reference to an *enclosed* `doc` value. Each successive call to `harryPotterAggregator` mutates that same `doc` variable.
```go
func concatter() func(string) string {
doc := ""
return func(word string) string {
doc += word + " "
return doc
}
}
func main() {
harryPotterAggregator := concatter()
harryPotterAggregator("Mr.")
harryPotterAggregator("and")
harryPotterAggregator("Mrs.")
harryPotterAggregator("Dursley")
harryPotterAggregator("of")
harryPotterAggregator("number")
harryPotterAggregator("four,")
harryPotterAggregator("Privet")
fmt.Println(harryPotterAggregator("Drive"))
// Mr. and Mrs. Dursley of number four, Privet Drive
}
```
## Assignment
Keeping track of how many emails we send is mission-critical at Mailio. Complete the `adder()` function.
It should return a function that adds its input (an `int`) to an enclosed `sum` value, then return the new sum. In other words, it keeps a running total of the `sum` variable within a closure.

View File

@@ -0,0 +1,7 @@
{
"question": "Can a closure mutate a variable outside its body?",
"answers": [
"Yes",
"No"
]
}

View File

@@ -0,0 +1,30 @@
# Closure Review
A closure is a function that references variables from outside its own function body. The function may access and *assign* to the referenced variables.
## Example Closure
```go
func concatter() func(string) string {
doc := ""
return func(word string) string {
doc += word + " "
return doc
}
}
func main() {
harryPotterAggregator := concatter()
harryPotterAggregator("Mr.")
harryPotterAggregator("and")
harryPotterAggregator("Mrs.")
harryPotterAggregator("Dursley")
harryPotterAggregator("of")
harryPotterAggregator("number")
harryPotterAggregator("four,")
harryPotterAggregator("Privet")
fmt.Println(harryPotterAggregator("Drive"))
// Mr. and Mrs. Dursley of number four, Privet Drive
}
```

View File

@@ -0,0 +1,7 @@
{
"question": "When a variable is enclosed in a closure, the enclosing function has access to ____",
"answers": [
"a mutable reference to the original value",
"a copy of the value"
]
}

View File

@@ -0,0 +1,30 @@
# Closure Review
A closure is a function that references variables from outside its own function body. The function may access and *assign* to the referenced variables.
## Example Closure
```go
func concatter() func(string) string {
doc := ""
return func(word string) string {
doc += word + " "
return doc
}
}
func main() {
harryPotterAggregator := concatter()
harryPotterAggregator("Mr.")
harryPotterAggregator("and")
harryPotterAggregator("Mrs.")
harryPotterAggregator("Dursley")
harryPotterAggregator("of")
harryPotterAggregator("number")
harryPotterAggregator("four,")
harryPotterAggregator("Privet")
fmt.Println(harryPotterAggregator("Drive"))
// Mr. and Mrs. Dursley of number four, Privet Drive
}
```

View File

@@ -0,0 +1,36 @@
package main
import "fmt"
func printReports(messages []string) {
// ?
}
// don't touch below this line
func test(messages []string) {
defer fmt.Println("====================================")
printReports(messages)
}
func main() {
test([]string{
"Here's Johnny!",
"Go ahead, make my day",
"You had me at hello",
"There's no place like home",
})
test([]string{
"Hello, my name is Inigo Montoya. You killed my father. Prepare to die.",
"May the Force be with you.",
"Show me the money!",
"Go ahead, make my day.",
})
}
func printCostReport(costCalculator func(string) int, message string) {
cost := costCalculator(message)
fmt.Printf(`Message: "%s" Cost: %v cents`, message, cost)
fmt.Println()
}

View File

@@ -0,0 +1,40 @@
package main
import "fmt"
func printReports(messages []string) {
for _, message := range messages {
printCostReport(func(m string) int {
return len(m) * 2
}, message)
}
}
// don't touch below this line
func test(messages []string) {
defer fmt.Println("====================================")
printReports(messages)
}
func main() {
test([]string{
"Here's Johnny!",
"Go ahead, make my day",
"You had me at hello",
"There's no place like home",
})
test([]string{
"Hello, my name is Inigo Montoya. You killed my father. Prepare to die.",
"May the Force be with you.",
"Show me the money!",
"Go ahead, make my day.",
})
}
func printCostReport(costCalculator func(string) int, message string) {
cost := costCalculator(message)
fmt.Printf(`Message: "%s" Cost: %v cents`, message, cost)
fmt.Println()
}

View File

@@ -0,0 +1,10 @@
Message: "Here's Johnny!" Cost: 28 cents
Message: "Go ahead, make my day" Cost: 42 cents
Message: "You had me at hello" Cost: 38 cents
Message: "There's no place like home" Cost: 52 cents
====================================
Message: "Hello, my name is Inigo Montoya. You killed my father. Prepare to die." Cost: 140 cents
Message: "May the Force be with you." Cost: 52 cents
Message: "Show me the money!" Cost: 36 cents
Message: "Go ahead, make my day." Cost: 44 cents
====================================

View File

@@ -0,0 +1,38 @@
# Anonymous Functions
Anonymous functions are true to form in that they have *no name*. We've been using them throughout this chapter, but we haven't really talked about them yet.
Anonymous functions are useful when defining a function that will only be used once or to create a quick [closure](https://en.wikipedia.org/wiki/Closure_(computer_programming)).
```go
// doMath accepts a function that converts one int into another
// and a slice of ints. It returns a slice of ints that have been
// converted by the passed in function.
func doMath(f func(int) int, nums []int) []int {
var results []int
for _, n := range nums {
results = append(results, f(n))
}
return results
}
func main() {
nums := []int{1, 2, 3, 4, 5}
// Here we define an anonymous function that doubles an int
// and pass it to doMath
allNumsDoubled := doMath(func(x int) int {
return x + x
}, nums)
fmt.Println(allNumsDoubled)
// prints:
// [2 4 6 8 10]
}
```
## Assignment
Complete the `printReports` function.
Call `printCostReport` once for each message. Pass in an anonymous function as the `costCalculator` that returns an `int` equal to twice the length of the input message.