mirror of
https://github.com/bootdotdev/fcc-learn-golang-assets.git
synced 2025-12-14 17:21:15 +00:00
first
This commit is contained in:
74
course/15-generics/exercises/1-generics/code.go
Normal file
74
course/15-generics/exercises/1-generics/code.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func getLast[]() {
|
||||
|
||||
}
|
||||
|
||||
// don't edit below this line
|
||||
|
||||
type email struct {
|
||||
message string
|
||||
senderEmail string
|
||||
recipientEmail string
|
||||
}
|
||||
|
||||
type payment struct {
|
||||
amount int
|
||||
senderEmail string
|
||||
recipientEmail string
|
||||
}
|
||||
|
||||
func main() {
|
||||
test([]email{}, "email")
|
||||
test([]email{
|
||||
{
|
||||
"Hi Margo",
|
||||
"janet@example.com",
|
||||
"margo@example.com",
|
||||
},
|
||||
{
|
||||
"Hey Margo I really wanna chat",
|
||||
"janet@example.com",
|
||||
"margo@example.com",
|
||||
},
|
||||
{
|
||||
"ANSWER ME",
|
||||
"janet@example.com",
|
||||
"margo@example.com",
|
||||
},
|
||||
}, "email")
|
||||
test([]payment{
|
||||
{
|
||||
5,
|
||||
"jane@example.com",
|
||||
"sally@example.com",
|
||||
},
|
||||
{
|
||||
25,
|
||||
"jane@example.com",
|
||||
"mark@example.com",
|
||||
},
|
||||
{
|
||||
1,
|
||||
"jane@example.com",
|
||||
"sally@example.com",
|
||||
},
|
||||
{
|
||||
16,
|
||||
"jane@example.com",
|
||||
"margo@example.com",
|
||||
},
|
||||
}, "payment")
|
||||
}
|
||||
|
||||
func test[T any](s []T, desc string) {
|
||||
last := getLast(s)
|
||||
fmt.Printf("Getting last %v from slice of length: %v\n", desc, len(s))
|
||||
for i, v := range s {
|
||||
fmt.Printf("Item #%v: %v\n", i+1, v)
|
||||
}
|
||||
fmt.Printf("Last item in list: %v\n", last)
|
||||
fmt.Println(" --- ")
|
||||
}
|
||||
78
course/15-generics/exercises/1-generics/complete.go
Normal file
78
course/15-generics/exercises/1-generics/complete.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func getLast[T any](s []T) T {
|
||||
if len(s) == 0 {
|
||||
var zero T
|
||||
return zero
|
||||
}
|
||||
return s[len(s)-1]
|
||||
}
|
||||
|
||||
// don't edit below this line
|
||||
|
||||
type email struct {
|
||||
message string
|
||||
senderEmail string
|
||||
recipientEmail string
|
||||
}
|
||||
|
||||
type payment struct {
|
||||
amount int
|
||||
senderEmail string
|
||||
recipientEmail string
|
||||
}
|
||||
|
||||
func main() {
|
||||
test([]email{}, "email")
|
||||
test([]email{
|
||||
{
|
||||
"Hi Margo",
|
||||
"janet@example.com",
|
||||
"margo@example.com",
|
||||
},
|
||||
{
|
||||
"Hey Margo I really wanna chat",
|
||||
"janet@example.com",
|
||||
"margo@example.com",
|
||||
},
|
||||
{
|
||||
"ANSWER ME",
|
||||
"janet@example.com",
|
||||
"margo@example.com",
|
||||
},
|
||||
}, "email")
|
||||
test([]payment{
|
||||
{
|
||||
5,
|
||||
"jane@example.com",
|
||||
"sally@example.com",
|
||||
},
|
||||
{
|
||||
25,
|
||||
"jane@example.com",
|
||||
"mark@example.com",
|
||||
},
|
||||
{
|
||||
1,
|
||||
"jane@example.com",
|
||||
"sally@example.com",
|
||||
},
|
||||
{
|
||||
16,
|
||||
"jane@example.com",
|
||||
"margo@example.com",
|
||||
},
|
||||
}, "payment")
|
||||
}
|
||||
|
||||
func test[T any](s []T, desc string) {
|
||||
last := getLast(s)
|
||||
fmt.Printf("Getting last %v from slice of length: %v\n", desc, len(s))
|
||||
for i, v := range s {
|
||||
fmt.Printf("Item #%v: %v\n", i+1, v)
|
||||
}
|
||||
fmt.Printf("Last item in list: %v\n", last)
|
||||
fmt.Println(" --- ")
|
||||
}
|
||||
16
course/15-generics/exercises/1-generics/expected.txt
Normal file
16
course/15-generics/exercises/1-generics/expected.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
Getting last email from slice of length: 0
|
||||
Last item in list: { }
|
||||
---
|
||||
Getting last email from slice of length: 3
|
||||
Item #1: {Hi Margo janet@example.com margo@example.com}
|
||||
Item #2: {Hey Margo I really wanna chat janet@example.com margo@example.com}
|
||||
Item #3: {ANSWER ME janet@example.com margo@example.com}
|
||||
Last item in list: {ANSWER ME janet@example.com margo@example.com}
|
||||
---
|
||||
Getting last payment from slice of length: 4
|
||||
Item #1: {5 jane@example.com sally@example.com}
|
||||
Item #2: {25 jane@example.com mark@example.com}
|
||||
Item #3: {1 jane@example.com sally@example.com}
|
||||
Item #4: {16 jane@example.com margo@example.com}
|
||||
Last item in list: {16 jane@example.com margo@example.com}
|
||||
---
|
||||
57
course/15-generics/exercises/1-generics/readme.md
Normal file
57
course/15-generics/exercises/1-generics/readme.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Generics in Go
|
||||
|
||||
As we've mentioned, Go does *not* support classes. For a long time, that meant that Go code couldn't easily be reused in many circumstances. For example, imagine some code that splits a slice into 2 equal parts. The code that splits the slice doesn't really care about the *values* stored in the slice. Unfortunately in Go we would need to write it multiple times for each type, which is a very un-[DRY](https://blog.boot.dev/clean-code/dry-code/) thing to do.
|
||||
|
||||
```go
|
||||
func splitIntSlice(s []int) ([]int, []int) {
|
||||
mid := len(s)/2
|
||||
return s[:mid], s[mid:]
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
func splitStringSlice(s []string) ([]string, []string) {
|
||||
mid := len(s)/2
|
||||
return s[:mid], s[mid:]
|
||||
}
|
||||
```
|
||||
|
||||
In Go 1.20 however, support for [generics](https://blog.boot.dev/golang/how-to-use-golangs-generics/) was released, effectively solving this problem!
|
||||
|
||||
## Type Parameters
|
||||
|
||||
Put simply, generics allow us to use variables to refer to specific types. This is an amazing feature because it allows us to write abstract functions that drastically reduce code duplication.
|
||||
|
||||
```go
|
||||
func splitAnySlice[T any](s []T) ([]T, []T) {
|
||||
mid := len(s)/2
|
||||
return s[:mid], s[mid:]
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, `T` is the name of the type parameter for the `splitAnySlice` function, and we've said that it must match the `any` constraint, which means it can be anything. This makes sense because the body of the function *doesn't care* about the types of things stored in the slice.
|
||||
|
||||
```go
|
||||
firstInts, secondInts := splitAnySlice([]int{0, 1, 2, 3})
|
||||
fmt.Println(firstInts, secondInts)
|
||||
```
|
||||
|
||||
## Assignment
|
||||
|
||||
At Mailio we often store all the emails of a given email campaign in memory as a slice. We store payments for a single user in the same way.
|
||||
|
||||
Complete the `getLast()` function. It should be a generic function that returns the last element from a slice, no matter the types stored in the slice. If the slice is empty, it should return the zero value of the type.
|
||||
|
||||
## Tip: Zero value of a type
|
||||
|
||||
Creating a variable that's the zero value of a type is easy:
|
||||
|
||||
```go
|
||||
var myZeroInt int
|
||||
```
|
||||
|
||||
It's the same with generics, we just have a variable that represents the type:
|
||||
|
||||
```go
|
||||
var myZero T
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "Which code would generics be most likely to help with?",
|
||||
"answers": [
|
||||
"A binary tree",
|
||||
"Calculating the area of a circle",
|
||||
"Detecting whether or not a string contains a given substring"
|
||||
]
|
||||
}
|
||||
15
course/15-generics/exercises/2-generics_why/readme.md
Normal file
15
course/15-generics/exercises/2-generics_why/readme.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Why Generics?
|
||||
|
||||
## Generics reduce repetitive code
|
||||
|
||||
You should care about generics because they mean you don’t have to write as much code! It can be frustrating to write the same logic over and over again, just because you have some underlying data types that are slightly different.
|
||||
|
||||
## Generics are used more often in libraries and packages
|
||||
|
||||
Generics give Go developers an elegant way to write amazing utility packages. While you will see and use generics in application code, I think it will much more common to see generics used in libraries and packages. Libraries and packages contain importable code intended to be used in *many* applications, so it makes sense to write them in a more abstract way. Generics are often the way to do just that!
|
||||
|
||||
## Why did it take so long to get generics?
|
||||
|
||||
Go places an emphasis on simplicity. In other words, Go has purposefully left out many features to provide its best feature: being simple and easy to work with.
|
||||
|
||||
According to [historical data from Go surveys](https://go.dev/blog/survey2020-results), Go’s lack of generics has always been listed as one of the top three biggest issues with the language. At a certain point, the drawbacks associated with the lack of a feature like generics justify adding complexity to the language.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "Go's approach to language design is...",
|
||||
"answers": [
|
||||
"Resist adding new features unless they're extremely important",
|
||||
"To support as many useful features as possible",
|
||||
"To never add new features, the language doesn't change"
|
||||
]
|
||||
}
|
||||
15
course/15-generics/exercises/2a-generics_why/readme.md
Normal file
15
course/15-generics/exercises/2a-generics_why/readme.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Why Generics?
|
||||
|
||||
## Generics reduce repetitive code
|
||||
|
||||
You should care about generics because they mean you don’t have to write as much code! It can be frustrating to write the same logic over and over again, just because you have some underlying data types that are slightly different.
|
||||
|
||||
## Generics are used more often in libraries and packages
|
||||
|
||||
Generics give Go developers an elegant way to write amazing utility packages. While you will see and use generics in application code, I think it will much more common to see generics used in libraries and packages. Libraries and packages contain importable code intended to be used in *many* applications, so it makes sense to write them in a more abstract way. Generics are often the way to do just that!
|
||||
|
||||
## Why did it take so long to get generics?
|
||||
|
||||
Go places an emphasis on simplicity. In other words, Go has purposefully left out many features to provide its best feature: being simple and easy to work with.
|
||||
|
||||
According to [historical data from Go surveys](https://go.dev/blog/survey2020-results), Go’s lack of generics has always been listed as one of the top three biggest issues with the language. At a certain point, the drawbacks associated with the lack of a feature like generics justify adding complexity to the language.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"question": "Generics will probably be used more heavily in...",
|
||||
"answers": [
|
||||
"Library packages",
|
||||
"Main packages (executable applications)"
|
||||
]
|
||||
}
|
||||
15
course/15-generics/exercises/2b-generics_why/readme.md
Normal file
15
course/15-generics/exercises/2b-generics_why/readme.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Why Generics?
|
||||
|
||||
## Generics reduce repetitive code
|
||||
|
||||
You should care about generics because they mean you don’t have to write as much code! It can be frustrating to write the same logic over and over again, just because you have some underlying data types that are slightly different.
|
||||
|
||||
## Generics are used more often in libraries and packages
|
||||
|
||||
Generics give Go developers an elegant way to write amazing utility packages. While you will see and use generics in application code, I think it will much more common to see generics used in libraries and packages. Libraries and packages contain importable code intended to be used in *many* applications, so it makes sense to write them in a more abstract way. Generics are often the way to do just that!
|
||||
|
||||
## Why did it take so long to get generics?
|
||||
|
||||
Go places an emphasis on simplicity. In other words, Go has purposefully left out many features to provide its best feature: being simple and easy to work with.
|
||||
|
||||
According to [historical data from Go surveys](https://go.dev/blog/survey2020-results), Go’s lack of generics has always been listed as one of the top three biggest issues with the language. At a certain point, the drawbacks associated with the lack of a feature like generics justify adding complexity to the language.
|
||||
111
course/15-generics/exercises/3-constraints/code.go
Normal file
111
course/15-generics/exercises/3-constraints/code.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func chargeForLineItem[T lineItem](newItem T, oldItems []T, balance float64) ([]T, float64, error) {
|
||||
// ?
|
||||
}
|
||||
|
||||
// don't edit below this line
|
||||
|
||||
type lineItem interface {
|
||||
GetCost() float64
|
||||
GetName() string
|
||||
}
|
||||
|
||||
type subscription struct {
|
||||
userEmail string
|
||||
startDate time.Time
|
||||
interval string
|
||||
}
|
||||
|
||||
func (s subscription) GetName() string {
|
||||
return fmt.Sprintf("%s subscription", s.interval)
|
||||
}
|
||||
|
||||
func (s subscription) GetCost() float64 {
|
||||
if s.interval == "monthly" {
|
||||
return 25.00
|
||||
}
|
||||
if s.interval == "yearly" {
|
||||
return 250.00
|
||||
}
|
||||
return 0.0
|
||||
}
|
||||
|
||||
type oneTimeUsagePlan struct {
|
||||
userEmail string
|
||||
numEmailsAllowed int
|
||||
}
|
||||
|
||||
func (otup oneTimeUsagePlan) GetName() string {
|
||||
return fmt.Sprintf("one time usage plan with %v emails", otup.numEmailsAllowed)
|
||||
}
|
||||
|
||||
func (otup oneTimeUsagePlan) GetCost() float64 {
|
||||
const costPerEmail = 0.03
|
||||
return float64(otup.numEmailsAllowed) * costPerEmail
|
||||
}
|
||||
|
||||
func main() {
|
||||
test(subscription{
|
||||
userEmail: "john@example.com",
|
||||
startDate: time.Now().UTC(),
|
||||
interval: "yearly",
|
||||
},
|
||||
[]subscription{},
|
||||
1000.00,
|
||||
)
|
||||
test(subscription{
|
||||
userEmail: "jane@example.com",
|
||||
startDate: time.Now().UTC(),
|
||||
interval: "monthly",
|
||||
},
|
||||
[]subscription{
|
||||
{
|
||||
userEmail: "jane@example.com",
|
||||
startDate: time.Now().UTC().Add(-time.Hour * 24 * 7),
|
||||
interval: "monthly",
|
||||
},
|
||||
{
|
||||
userEmail: "jane@example.com",
|
||||
startDate: time.Now().UTC().Add(-time.Hour * 24 * 7 * 52 * 2),
|
||||
interval: "yearly",
|
||||
},
|
||||
},
|
||||
686.20,
|
||||
)
|
||||
test(oneTimeUsagePlan{
|
||||
userEmail: "dillon@example.com",
|
||||
numEmailsAllowed: 5000,
|
||||
},
|
||||
[]oneTimeUsagePlan{},
|
||||
756.20,
|
||||
)
|
||||
test(oneTimeUsagePlan{
|
||||
userEmail: "dalton@example.com",
|
||||
numEmailsAllowed: 100000,
|
||||
},
|
||||
[]oneTimeUsagePlan{
|
||||
{
|
||||
userEmail: "dalton@example.com",
|
||||
numEmailsAllowed: 34200,
|
||||
},
|
||||
},
|
||||
32.20,
|
||||
)
|
||||
}
|
||||
|
||||
func test[T lineItem](newItem T, oldItems []T, balance float64) {
|
||||
fmt.Println(" --- ")
|
||||
fmt.Printf("Charging customer for a '%s', current balance is %v...\n", newItem.GetName(), balance)
|
||||
newItems, newBalance, err := chargeForLineItem(newItem, oldItems, balance)
|
||||
if err != nil {
|
||||
fmt.Printf("Got error: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("New balance is: %v. Total number of line items is now %v\n", newBalance, len(newItems))
|
||||
}
|
||||
117
course/15-generics/exercises/3-constraints/complete.go
Normal file
117
course/15-generics/exercises/3-constraints/complete.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func chargeForLineItem[T lineItem](newItem T, oldItems []T, balance float64) ([]T, float64, error) {
|
||||
newBalance := balance - newItem.GetCost()
|
||||
if newBalance < 0 {
|
||||
return nil, 0, errors.New("insufficient funds")
|
||||
}
|
||||
oldItems = append(oldItems, newItem)
|
||||
return oldItems, newBalance, nil
|
||||
}
|
||||
|
||||
// don't edit below this line
|
||||
|
||||
type lineItem interface {
|
||||
GetCost() float64
|
||||
GetName() string
|
||||
}
|
||||
|
||||
type subscription struct {
|
||||
userEmail string
|
||||
startDate time.Time
|
||||
interval string
|
||||
}
|
||||
|
||||
func (s subscription) GetName() string {
|
||||
return fmt.Sprintf("%s subscription", s.interval)
|
||||
}
|
||||
|
||||
func (s subscription) GetCost() float64 {
|
||||
if s.interval == "monthly" {
|
||||
return 25.00
|
||||
}
|
||||
if s.interval == "yearly" {
|
||||
return 250.00
|
||||
}
|
||||
return 0.0
|
||||
}
|
||||
|
||||
type oneTimeUsagePlan struct {
|
||||
userEmail string
|
||||
numEmailsAllowed int
|
||||
}
|
||||
|
||||
func (otup oneTimeUsagePlan) GetName() string {
|
||||
return fmt.Sprintf("one time usage plan with %v emails", otup.numEmailsAllowed)
|
||||
}
|
||||
|
||||
func (otup oneTimeUsagePlan) GetCost() float64 {
|
||||
const costPerEmail = 0.03
|
||||
return float64(otup.numEmailsAllowed) * costPerEmail
|
||||
}
|
||||
|
||||
func main() {
|
||||
test(subscription{
|
||||
userEmail: "john@example.com",
|
||||
startDate: time.Now().UTC(),
|
||||
interval: "yearly",
|
||||
},
|
||||
[]subscription{},
|
||||
1000.00,
|
||||
)
|
||||
test(subscription{
|
||||
userEmail: "jane@example.com",
|
||||
startDate: time.Now().UTC(),
|
||||
interval: "monthly",
|
||||
},
|
||||
[]subscription{
|
||||
{
|
||||
userEmail: "jane@example.com",
|
||||
startDate: time.Now().UTC().Add(-time.Hour * 24 * 7),
|
||||
interval: "monthly",
|
||||
},
|
||||
{
|
||||
userEmail: "jane@example.com",
|
||||
startDate: time.Now().UTC().Add(-time.Hour * 24 * 7 * 52 * 2),
|
||||
interval: "yearly",
|
||||
},
|
||||
},
|
||||
686.20,
|
||||
)
|
||||
test(oneTimeUsagePlan{
|
||||
userEmail: "dillon@example.com",
|
||||
numEmailsAllowed: 5000,
|
||||
},
|
||||
[]oneTimeUsagePlan{},
|
||||
756.20,
|
||||
)
|
||||
test(oneTimeUsagePlan{
|
||||
userEmail: "dalton@example.com",
|
||||
numEmailsAllowed: 100000,
|
||||
},
|
||||
[]oneTimeUsagePlan{
|
||||
{
|
||||
userEmail: "dalton@example.com",
|
||||
numEmailsAllowed: 34200,
|
||||
},
|
||||
},
|
||||
32.20,
|
||||
)
|
||||
}
|
||||
|
||||
func test[T lineItem](newItem T, oldItems []T, balance float64) {
|
||||
fmt.Println(" --- ")
|
||||
fmt.Printf("Charging customer for a '%s', current balance is %v...\n", newItem.GetName(), balance)
|
||||
newItems, newBalance, err := chargeForLineItem(newItem, oldItems, balance)
|
||||
if err != nil {
|
||||
fmt.Printf("Got error: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("New balance is: %v. Total number of line items is now %v\n", newBalance, len(newItems))
|
||||
}
|
||||
12
course/15-generics/exercises/3-constraints/expected.txt
Normal file
12
course/15-generics/exercises/3-constraints/expected.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
Charging customer for a 'yearly subscription', current balance is 1000...
|
||||
New balance is: 750. Total number of line items is now 1
|
||||
---
|
||||
Charging customer for a 'monthly subscription', current balance is 686.2...
|
||||
New balance is: 661.2. Total number of line items is now 3
|
||||
---
|
||||
Charging customer for a 'one time usage plan with 5000 emails', current balance is 756.2...
|
||||
New balance is: 606.2. Total number of line items is now 1
|
||||
---
|
||||
Charging customer for a 'one time usage plan with 100000 emails', current balance is 32.2...
|
||||
Got error: insufficient funds
|
||||
44
course/15-generics/exercises/3-constraints/readme.md
Normal file
44
course/15-generics/exercises/3-constraints/readme.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Constraints
|
||||
|
||||
Sometimes you need the logic in your generic function to know *something* about the types it operates on. The example we used in the first exercise didn't need to know *anything* about the types in the slice, so we used the built-in `any` constraint:
|
||||
|
||||
```go
|
||||
func splitAnySlice[T any](s []T) ([]T, []T) {
|
||||
mid := len(s)/2
|
||||
return s[:mid], s[mid:]
|
||||
}
|
||||
```
|
||||
|
||||
Constraints are just interfaces that allow us to write generics that only operate within the constraint of a given interface type. In the example above, the `any` constraint is the same as the empty interface because it means the type in question can be *anything*.
|
||||
|
||||
## Creating a custom constraint
|
||||
|
||||
Let's take a look at the example of a `concat` function. It takes a slice of values and concatenates the values into a string. This should work with *any type that can represent itself as a string*, even if it's not a string under the hood. For example, a `user` struct can have a `.String()` that returns a string with the user's name and age.
|
||||
|
||||
```go
|
||||
type stringer interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
func concat[T stringer](vals []T) string {
|
||||
result := ""
|
||||
for _, val := range vals {
|
||||
// this is where the .String() method
|
||||
// is used. That's why we need a more specific
|
||||
// constraint instead of the any constraint
|
||||
result += val.String()
|
||||
}
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
## Assignment
|
||||
|
||||
We have different kinds of "line items" that we charge our customer's credit cards for. Line items can be things like "subscriptions" or "one-time payments" for email usage.
|
||||
|
||||
Complete the `chargeForLineItem` function. First, it should check if the user has a balance with enough funds to be able to pay for the cost of the `newItem`. If they don't then return an "insufficient funds" error.
|
||||
|
||||
If they *do* have enough funds:
|
||||
|
||||
* Add the line item to the user's history by appending the `newItem` to the slice of `oldItems`. This new slice is your first return value.
|
||||
* Calculate the user's new balance by subtracting the cost of the new item from their balance. This is your second return value.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"question": "Why might you create an interface using a type list?",
|
||||
"answers": [
|
||||
"You know exactly which types satisfy your interface",
|
||||
"It's too much trouble to define the methods required by your interface"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# Interface type lists
|
||||
|
||||
When generics were released, a new way of writing interfaces was also released at the same time!
|
||||
|
||||
We can now simply list a bunch of types to get a new interface/constraint.
|
||||
|
||||
```go
|
||||
// Ordered is a type constraint that matches any ordered type.
|
||||
// An ordered type is one that supports the <, <=, >, and >= operators.
|
||||
type Ordered interface {
|
||||
~int | ~int8 | ~int16 | ~int32 | ~int64 |
|
||||
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
|
||||
~float32 | ~float64 |
|
||||
~string
|
||||
}
|
||||
```
|
||||
103
course/15-generics/exercises/5-parametric_constraints/code.go
Normal file
103
course/15-generics/exercises/5-parametric_constraints/code.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ?
|
||||
|
||||
// don't edit below this line
|
||||
|
||||
type userBiller struct {
|
||||
Plan string
|
||||
}
|
||||
|
||||
func (ub userBiller) Charge(u user) bill {
|
||||
amount := 50.0
|
||||
if ub.Plan == "pro" {
|
||||
amount = 100.0
|
||||
}
|
||||
return bill{
|
||||
Customer: u,
|
||||
Amount: amount,
|
||||
}
|
||||
}
|
||||
|
||||
func (sb userBiller) Name() string {
|
||||
return fmt.Sprintf("%s user biller", sb.Plan)
|
||||
}
|
||||
|
||||
type orgBiller struct {
|
||||
Plan string
|
||||
}
|
||||
|
||||
func (ob orgBiller) Name() string {
|
||||
return fmt.Sprintf("%s org biller", ob.Plan)
|
||||
}
|
||||
|
||||
func (ob orgBiller) Charge(o org) bill {
|
||||
amount := 2000.0
|
||||
if ob.Plan == "pro" {
|
||||
amount = 3000.0
|
||||
}
|
||||
return bill{
|
||||
Customer: o,
|
||||
Amount: amount,
|
||||
}
|
||||
}
|
||||
|
||||
type customer interface {
|
||||
GetBillingEmail() string
|
||||
}
|
||||
|
||||
type bill struct {
|
||||
Customer customer
|
||||
Amount float64
|
||||
}
|
||||
|
||||
type user struct {
|
||||
UserEmail string
|
||||
}
|
||||
|
||||
func (u user) GetBillingEmail() string {
|
||||
return u.UserEmail
|
||||
}
|
||||
|
||||
type org struct {
|
||||
Admin user
|
||||
Name string
|
||||
}
|
||||
|
||||
func (o org) GetBillingEmail() string {
|
||||
return o.Admin.GetBillingEmail()
|
||||
}
|
||||
|
||||
func main() {
|
||||
testBiller[user](
|
||||
userBiller{Plan: "basic"},
|
||||
user{UserEmail: "joe@example.com"},
|
||||
)
|
||||
testBiller[user](
|
||||
userBiller{Plan: "basic"},
|
||||
user{UserEmail: "samuel.boggs@example.com"},
|
||||
)
|
||||
testBiller[user](
|
||||
userBiller{Plan: "pro"},
|
||||
user{UserEmail: "jade.row@example.com"},
|
||||
)
|
||||
testBiller[org](
|
||||
orgBiller{Plan: "basic"},
|
||||
org{Admin: user{UserEmail: "challis.rane@example.com"}},
|
||||
)
|
||||
testBiller[org](
|
||||
orgBiller{Plan: "pro"},
|
||||
org{Admin: user{UserEmail: "challis.rane@example.com"}},
|
||||
)
|
||||
}
|
||||
|
||||
func testBiller[C customer](b biller[C], c C) {
|
||||
fmt.Printf("Using '%s' to create a bill for '%s'\n", b.Name(), c.GetBillingEmail())
|
||||
bill := b.Charge(c)
|
||||
fmt.Printf("Bill created for %v dollars\n", bill.Amount)
|
||||
fmt.Println(" --- ")
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type biller[C customer] interface {
|
||||
Charge(C) bill
|
||||
Name() string
|
||||
}
|
||||
|
||||
// don't edit below this line
|
||||
|
||||
type userBiller struct {
|
||||
Plan string
|
||||
}
|
||||
|
||||
func (ub userBiller) Charge(u user) bill {
|
||||
amount := 50.0
|
||||
if ub.Plan == "pro" {
|
||||
amount = 100.0
|
||||
}
|
||||
return bill{
|
||||
Customer: u,
|
||||
Amount: amount,
|
||||
}
|
||||
}
|
||||
|
||||
func (sb userBiller) Name() string {
|
||||
return fmt.Sprintf("%s user biller", sb.Plan)
|
||||
}
|
||||
|
||||
type orgBiller struct {
|
||||
Plan string
|
||||
}
|
||||
|
||||
func (ob orgBiller) Name() string {
|
||||
return fmt.Sprintf("%s org biller", ob.Plan)
|
||||
}
|
||||
|
||||
func (ob orgBiller) Charge(o org) bill {
|
||||
amount := 2000.0
|
||||
if ob.Plan == "pro" {
|
||||
amount = 3000.0
|
||||
}
|
||||
return bill{
|
||||
Customer: o,
|
||||
Amount: amount,
|
||||
}
|
||||
}
|
||||
|
||||
type customer interface {
|
||||
GetBillingEmail() string
|
||||
}
|
||||
|
||||
type bill struct {
|
||||
Customer customer
|
||||
Amount float64
|
||||
}
|
||||
|
||||
type user struct {
|
||||
UserEmail string
|
||||
}
|
||||
|
||||
func (u user) GetBillingEmail() string {
|
||||
return u.UserEmail
|
||||
}
|
||||
|
||||
type org struct {
|
||||
Admin user
|
||||
Name string
|
||||
}
|
||||
|
||||
func (o org) GetBillingEmail() string {
|
||||
return o.Admin.GetBillingEmail()
|
||||
}
|
||||
|
||||
func main() {
|
||||
testBiller[user](
|
||||
userBiller{Plan: "basic"},
|
||||
user{UserEmail: "joe@example.com"},
|
||||
)
|
||||
testBiller[user](
|
||||
userBiller{Plan: "basic"},
|
||||
user{UserEmail: "samuel.boggs@example.com"},
|
||||
)
|
||||
testBiller[user](
|
||||
userBiller{Plan: "pro"},
|
||||
user{UserEmail: "jade.row@example.com"},
|
||||
)
|
||||
testBiller[org](
|
||||
orgBiller{Plan: "basic"},
|
||||
org{Admin: user{UserEmail: "challis.rane@example.com"}},
|
||||
)
|
||||
testBiller[org](
|
||||
orgBiller{Plan: "pro"},
|
||||
org{Admin: user{UserEmail: "challis.rane@example.com"}},
|
||||
)
|
||||
}
|
||||
|
||||
func testBiller[C customer](b biller[C], c C) {
|
||||
fmt.Printf("Using '%s' to create a bill for '%s'\n", b.Name(), c.GetBillingEmail())
|
||||
bill := b.Charge(c)
|
||||
fmt.Printf("Bill created for %v dollars\n", bill.Amount)
|
||||
fmt.Println(" --- ")
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
Using 'basic user biller' to create a bill for 'joe@example.com'
|
||||
Bill created for 50 dollars
|
||||
---
|
||||
Using 'basic user biller' to create a bill for 'samuel.boggs@example.com'
|
||||
Bill created for 50 dollars
|
||||
---
|
||||
Using 'pro user biller' to create a bill for 'jade.row@example.com'
|
||||
Bill created for 100 dollars
|
||||
---
|
||||
Using 'basic org biller' to create a bill for 'challis.rane@example.com'
|
||||
Bill created for 2000 dollars
|
||||
---
|
||||
Using 'pro org biller' to create a bill for 'challis.rane@example.com'
|
||||
Bill created for 3000 dollars
|
||||
---
|
||||
126
course/15-generics/exercises/5-parametric_constraints/readme.md
Normal file
126
course/15-generics/exercises/5-parametric_constraints/readme.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# Parametric Constraints
|
||||
|
||||
Your interface definitions, which can later be used as constraints, can accept type parameters as well.
|
||||
|
||||
```go
|
||||
// The store interface represents a store that sells products.
|
||||
// It takes a type parameter P that represents the type of products the store sells.
|
||||
type store[P product] interface {
|
||||
Sell(P)
|
||||
}
|
||||
|
||||
type product interface {
|
||||
Price() float64
|
||||
Name() string
|
||||
}
|
||||
|
||||
type book struct {
|
||||
title string
|
||||
author string
|
||||
price float64
|
||||
}
|
||||
|
||||
func (b book) Price() float64 {
|
||||
return b.price
|
||||
}
|
||||
|
||||
func (b book) Name() string {
|
||||
return fmt.Sprintf("%s by %s", b.title, b.author)
|
||||
}
|
||||
|
||||
type toy struct {
|
||||
name string
|
||||
price float64
|
||||
}
|
||||
|
||||
func (t toy) Price() float64 {
|
||||
return t.price
|
||||
}
|
||||
|
||||
func (t toy) Name() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
// The bookStore struct represents a store that sells books.
|
||||
type bookStore struct {
|
||||
booksSold []book
|
||||
}
|
||||
|
||||
// Sell adds a book to the bookStore's inventory.
|
||||
func (bs *bookStore) Sell(b book) {
|
||||
bs.booksSold = append(bs.booksSold, b)
|
||||
}
|
||||
|
||||
// The toyStore struct represents a store that sells toys.
|
||||
type toyStore struct {
|
||||
toysSold []toy
|
||||
}
|
||||
|
||||
// Sell adds a toy to the toyStore's inventory.
|
||||
func (ts *toyStore) Sell(t toy) {
|
||||
ts.toysSold = append(ts.toysSold, t)
|
||||
}
|
||||
|
||||
// sellProducts takes a store and a slice of products and sells
|
||||
// each product one by one.
|
||||
func sellProducts[P product](s store[P], products []P) {
|
||||
for _, p := range products {
|
||||
s.Sell(p)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
bs := bookStore{
|
||||
booksSold: []book{},
|
||||
}
|
||||
|
||||
// By passing in "book" as a type parameter, we can use the sellProducts function to sell books in a bookStore
|
||||
sellProducts[book](&bs, []book{
|
||||
{
|
||||
title: "The Hobbit",
|
||||
author: "J.R.R. Tolkien",
|
||||
price: 10.0,
|
||||
},
|
||||
{
|
||||
title: "The Lord of the Rings",
|
||||
author: "J.R.R. Tolkien",
|
||||
price: 20.0,
|
||||
},
|
||||
})
|
||||
fmt.Println(bs.booksSold)
|
||||
|
||||
// We can then do the same for toys
|
||||
ts := toyStore{
|
||||
toysSold: []toy{},
|
||||
}
|
||||
sellProducts[toy](&ts, []toy{
|
||||
{
|
||||
name: "Lego",
|
||||
price: 10.0,
|
||||
},
|
||||
{
|
||||
name: "Barbie",
|
||||
price: 20.0,
|
||||
},
|
||||
})
|
||||
fmt.Println(ts.toysSold)
|
||||
}
|
||||
```
|
||||
|
||||
## Assignment
|
||||
|
||||
The chief architect at Mailio has decided she wants to implement billing with generics. Specifically, she wants us to create a new `biller` interface. A `biller` is an interface that can be used to charge a `customer`, and it can also report its `name`.
|
||||
|
||||
There are two kinds of billers:
|
||||
|
||||
* `userBiller` (cheaper)
|
||||
* `orgBiller` (more expensive)
|
||||
|
||||
A `customer` is either a `user` or an `org`. A `user` will be billed with a `userBiller` and an `org` with an `orgBiller`.
|
||||
|
||||
Create the new `biller` interface. It should have 2 methods:
|
||||
|
||||
* `Charge`
|
||||
* `Name`
|
||||
|
||||
The good news is that the architect already wrote the `userBiller` and `orgBiller` types for us that fulfill this new `biller` interface. Use the definitions of those types and their methods to figure out how to write the `biller` interface definition starting on line 7.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "The name of a type parameter...",
|
||||
"answers": [
|
||||
"...can be anything, but 'T' is a common convention",
|
||||
"...can and should be whatever you want",
|
||||
"...must be 'T'"
|
||||
]
|
||||
}
|
||||
21
course/15-generics/exercises/6-type_names/readme.md
Normal file
21
course/15-generics/exercises/6-type_names/readme.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Naming Generic Types
|
||||
|
||||
Let's look at this simple example again:
|
||||
|
||||
```go
|
||||
func splitAnySlice[T any](s []T) ([]T, []T) {
|
||||
mid := len(s)/2
|
||||
return s[:mid], s[mid:]
|
||||
}
|
||||
```
|
||||
|
||||
Remember, `T` is just a variable name, We could have named the type parameter *anything*. `T` happens to be a fairly common convention for a type variable, similar to how `i` is a convention for index variables in loops.
|
||||
|
||||
This is just as valid:
|
||||
|
||||
```go
|
||||
func splitAnySlice[MyAnyType any](s []MyAnyType) ([]MyAnyType, []MyAnyType) {
|
||||
mid := len(s)/2
|
||||
return s[:mid], s[mid:]
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user