You could define the Monoid interface as the following generic interface:
type Monoid[T any] interface {
Identity() T
Combine(T) T
}
Combine() corresponds to the monoid's (associative) binary operation, and Identity() returns the monoid's identity element. You called them op() and ide(), respectively.
CombineMonoids is a generic convenience function for combining an arbitrary number of monoids into a single monoid:
func CombineMonoids[M Monoid[M]](ms ...M) M {
var res M
res = res.Identity()
for _, m := range ms {
res = res.Combine(m)
}
return res
}
The constraint Monoid[M] on the type parameter M – i.e., M Monoid[M] – means that it works with any type M that has the methods Identity() M and Combine(M) M, i.e., it satisfies Monoid[M].
Examples
For example, here are the types SumMonoid and MaxMonoid that correspond to your monoid_sum and monoid_max, respectively.
SumMonoid
type SumMonoid int
func (s SumMonoid) Identity() SumMonoid {
return 0
}
func (s SumMonoid) Combine(r SumMonoid) SumMonoid {
return s + r
}
The type SumMonoid satisfies the interface Monoid[SumMonoid].
func TestSumMonoid(t *testing.T) {
a := SumMonoid(3)
b := SumMonoid(7)
c := SumMonoid(10)
want := SumMonoid(20)
got := CombineMonoids(a, b, c)
if got != want {
t.Fail()
}
}
MaxMonoid
type MaxMonoid int
func (m MaxMonoid) Identity() MaxMonoid {
return math.MinInt
}
func (m MaxMonoid) Combine(n MaxMonoid) MaxMonoid {
if m > n {
return m
} else {
return n
}
}
The type MaxMonoid satisfies the interface Monoid[MaxMonoid].
func TestMaxMonoid(t *testing.T) {
a := MaxMonoid(-100)
b := MaxMonoid(500)
c := MaxMonoid(100)
want := MaxMonoid(500)
got := CombineMonoids(a, b, c)
if got != want {
t.Fail()
}
}
What about a non-generic Monoid interface?
Similarly to what you suggested, you could, in principle, define the Monoid as a non-generic interface:
type Monoid interface {
Identity() Monoid
Combine(Monoid) Monoid
}
The problem is that the original type of the particular monoid (e.g., SumMonoid or MaxMonoid) will be erased to just the interface Monoid.
Combining a SumMonoid with a MaxMonoid makes no sense – you would place type assertions inside of the Combine method implementation that will lead to a panic if the dynamic type of the two monoids to be combined differ. So, the solution based on the generic interface seems to be more robust.