I have two structs that are very similar and I would like to create functions that can operate on both of them.
I also have two types that are defined as slices of these two structs. This example is simplified. In reality I have function receivers on those struct types too.
I can't get the generics to work mainly because of two reasons. My slice type seem to work with some operations but not all. It can be used with the len()
function for example - but it cant be indexed using brackets: []
.
The second issue is that I can't figure out how to extract the struct type from the slice type. E.g if I want to iterate over the slice and pass values to a function how do I type that function to accept the structs that the slice contains?
Can I not achieve this with generics in go? If so what would be the idiomatic way to achieve this?
Here is a simplified example of what I'm trying to achieve:
package main
import (
"fmt"
"math"
)
type Year struct {
Revenue int
Earnings int
}
type Years []Year
type Quarter struct {
Revenue int
Earnings int
}
type Quarters []Quarter
func revenueGrowth[T Year | Quarter](current, previous T) {
return (current.Revenue - previous.Revenue) / math.Abs(previous.Revenue) // ERROR: current.Revenue undefined (type T has no field or method Revenue)
}
func earningsGrowth[T Year | Quarter](current, previous T) {
return (current.Earnings - previous.Earnings) / math.Abs(previous.Earnings) // ERROR: current.Revenue undefined (type T has no field or method Revenue)
}
// printGrowthPerYear prints the revenue growth for each year
func growthPerYear[T Years | Quarters, U Year | Quarter](reports T, selector func(current, previous U) float64) float64 {
var sum float64
for i := 0; i < len(reports)-1; i++ {
sum = sum + selector(reports[i], reports[i+1]) // ERROR: invalid operation: cannot index reports (variable of type T constrained by Years|Quarters)
}
return sum
}
func printAverageGrowthOverYears[T Years | Quarters, U Year | Quarter](estimates, reports T, numOfYears int, selector func(current, previous U) float64) {
combinedReportsAndEstimates := T{} // ERROR: invalid composite literal type T
// Start by adding estimates to the combined slice
for i := 0; i < len(numOfYears); i++ {
combinedReportsAndEstimates := append(combinedReportsAndEstimates, estimates[i]) // ERROR: invalid operation: cannot index estimates (variable of type T constrained by Years|Quarters)
}
// Backfill with the latest reports
numOfReportsToBackfillWith := numOfYears - len(combinedReportsAndEstimates)
for i := 0; i < numOfReportsToBackfillWith; i++ {
combinedReportsAndEstimates[i] = reports[i] // ERROR: invalid operation: cannot index reports (variable of type T constrained by Years|Quarters)
}
growth := growthPerYear(combinedReportsAndEstimates, selector)
fmt.Println(growth / float64(numOfYears))
}
func main() {
years := Years{
{Revenue: 1000, Earnings: 100},
{Revenue: 750, Earnings: 75},
{Revenue: 500, Earnings: 50},
{Revenue: 250, Earnings: 25},
}
estimatedYears := Years{
{Revenue: 1250, Earnings: 125},
}
fmt.Println("========Years==========")
printAverageGrowthOverYears(estimatedYears, years, revenueGrowth[Year], 4) // ERROR: cannot infer U
printAverageGrowthOverYears(estimatedYears, years, earningsGrowth[Year], 4) // ERROR: cannot infer U
quarters := Quarters{
{Revenue: 100, Earnings: 10},
{Revenue: 75, Earnings: 7},
{Revenue: 50, Earnings: 5},
{Revenue: 25, Earnings: 2},
}
estimatedQuarters := Quarters{
{Revenue: 125, Earnings: 12},
}
fmt.Println("========Quarters==========")
printAverageGrowthOverYears(estimatedQuarters, quarters, revenueGrowth[Quarter], 2) // ERROR: cannot infer U
printAverageGrowthOverYears(estimatedQuarters, quarters, earningsGrowth[Quarter], 2) // ERROR: cannot infer U
}
I've tried to make the functions generic to work with both Year
, Years
, Quarter
and Quarters
.