mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-23 22:14:53 +02:00
717 lines
17 KiB
Go
717 lines
17 KiB
Go
// Copyright (c) 2023 - 2025 IBM Corp.
|
|
// All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package monoid
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
|
|
S "github.com/IBM/fp-go/v2/semigroup"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// assertMonoidLaws checks monoid laws for a given value
|
|
func assertMonoidLaws[A any](t *testing.T, m Monoid[A], a A) bool {
|
|
e := m.Empty()
|
|
return assert.Equal(t, a, m.Concat(a, e), "Monoid right identity") &&
|
|
assert.Equal(t, a, m.Concat(e, a), "Monoid left identity")
|
|
}
|
|
|
|
// assertMonoidLawsForAll checks monoid laws for all values in a slice
|
|
func assertMonoidLawsForAll[A any](t *testing.T, m Monoid[A], data []A) bool {
|
|
result := true
|
|
for _, value := range data {
|
|
result = result && assertMonoidLaws(t, m, value)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Test MakeMonoid creates a valid monoid
|
|
func TestMakeMonoid(t *testing.T) {
|
|
// Integer addition monoid
|
|
addMonoid := MakeMonoid(
|
|
func(a, b int) int { return a + b },
|
|
0,
|
|
)
|
|
|
|
assert.Equal(t, 8, addMonoid.Concat(5, 3))
|
|
assert.Equal(t, 0, addMonoid.Empty())
|
|
|
|
// Integer multiplication monoid
|
|
mulMonoid := MakeMonoid(
|
|
func(a, b int) int { return a * b },
|
|
1,
|
|
)
|
|
|
|
assert.Equal(t, 15, mulMonoid.Concat(5, 3))
|
|
assert.Equal(t, 1, mulMonoid.Empty())
|
|
|
|
// String concatenation monoid
|
|
stringMonoid := MakeMonoid(
|
|
func(a, b string) string { return a + b },
|
|
"",
|
|
)
|
|
|
|
assert.Equal(t, "HelloWorld", stringMonoid.Concat("Hello", "World"))
|
|
assert.Equal(t, "", stringMonoid.Empty())
|
|
}
|
|
|
|
// Test monoid laws for integer addition
|
|
func TestMonoidLaws_IntAddition(t *testing.T) {
|
|
addMonoid := MakeMonoid(
|
|
func(a, b int) int { return a + b },
|
|
0,
|
|
)
|
|
|
|
testData := []int{0, 1, -1, 5, 10, -10, 100}
|
|
|
|
// Test using helper function
|
|
assert.True(t, assertMonoidLawsForAll(t, addMonoid, testData))
|
|
|
|
// Explicit law tests
|
|
for _, x := range testData {
|
|
// Left identity: Empty() + x = x
|
|
assert.Equal(t, x, addMonoid.Concat(addMonoid.Empty(), x),
|
|
"Left identity failed for %d", x)
|
|
|
|
// Right identity: x + Empty() = x
|
|
assert.Equal(t, x, addMonoid.Concat(x, addMonoid.Empty()),
|
|
"Right identity failed for %d", x)
|
|
}
|
|
|
|
// Associativity (from Semigroup)
|
|
assert.Equal(t,
|
|
addMonoid.Concat(addMonoid.Concat(1, 2), 3),
|
|
addMonoid.Concat(1, addMonoid.Concat(2, 3)),
|
|
)
|
|
}
|
|
|
|
// Test monoid laws for integer multiplication
|
|
func TestMonoidLaws_IntMultiplication(t *testing.T) {
|
|
mulMonoid := MakeMonoid(
|
|
func(a, b int) int { return a * b },
|
|
1,
|
|
)
|
|
|
|
testData := []int{1, 2, 3, 5, 10}
|
|
|
|
assert.True(t, assertMonoidLawsForAll(t, mulMonoid, testData))
|
|
|
|
// Explicit tests
|
|
for _, x := range testData {
|
|
assert.Equal(t, x, mulMonoid.Concat(mulMonoid.Empty(), x))
|
|
assert.Equal(t, x, mulMonoid.Concat(x, mulMonoid.Empty()))
|
|
}
|
|
}
|
|
|
|
// Test monoid laws for string concatenation
|
|
func TestMonoidLaws_String(t *testing.T) {
|
|
stringMonoid := MakeMonoid(
|
|
func(a, b string) string { return a + b },
|
|
"",
|
|
)
|
|
|
|
testData := []string{"", "a", "hello", "world", "test"}
|
|
|
|
assert.True(t, assertMonoidLawsForAll(t, stringMonoid, testData))
|
|
}
|
|
|
|
// Test monoid laws for boolean AND
|
|
func TestMonoidLaws_BooleanAnd(t *testing.T) {
|
|
andMonoid := MakeMonoid(
|
|
func(a, b bool) bool { return a && b },
|
|
true,
|
|
)
|
|
|
|
testData := []bool{true, false}
|
|
|
|
assert.True(t, assertMonoidLawsForAll(t, andMonoid, testData))
|
|
|
|
// Specific tests
|
|
assert.Equal(t, true, andMonoid.Concat(true, true))
|
|
assert.Equal(t, false, andMonoid.Concat(true, false))
|
|
assert.Equal(t, false, andMonoid.Concat(false, true))
|
|
assert.Equal(t, false, andMonoid.Concat(false, false))
|
|
assert.Equal(t, true, andMonoid.Empty())
|
|
}
|
|
|
|
// Test monoid laws for boolean OR
|
|
func TestMonoidLaws_BooleanOr(t *testing.T) {
|
|
orMonoid := MakeMonoid(
|
|
func(a, b bool) bool { return a || b },
|
|
false,
|
|
)
|
|
|
|
testData := []bool{true, false}
|
|
|
|
assert.True(t, assertMonoidLawsForAll(t, orMonoid, testData))
|
|
|
|
// Specific tests
|
|
assert.Equal(t, true, orMonoid.Concat(true, true))
|
|
assert.Equal(t, true, orMonoid.Concat(true, false))
|
|
assert.Equal(t, true, orMonoid.Concat(false, true))
|
|
assert.Equal(t, false, orMonoid.Concat(false, false))
|
|
assert.Equal(t, false, orMonoid.Empty())
|
|
}
|
|
|
|
// Test Reverse swaps argument order
|
|
func TestReverse(t *testing.T) {
|
|
// Subtraction is not commutative, so reverse will give different results
|
|
subMonoid := MakeMonoid(
|
|
func(a, b int) int { return a - b },
|
|
0,
|
|
)
|
|
|
|
reversedMonoid := Reverse(subMonoid)
|
|
|
|
// Original: 10 - 3 = 7
|
|
assert.Equal(t, 7, subMonoid.Concat(10, 3))
|
|
|
|
// Reversed: 3 - 10 = -7
|
|
assert.Equal(t, -7, reversedMonoid.Concat(10, 3))
|
|
|
|
// Empty should be the same
|
|
assert.Equal(t, subMonoid.Empty(), reversedMonoid.Empty())
|
|
|
|
// Test with string concatenation
|
|
stringMonoid := MakeMonoid(
|
|
func(a, b string) string { return a + b },
|
|
"",
|
|
)
|
|
|
|
reversedString := Reverse(stringMonoid)
|
|
|
|
assert.Equal(t, "HelloWorld", stringMonoid.Concat("Hello", "World"))
|
|
assert.Equal(t, "WorldHello", reversedString.Concat("Hello", "World"))
|
|
}
|
|
|
|
// Test Reverse preserves monoid laws
|
|
func TestReverse_PreservesLaws(t *testing.T) {
|
|
addMonoid := MakeMonoid(
|
|
func(a, b int) int { return a + b },
|
|
0,
|
|
)
|
|
|
|
reversedMonoid := Reverse(addMonoid)
|
|
|
|
testData := []int{0, 1, 5, 10}
|
|
|
|
// Reversed monoid should still satisfy monoid laws
|
|
assert.True(t, assertMonoidLawsForAll(t, reversedMonoid, testData))
|
|
}
|
|
|
|
// Test ToSemigroup conversion
|
|
func TestToSemigroup(t *testing.T) {
|
|
addMonoid := MakeMonoid(
|
|
func(a, b int) int { return a + b },
|
|
0,
|
|
)
|
|
|
|
sg := ToSemigroup(addMonoid)
|
|
|
|
// Should work as a semigroup
|
|
assert.Equal(t, 8, sg.Concat(5, 3))
|
|
assert.Equal(t, 15, sg.Concat(10, 5))
|
|
|
|
// Verify it's the same underlying operation
|
|
assert.Equal(t, addMonoid.Concat(5, 3), sg.Concat(5, 3))
|
|
}
|
|
|
|
// Test ConcatAll with various inputs
|
|
func TestConcatAll(t *testing.T) {
|
|
addMonoid := MakeMonoid(
|
|
func(a, b int) int { return a + b },
|
|
0,
|
|
)
|
|
|
|
concatAll := ConcatAll(addMonoid)
|
|
|
|
tests := []struct {
|
|
name string
|
|
input []int
|
|
expected int
|
|
}{
|
|
{"empty slice", []int{}, 0},
|
|
{"single element", []int{5}, 5},
|
|
{"multiple elements", []int{1, 2, 3, 4, 5}, 15},
|
|
{"with zeros", []int{0, 5, 0, 3}, 8},
|
|
{"negative numbers", []int{-1, -2, -3}, -6},
|
|
{"mixed signs", []int{10, -5, 3, -2}, 6},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := concatAll(tt.input)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test ConcatAll with multiplication
|
|
func TestConcatAll_Multiplication(t *testing.T) {
|
|
mulMonoid := MakeMonoid(
|
|
func(a, b int) int { return a * b },
|
|
1,
|
|
)
|
|
|
|
concatAll := ConcatAll(mulMonoid)
|
|
|
|
tests := []struct {
|
|
name string
|
|
input []int
|
|
expected int
|
|
}{
|
|
{"empty slice", []int{}, 1},
|
|
{"single element", []int{5}, 5},
|
|
{"multiple elements", []int{2, 3, 4}, 24},
|
|
{"with one", []int{1, 5, 1, 3}, 15},
|
|
{"with zero", []int{2, 0, 3}, 0},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := concatAll(tt.input)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test ConcatAll with strings
|
|
func TestConcatAll_String(t *testing.T) {
|
|
stringMonoid := MakeMonoid(
|
|
func(a, b string) string { return a + b },
|
|
"",
|
|
)
|
|
|
|
concatAll := ConcatAll(stringMonoid)
|
|
|
|
tests := []struct {
|
|
name string
|
|
input []string
|
|
expected string
|
|
}{
|
|
{"empty slice", []string{}, ""},
|
|
{"single element", []string{"hello"}, "hello"},
|
|
{"multiple elements", []string{"Hello", " ", "World", "!"}, "Hello World!"},
|
|
{"with empty strings", []string{"a", "", "b", "", "c"}, "abc"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := concatAll(tt.input)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test Fold (alias for ConcatAll)
|
|
func TestFold(t *testing.T) {
|
|
addMonoid := MakeMonoid(
|
|
func(a, b int) int { return a + b },
|
|
0,
|
|
)
|
|
|
|
fold := Fold(addMonoid)
|
|
concatAll := ConcatAll(addMonoid)
|
|
|
|
input := []int{1, 2, 3, 4, 5}
|
|
|
|
// Fold and ConcatAll should produce the same result
|
|
assert.Equal(t, concatAll(input), fold(input))
|
|
assert.Equal(t, 15, fold(input))
|
|
}
|
|
|
|
// Test GenericConcatAll with custom slice type
|
|
func TestGenericConcatAll(t *testing.T) {
|
|
type IntSlice []int
|
|
|
|
addMonoid := MakeMonoid(
|
|
func(a, b int) int { return a + b },
|
|
0,
|
|
)
|
|
|
|
concatAll := GenericConcatAll[IntSlice](addMonoid)
|
|
|
|
tests := []struct {
|
|
name string
|
|
input IntSlice
|
|
expected int
|
|
}{
|
|
{"empty slice", IntSlice{}, 0},
|
|
{"single element", IntSlice{5}, 5},
|
|
{"multiple elements", IntSlice{1, 2, 3, 4, 5}, 15},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := concatAll(tt.input)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test FunctionMonoid
|
|
func TestFunctionMonoid(t *testing.T) {
|
|
// Create a monoid for functions that return integers
|
|
intAddMonoid := MakeMonoid(
|
|
func(a, b int) int { return a + b },
|
|
0,
|
|
)
|
|
|
|
funcMonoid := FunctionMonoid[string](intAddMonoid)
|
|
|
|
// Create some functions
|
|
f1 := func(s string) int { return len(s) }
|
|
f2 := func(s string) int { return len(s) * 2 }
|
|
f3 := func(s string) int { return 10 }
|
|
|
|
// Combine functions
|
|
combined := funcMonoid.Concat(f1, f2)
|
|
|
|
// Test combined function
|
|
assert.Equal(t, 15, combined("hello")) // len("hello") + len("hello")*2 = 5 + 10 = 15
|
|
assert.Equal(t, 9, combined("abc")) // len("abc") + len("abc")*2 = 3 + 6 = 9
|
|
|
|
// Test with three functions
|
|
combined3 := funcMonoid.Concat(funcMonoid.Concat(f1, f2), f3)
|
|
assert.Equal(t, 25, combined3("hello")) // 5 + 10 + 10 = 25
|
|
|
|
// Test empty function
|
|
emptyFunc := funcMonoid.Empty()
|
|
assert.Equal(t, 0, emptyFunc("anything"))
|
|
assert.Equal(t, 0, emptyFunc(""))
|
|
|
|
// Test identity laws
|
|
input := "test"
|
|
assert.Equal(t, f1(input), funcMonoid.Concat(funcMonoid.Empty(), f1)(input))
|
|
assert.Equal(t, f1(input), funcMonoid.Concat(f1, funcMonoid.Empty())(input))
|
|
}
|
|
|
|
// Test FunctionMonoid with different types
|
|
func TestFunctionMonoid_DifferentTypes(t *testing.T) {
|
|
// Monoid for functions that return booleans (OR)
|
|
boolOrMonoid := MakeMonoid(
|
|
func(a, b bool) bool { return a || b },
|
|
false,
|
|
)
|
|
|
|
funcMonoid := FunctionMonoid[int](boolOrMonoid)
|
|
|
|
isEven := func(n int) bool { return n%2 == 0 }
|
|
isPositive := func(n int) bool { return n > 0 }
|
|
|
|
// Combine: returns true if number is even OR positive
|
|
combined := funcMonoid.Concat(isEven, isPositive)
|
|
|
|
assert.True(t, combined(4)) // even and positive
|
|
assert.True(t, combined(3)) // odd but positive
|
|
assert.True(t, combined(-2)) // even but negative
|
|
assert.False(t, combined(-3)) // odd and negative
|
|
}
|
|
|
|
// Test ApplicativeMonoid with a simple applicative
|
|
func TestApplicativeMonoid(t *testing.T) {
|
|
// Simple Option-like type for testing
|
|
type Option[A any] struct {
|
|
value *A
|
|
}
|
|
|
|
some := func(a int) Option[int] {
|
|
return Option[int]{value: &a}
|
|
}
|
|
|
|
none := func() Option[int] {
|
|
return Option[int]{value: nil}
|
|
}
|
|
|
|
fmap := func(opt Option[int], f func(int) func(int) int) Option[func(int) int] {
|
|
if opt.value == nil {
|
|
return Option[func(int) int]{value: nil}
|
|
}
|
|
fn := f(*opt.value)
|
|
return Option[func(int) int]{value: &fn}
|
|
}
|
|
|
|
fap := func(optF Option[func(int) int], opt Option[int]) Option[int] {
|
|
if optF.value == nil || opt.value == nil {
|
|
return none()
|
|
}
|
|
result := (*optF.value)(*opt.value)
|
|
return some(result)
|
|
}
|
|
|
|
intAddMonoid := MakeMonoid(
|
|
func(a, b int) int { return a + b },
|
|
0,
|
|
)
|
|
|
|
optMonoid := ApplicativeMonoid(some, fmap, fap, intAddMonoid)
|
|
|
|
// Test concat of Some values
|
|
opt1 := some(5)
|
|
opt2 := some(3)
|
|
result := optMonoid.Concat(opt1, opt2)
|
|
assert.NotNil(t, result.value)
|
|
assert.Equal(t, 8, *result.value)
|
|
|
|
// Test empty
|
|
empty := optMonoid.Empty()
|
|
assert.NotNil(t, empty.value)
|
|
assert.Equal(t, 0, *empty.value)
|
|
|
|
// Test identity laws
|
|
assert.Equal(t, opt1, optMonoid.Concat(optMonoid.Empty(), opt1))
|
|
assert.Equal(t, opt1, optMonoid.Concat(opt1, optMonoid.Empty()))
|
|
}
|
|
|
|
// Test AlternativeMonoid
|
|
func TestAlternativeMonoid(t *testing.T) {
|
|
// Simple Option-like type for testing
|
|
type Option[A any] struct {
|
|
value *A
|
|
}
|
|
|
|
some := func(a int) Option[int] {
|
|
return Option[int]{value: &a}
|
|
}
|
|
|
|
none := func() Option[int] {
|
|
return Option[int]{value: nil}
|
|
}
|
|
|
|
fmap := func(opt Option[int], f func(int) func(int) int) Option[func(int) int] {
|
|
if opt.value == nil {
|
|
return Option[func(int) int]{value: nil}
|
|
}
|
|
fn := f(*opt.value)
|
|
return Option[func(int) int]{value: &fn}
|
|
}
|
|
|
|
fap := func(optF Option[func(int) int], opt Option[int]) Option[int] {
|
|
if optF.value == nil || opt.value == nil {
|
|
return none()
|
|
}
|
|
result := (*optF.value)(*opt.value)
|
|
return some(result)
|
|
}
|
|
|
|
falt := func(first Option[int], second func() Option[int]) Option[int] {
|
|
if first.value != nil {
|
|
return first
|
|
}
|
|
return second()
|
|
}
|
|
|
|
intAddMonoid := MakeMonoid(
|
|
func(a, b int) int { return a + b },
|
|
0,
|
|
)
|
|
|
|
optMonoid := AlternativeMonoid(some, fmap, fap, falt, intAddMonoid)
|
|
|
|
// Test concat of Some values - should add them
|
|
opt1 := some(5)
|
|
opt2 := some(3)
|
|
result := optMonoid.Concat(opt1, opt2)
|
|
assert.NotNil(t, result.value)
|
|
assert.Equal(t, 8, *result.value)
|
|
|
|
// Test concat with None - should fall back
|
|
result2 := optMonoid.Concat(none(), opt2)
|
|
assert.NotNil(t, result2.value)
|
|
assert.Equal(t, 3, *result2.value)
|
|
|
|
// Test concat: first Some wins in alternative
|
|
result3 := optMonoid.Concat(opt1, opt2)
|
|
assert.NotNil(t, result3.value)
|
|
// The result should be 8 (5 + 3) from applicative behavior
|
|
|
|
// Test empty
|
|
empty := optMonoid.Empty()
|
|
assert.NotNil(t, empty.value)
|
|
assert.Equal(t, 0, *empty.value)
|
|
|
|
// Test identity laws
|
|
assert.Equal(t, opt1, optMonoid.Concat(optMonoid.Empty(), opt1))
|
|
assert.Equal(t, opt1, optMonoid.Concat(opt1, optMonoid.Empty()))
|
|
}
|
|
|
|
// Test AltMonoid
|
|
func TestAltMonoid(t *testing.T) {
|
|
// Simple Option-like type
|
|
type Option[A any] struct {
|
|
value *A
|
|
}
|
|
|
|
some := func(a int) Option[int] {
|
|
return Option[int]{value: &a}
|
|
}
|
|
|
|
none := func() Option[int] {
|
|
return Option[int]{value: nil}
|
|
}
|
|
|
|
falt := func(first Option[int], second func() Option[int]) Option[int] {
|
|
if first.value != nil {
|
|
return first
|
|
}
|
|
return second()
|
|
}
|
|
|
|
optMonoid := AltMonoid(none, falt)
|
|
|
|
// Test concat: first Some wins
|
|
opt1 := some(5)
|
|
opt2 := some(3)
|
|
result := optMonoid.Concat(opt1, opt2)
|
|
assert.NotNil(t, result.value)
|
|
assert.Equal(t, 5, *result.value)
|
|
|
|
// Test concat: None falls back to second
|
|
result2 := optMonoid.Concat(none(), opt2)
|
|
assert.NotNil(t, result2.value)
|
|
assert.Equal(t, 3, *result2.value)
|
|
|
|
// Test concat: both None
|
|
result3 := optMonoid.Concat(none(), none())
|
|
assert.Nil(t, result3.value)
|
|
|
|
// Test empty
|
|
empty := optMonoid.Empty()
|
|
assert.Nil(t, empty.value)
|
|
}
|
|
|
|
// Test monoid with max operation
|
|
func TestMonoid_Max(t *testing.T) {
|
|
maxMonoid := MakeMonoid(
|
|
func(a, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
},
|
|
math.MinInt,
|
|
)
|
|
|
|
numbers := []int{3, 7, 2, 9, 1, 5}
|
|
maximum := ConcatAll(maxMonoid)(numbers)
|
|
assert.Equal(t, 9, maximum)
|
|
|
|
// Empty slice returns identity (MinInt)
|
|
emptyMax := ConcatAll(maxMonoid)([]int{})
|
|
assert.Equal(t, math.MinInt, emptyMax)
|
|
}
|
|
|
|
// Test monoid with min operation
|
|
func TestMonoid_Min(t *testing.T) {
|
|
minMonoid := MakeMonoid(
|
|
func(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
},
|
|
math.MaxInt,
|
|
)
|
|
|
|
numbers := []int{3, 7, 2, 9, 1, 5}
|
|
minimum := ConcatAll(minMonoid)(numbers)
|
|
assert.Equal(t, 1, minimum)
|
|
|
|
// Empty slice returns identity (MaxInt)
|
|
emptyMin := ConcatAll(minMonoid)([]int{})
|
|
assert.Equal(t, math.MaxInt, emptyMin)
|
|
}
|
|
|
|
// Test monoid with list concatenation
|
|
func TestMonoid_ListConcat(t *testing.T) {
|
|
listMonoid := MakeMonoid(
|
|
func(a, b []int) []int {
|
|
result := make([]int, len(a)+len(b))
|
|
copy(result, a)
|
|
copy(result[len(a):], b)
|
|
return result
|
|
},
|
|
[]int{},
|
|
)
|
|
|
|
lists := [][]int{{1, 2}, {3, 4}, {5}}
|
|
flattened := ConcatAll(listMonoid)(lists)
|
|
assert.Equal(t, []int{1, 2, 3, 4, 5}, flattened)
|
|
|
|
// Empty slice returns empty list
|
|
emptyList := ConcatAll(listMonoid)([][]int{})
|
|
assert.Equal(t, []int{}, emptyList)
|
|
|
|
// Test identity laws
|
|
list := []int{1, 2, 3}
|
|
assert.Equal(t, list, listMonoid.Concat(listMonoid.Empty(), list))
|
|
assert.Equal(t, list, listMonoid.Concat(list, listMonoid.Empty()))
|
|
}
|
|
|
|
// Test that monoid interface is properly implemented
|
|
func TestMonoidInterface(t *testing.T) {
|
|
addMonoid := MakeMonoid(
|
|
func(a, b int) int { return a + b },
|
|
0,
|
|
)
|
|
|
|
// Verify it implements Monoid interface
|
|
var _ Monoid[int] = addMonoid
|
|
|
|
// Verify it also implements Semigroup interface (through embedding)
|
|
var _ S.Semigroup[int] = addMonoid
|
|
}
|
|
|
|
// Benchmark ConcatAll
|
|
func BenchmarkConcatAll(b *testing.B) {
|
|
addMonoid := MakeMonoid(
|
|
func(a, b int) int { return a + b },
|
|
0,
|
|
)
|
|
|
|
concatAll := ConcatAll(addMonoid)
|
|
numbers := make([]int, 1000)
|
|
for i := range numbers {
|
|
numbers[i] = i
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
_ = concatAll(numbers)
|
|
}
|
|
}
|
|
|
|
// Benchmark FunctionMonoid
|
|
func BenchmarkFunctionMonoid(b *testing.B) {
|
|
intAddMonoid := MakeMonoid(
|
|
func(a, b int) int { return a + b },
|
|
0,
|
|
)
|
|
|
|
funcMonoid := FunctionMonoid[string](intAddMonoid)
|
|
|
|
f1 := func(s string) int { return len(s) }
|
|
f2 := func(s string) int { return len(s) * 2 }
|
|
|
|
combined := funcMonoid.Concat(f1, f2)
|
|
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
_ = combined("benchmark")
|
|
}
|
|
}
|