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.