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,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))
}

View 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))
}

View 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

View 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.