mirror of
https://github.com/IBM/fp-go.git
synced 2026-01-15 00:53:10 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f2e76dd94 | ||
|
|
77965a12ff |
230
v2/pair/monoid.go
Normal file
230
v2/pair/monoid.go
Normal file
@@ -0,0 +1,230 @@
|
||||
// Copyright (c) 2024 - 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 pair
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
M "github.com/IBM/fp-go/v2/monoid"
|
||||
)
|
||||
|
||||
// ApplicativeMonoid creates a monoid for [Pair] using applicative functor operations on the tail.
|
||||
//
|
||||
// This is an alias for [ApplicativeMonoidTail], which lifts the right (tail) monoid into the
|
||||
// Pair applicative functor. The left monoid provides the semigroup for combining head values
|
||||
// during applicative operations.
|
||||
//
|
||||
// IMPORTANT: The three monoid constructors (ApplicativeMonoid/ApplicativeMonoidTail and
|
||||
// ApplicativeMonoidHead) produce DIFFERENT results:
|
||||
// - ApplicativeMonoidTail: Combines head values in REVERSE order (right-to-left)
|
||||
// - ApplicativeMonoidHead: Combines tail values in REVERSE order (right-to-left)
|
||||
// - The "focused" component (tail for Tail, head for Head) combines in normal order (left-to-right)
|
||||
//
|
||||
// This difference is significant for non-commutative operations like string concatenation.
|
||||
//
|
||||
// Parameters:
|
||||
// - l: A monoid for the head (left) values of type L
|
||||
// - r: A monoid for the tail (right) values of type R
|
||||
//
|
||||
// Returns:
|
||||
// - A Monoid[Pair[L, R]] that combines pairs using applicative operations on the tail
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import (
|
||||
// N "github.com/IBM/fp-go/v2/number"
|
||||
// S "github.com/IBM/fp-go/v2/string"
|
||||
// )
|
||||
//
|
||||
// intAdd := N.MonoidSum[int]()
|
||||
// strConcat := S.Monoid
|
||||
//
|
||||
// pairMonoid := pair.ApplicativeMonoid(intAdd, strConcat)
|
||||
//
|
||||
// p1 := pair.MakePair(10, "foo")
|
||||
// p2 := pair.MakePair(20, "bar")
|
||||
//
|
||||
// result := pairMonoid.Concat(p1, p2)
|
||||
// // result is Pair[int, string]{30, "foobar"}
|
||||
// // Note: head combines normally (10+20), tail combines normally ("foo"+"bar")
|
||||
//
|
||||
// empty := pairMonoid.Empty()
|
||||
// // empty is Pair[int, string]{0, ""}
|
||||
//
|
||||
//go:inline
|
||||
func ApplicativeMonoid[L, R any](l M.Monoid[L], r M.Monoid[R]) M.Monoid[Pair[L, R]] {
|
||||
return ApplicativeMonoidTail(l, r)
|
||||
}
|
||||
|
||||
// ApplicativeMonoidTail creates a monoid for [Pair] by lifting the tail monoid into the applicative functor.
|
||||
//
|
||||
// This function constructs a monoid using the applicative structure of Pair, focusing on
|
||||
// the tail (right) value. The head values are combined using the left monoid's semigroup
|
||||
// operation during applicative application.
|
||||
//
|
||||
// CRITICAL BEHAVIOR: Due to the applicative functor implementation, the HEAD values are
|
||||
// combined in REVERSE order (right-to-left), while TAIL values combine in normal order
|
||||
// (left-to-right). This matters for non-commutative operations:
|
||||
//
|
||||
// strConcat := S.Monoid
|
||||
// pairMonoid := pair.ApplicativeMonoidTail(strConcat, strConcat)
|
||||
// p1 := pair.MakePair("hello", "foo")
|
||||
// p2 := pair.MakePair(" world", "bar")
|
||||
// result := pairMonoid.Concat(p1, p2)
|
||||
// // result is Pair[string, string]{" worldhello", "foobar"}
|
||||
// // ^^^^^^^^^^^^^^ ^^^^^^
|
||||
// // REVERSED! normal
|
||||
//
|
||||
// The resulting monoid satisfies the standard monoid laws:
|
||||
// - Associativity: Concat(Concat(p1, p2), p3) = Concat(p1, Concat(p2, p3))
|
||||
// - Left identity: Concat(Empty(), p) = p
|
||||
// - Right identity: Concat(p, Empty()) = p
|
||||
//
|
||||
// Parameters:
|
||||
// - l: A monoid for the head (left) values of type L
|
||||
// - r: A monoid for the tail (right) values of type R
|
||||
//
|
||||
// Returns:
|
||||
// - A Monoid[Pair[L, R]] that combines pairs component-wise
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import (
|
||||
// N "github.com/IBM/fp-go/v2/number"
|
||||
// M "github.com/IBM/fp-go/v2/monoid"
|
||||
// )
|
||||
//
|
||||
// intAdd := N.MonoidSum[int]()
|
||||
// intMul := N.MonoidProduct[int]()
|
||||
//
|
||||
// pairMonoid := pair.ApplicativeMonoidTail(intAdd, intMul)
|
||||
//
|
||||
// p1 := pair.MakePair(5, 3)
|
||||
// p2 := pair.MakePair(10, 4)
|
||||
//
|
||||
// result := pairMonoid.Concat(p1, p2)
|
||||
// // result is Pair[int, int]{15, 12} (5+10, 3*4)
|
||||
// // Note: Addition is commutative, so order doesn't matter for head
|
||||
//
|
||||
// empty := pairMonoid.Empty()
|
||||
// // empty is Pair[int, int]{0, 1}
|
||||
//
|
||||
// Example with different types:
|
||||
//
|
||||
// import S "github.com/IBM/fp-go/v2/string"
|
||||
//
|
||||
// boolAnd := M.MakeMonoid(func(a, b bool) bool { return a && b }, true)
|
||||
// strConcat := S.Monoid
|
||||
//
|
||||
// pairMonoid := pair.ApplicativeMonoidTail(boolAnd, strConcat)
|
||||
//
|
||||
// p1 := pair.MakePair(true, "hello")
|
||||
// p2 := pair.MakePair(true, " world")
|
||||
//
|
||||
// result := pairMonoid.Concat(p1, p2)
|
||||
// // result is Pair[bool, string]{true, "hello world"}
|
||||
// // Note: Boolean AND is commutative, so order doesn't matter for head
|
||||
//
|
||||
//go:inline
|
||||
func ApplicativeMonoidTail[L, R any](l M.Monoid[L], r M.Monoid[R]) M.Monoid[Pair[L, R]] {
|
||||
return M.ApplicativeMonoid(
|
||||
FromHead[R](l.Empty()),
|
||||
MonadMapTail[L, R, func(R) R],
|
||||
F.Bind1of3(MonadApTail[L, R, R])(l),
|
||||
r)
|
||||
}
|
||||
|
||||
// ApplicativeMonoidHead creates a monoid for [Pair] by lifting the head monoid into the applicative functor.
|
||||
//
|
||||
// This function constructs a monoid using the applicative structure of Pair, focusing on
|
||||
// the head (left) value. The tail values are combined using the right monoid's semigroup
|
||||
// operation during applicative application.
|
||||
//
|
||||
// This is the dual of [ApplicativeMonoidTail], operating on the head instead of the tail.
|
||||
//
|
||||
// CRITICAL BEHAVIOR: Due to the applicative functor implementation, the TAIL values are
|
||||
// combined in REVERSE order (right-to-left), while HEAD values combine in normal order
|
||||
// (left-to-right). This is the opposite of ApplicativeMonoidTail:
|
||||
//
|
||||
// strConcat := S.Monoid
|
||||
// pairMonoid := pair.ApplicativeMonoidHead(strConcat, strConcat)
|
||||
// p1 := pair.MakePair("hello", "foo")
|
||||
// p2 := pair.MakePair(" world", "bar")
|
||||
// result := pairMonoid.Concat(p1, p2)
|
||||
// // result is Pair[string, string]{"hello world", "barfoo"}
|
||||
// // ^^^^^^^^^^^^ ^^^^^^^^
|
||||
// // normal REVERSED!
|
||||
//
|
||||
// The resulting monoid satisfies the standard monoid laws:
|
||||
// - Associativity: Concat(Concat(p1, p2), p3) = Concat(p1, Concat(p2, p3))
|
||||
// - Left identity: Concat(Empty(), p) = p
|
||||
// - Right identity: Concat(p, Empty()) = p
|
||||
//
|
||||
// Parameters:
|
||||
// - l: A monoid for the head (left) values of type L
|
||||
// - r: A monoid for the tail (right) values of type R
|
||||
//
|
||||
// Returns:
|
||||
// - A Monoid[Pair[L, R]] that combines pairs component-wise
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import (
|
||||
// N "github.com/IBM/fp-go/v2/number"
|
||||
// M "github.com/IBM/fp-go/v2/monoid"
|
||||
// )
|
||||
//
|
||||
// intMul := N.MonoidProduct[int]()
|
||||
// intAdd := N.MonoidSum[int]()
|
||||
//
|
||||
// pairMonoid := pair.ApplicativeMonoidHead(intMul, intAdd)
|
||||
//
|
||||
// p1 := pair.MakePair(3, 5)
|
||||
// p2 := pair.MakePair(4, 10)
|
||||
//
|
||||
// result := pairMonoid.Concat(p1, p2)
|
||||
// // result is Pair[int, int]{12, 15} (3*4, 5+10)
|
||||
// // Note: Both operations are commutative, so order doesn't matter
|
||||
//
|
||||
// empty := pairMonoid.Empty()
|
||||
// // empty is Pair[int, int]{1, 0}
|
||||
//
|
||||
// Example comparing Head vs Tail with non-commutative operations:
|
||||
//
|
||||
// import S "github.com/IBM/fp-go/v2/string"
|
||||
//
|
||||
// strConcat := S.Monoid
|
||||
//
|
||||
// // Using ApplicativeMonoidHead - tail values REVERSED
|
||||
// headMonoid := pair.ApplicativeMonoidHead(strConcat, strConcat)
|
||||
// p1 := pair.MakePair("hello", "foo")
|
||||
// p2 := pair.MakePair(" world", "bar")
|
||||
// result := headMonoid.Concat(p1, p2)
|
||||
// // result is Pair[string, string]{"hello world", "barfoo"}
|
||||
//
|
||||
// // Using ApplicativeMonoidTail - head values REVERSED
|
||||
// tailMonoid := pair.ApplicativeMonoidTail(strConcat, strConcat)
|
||||
// result2 := tailMonoid.Concat(p1, p2)
|
||||
// // result2 is Pair[string, string]{" worldhello", "foobar"}
|
||||
// // DIFFERENT result! Head and tail are swapped in their reversal behavior
|
||||
//
|
||||
//go:inline
|
||||
func ApplicativeMonoidHead[L, R any](l M.Monoid[L], r M.Monoid[R]) M.Monoid[Pair[L, R]] {
|
||||
return M.ApplicativeMonoid(
|
||||
FromTail[L](r.Empty()),
|
||||
MonadMapHead[R, L, func(L) L],
|
||||
F.Bind1of3(MonadApHead[R, L, L])(r),
|
||||
l)
|
||||
}
|
||||
497
v2/pair/monoid_test.go
Normal file
497
v2/pair/monoid_test.go
Normal file
@@ -0,0 +1,497 @@
|
||||
// Copyright (c) 2024 - 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 pair
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
M "github.com/IBM/fp-go/v2/monoid"
|
||||
N "github.com/IBM/fp-go/v2/number"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestApplicativeMonoidTail tests the ApplicativeMonoidTail implementation
|
||||
func TestApplicativeMonoidTail(t *testing.T) {
|
||||
t.Run("integer addition and string concatenation", func(t *testing.T) {
|
||||
intAdd := N.MonoidSum[int]()
|
||||
strConcat := S.Monoid
|
||||
|
||||
pairMonoid := ApplicativeMonoidTail(intAdd, strConcat)
|
||||
|
||||
p1 := MakePair(5, "hello")
|
||||
p2 := MakePair(3, " world")
|
||||
|
||||
result := pairMonoid.Concat(p1, p2)
|
||||
assert.Equal(t, 8, Head(result))
|
||||
assert.Equal(t, "hello world", Tail(result))
|
||||
})
|
||||
|
||||
t.Run("integer multiplication and addition", func(t *testing.T) {
|
||||
intMul := N.MonoidProduct[int]()
|
||||
intAdd := N.MonoidSum[int]()
|
||||
|
||||
pairMonoid := ApplicativeMonoidTail(intMul, intAdd)
|
||||
|
||||
p1 := MakePair(3, 5)
|
||||
p2 := MakePair(4, 10)
|
||||
|
||||
result := pairMonoid.Concat(p1, p2)
|
||||
assert.Equal(t, 12, Head(result)) // 3 * 4
|
||||
assert.Equal(t, 15, Tail(result)) // 5 + 10
|
||||
})
|
||||
|
||||
t.Run("boolean AND and OR", func(t *testing.T) {
|
||||
boolAnd := M.MakeMonoid(func(a, b bool) bool { return a && b }, true)
|
||||
boolOr := M.MakeMonoid(func(a, b bool) bool { return a || b }, false)
|
||||
|
||||
pairMonoid := ApplicativeMonoidTail(boolAnd, boolOr)
|
||||
|
||||
p1 := MakePair(true, false)
|
||||
p2 := MakePair(true, true)
|
||||
|
||||
result := pairMonoid.Concat(p1, p2)
|
||||
assert.Equal(t, true, Head(result)) // true && true
|
||||
assert.Equal(t, true, Tail(result)) // false || true
|
||||
})
|
||||
|
||||
t.Run("empty value", func(t *testing.T) {
|
||||
intAdd := N.MonoidSum[int]()
|
||||
strConcat := S.Monoid
|
||||
|
||||
pairMonoid := ApplicativeMonoidTail(intAdd, strConcat)
|
||||
|
||||
empty := pairMonoid.Empty()
|
||||
assert.Equal(t, 0, Head(empty))
|
||||
assert.Equal(t, "", Tail(empty))
|
||||
})
|
||||
|
||||
t.Run("left identity law", func(t *testing.T) {
|
||||
intAdd := N.MonoidSum[int]()
|
||||
strConcat := S.Monoid
|
||||
|
||||
pairMonoid := ApplicativeMonoidTail(intAdd, strConcat)
|
||||
|
||||
p := MakePair(5, "test")
|
||||
result := pairMonoid.Concat(pairMonoid.Empty(), p)
|
||||
|
||||
assert.Equal(t, p, result)
|
||||
})
|
||||
|
||||
t.Run("right identity law", func(t *testing.T) {
|
||||
intAdd := N.MonoidSum[int]()
|
||||
strConcat := S.Monoid
|
||||
|
||||
pairMonoid := ApplicativeMonoidTail(intAdd, strConcat)
|
||||
|
||||
p := MakePair(5, "test")
|
||||
result := pairMonoid.Concat(p, pairMonoid.Empty())
|
||||
|
||||
assert.Equal(t, p, result)
|
||||
})
|
||||
|
||||
t.Run("associativity law", func(t *testing.T) {
|
||||
intAdd := N.MonoidSum[int]()
|
||||
strConcat := S.Monoid
|
||||
|
||||
pairMonoid := ApplicativeMonoidTail(intAdd, strConcat)
|
||||
|
||||
p1 := MakePair(1, "a")
|
||||
p2 := MakePair(2, "b")
|
||||
p3 := MakePair(3, "c")
|
||||
|
||||
left := pairMonoid.Concat(pairMonoid.Concat(p1, p2), p3)
|
||||
right := pairMonoid.Concat(p1, pairMonoid.Concat(p2, p3))
|
||||
|
||||
assert.Equal(t, left, right)
|
||||
assert.Equal(t, 6, Head(left))
|
||||
assert.Equal(t, "abc", Tail(left))
|
||||
})
|
||||
|
||||
t.Run("multiple concatenations", func(t *testing.T) {
|
||||
intAdd := N.MonoidSum[int]()
|
||||
intMul := N.MonoidProduct[int]()
|
||||
|
||||
pairMonoid := ApplicativeMonoidTail(intAdd, intMul)
|
||||
|
||||
pairs := []Pair[int, int]{
|
||||
MakePair(1, 2),
|
||||
MakePair(3, 4),
|
||||
MakePair(5, 6),
|
||||
}
|
||||
|
||||
result := pairMonoid.Empty()
|
||||
for _, p := range pairs {
|
||||
result = pairMonoid.Concat(result, p)
|
||||
}
|
||||
|
||||
assert.Equal(t, 9, Head(result)) // 0 + 1 + 3 + 5
|
||||
assert.Equal(t, 48, Tail(result)) // 1 * 2 * 4 * 6
|
||||
})
|
||||
}
|
||||
|
||||
// TestApplicativeMonoidHead tests the ApplicativeMonoidHead implementation
|
||||
func TestApplicativeMonoidHead(t *testing.T) {
|
||||
t.Run("integer multiplication and addition", func(t *testing.T) {
|
||||
intMul := N.MonoidProduct[int]()
|
||||
intAdd := N.MonoidSum[int]()
|
||||
|
||||
pairMonoid := ApplicativeMonoidHead(intMul, intAdd)
|
||||
|
||||
p1 := MakePair(3, 5)
|
||||
p2 := MakePair(4, 10)
|
||||
|
||||
result := pairMonoid.Concat(p1, p2)
|
||||
assert.Equal(t, 12, Head(result)) // 3 * 4
|
||||
assert.Equal(t, 15, Tail(result)) // 5 + 10
|
||||
})
|
||||
|
||||
t.Run("string concatenation and boolean OR", func(t *testing.T) {
|
||||
strConcat := S.Monoid
|
||||
boolOr := M.MakeMonoid(func(a, b bool) bool { return a || b }, false)
|
||||
|
||||
pairMonoid := ApplicativeMonoidHead(strConcat, boolOr)
|
||||
|
||||
p1 := MakePair("hello", false)
|
||||
p2 := MakePair(" world", true)
|
||||
|
||||
result := pairMonoid.Concat(p1, p2)
|
||||
assert.Equal(t, "hello world", Head(result))
|
||||
assert.Equal(t, true, Tail(result))
|
||||
})
|
||||
|
||||
t.Run("empty value", func(t *testing.T) {
|
||||
intMul := N.MonoidProduct[int]()
|
||||
intAdd := N.MonoidSum[int]()
|
||||
|
||||
pairMonoid := ApplicativeMonoidHead(intMul, intAdd)
|
||||
|
||||
empty := pairMonoid.Empty()
|
||||
assert.Equal(t, 1, Head(empty))
|
||||
assert.Equal(t, 0, Tail(empty))
|
||||
})
|
||||
|
||||
t.Run("left identity law", func(t *testing.T) {
|
||||
intMul := N.MonoidProduct[int]()
|
||||
intAdd := N.MonoidSum[int]()
|
||||
|
||||
pairMonoid := ApplicativeMonoidHead(intMul, intAdd)
|
||||
|
||||
p := MakePair(5, 10)
|
||||
result := pairMonoid.Concat(pairMonoid.Empty(), p)
|
||||
|
||||
assert.Equal(t, p, result)
|
||||
})
|
||||
|
||||
t.Run("right identity law", func(t *testing.T) {
|
||||
intMul := N.MonoidProduct[int]()
|
||||
intAdd := N.MonoidSum[int]()
|
||||
|
||||
pairMonoid := ApplicativeMonoidHead(intMul, intAdd)
|
||||
|
||||
p := MakePair(5, 10)
|
||||
result := pairMonoid.Concat(p, pairMonoid.Empty())
|
||||
|
||||
assert.Equal(t, p, result)
|
||||
})
|
||||
|
||||
t.Run("associativity law", func(t *testing.T) {
|
||||
intMul := N.MonoidProduct[int]()
|
||||
intAdd := N.MonoidSum[int]()
|
||||
|
||||
pairMonoid := ApplicativeMonoidHead(intMul, intAdd)
|
||||
|
||||
p1 := MakePair(2, 1)
|
||||
p2 := MakePair(3, 2)
|
||||
p3 := MakePair(4, 3)
|
||||
|
||||
left := pairMonoid.Concat(pairMonoid.Concat(p1, p2), p3)
|
||||
right := pairMonoid.Concat(p1, pairMonoid.Concat(p2, p3))
|
||||
|
||||
assert.Equal(t, left, right)
|
||||
assert.Equal(t, 24, Head(left)) // 2 * 3 * 4
|
||||
assert.Equal(t, 6, Tail(left)) // 1 + 2 + 3
|
||||
})
|
||||
|
||||
t.Run("multiple concatenations", func(t *testing.T) {
|
||||
intAdd := N.MonoidSum[int]()
|
||||
intMul := N.MonoidProduct[int]()
|
||||
|
||||
pairMonoid := ApplicativeMonoidHead(intAdd, intMul)
|
||||
|
||||
pairs := []Pair[int, int]{
|
||||
MakePair(1, 2),
|
||||
MakePair(3, 4),
|
||||
MakePair(5, 6),
|
||||
}
|
||||
|
||||
result := pairMonoid.Empty()
|
||||
for _, p := range pairs {
|
||||
result = pairMonoid.Concat(result, p)
|
||||
}
|
||||
|
||||
assert.Equal(t, 9, Head(result)) // 0 + 1 + 3 + 5
|
||||
assert.Equal(t, 48, Tail(result)) // 1 * 2 * 4 * 6
|
||||
})
|
||||
}
|
||||
|
||||
// TestApplicativeMonoid tests the ApplicativeMonoid alias
|
||||
func TestApplicativeMonoid(t *testing.T) {
|
||||
t.Run("is alias for ApplicativeMonoidTail", func(t *testing.T) {
|
||||
intAdd := N.MonoidSum[int]()
|
||||
strConcat := S.Monoid
|
||||
|
||||
monoid1 := ApplicativeMonoid(intAdd, strConcat)
|
||||
monoid2 := ApplicativeMonoidTail(intAdd, strConcat)
|
||||
|
||||
p1 := MakePair(5, "hello")
|
||||
p2 := MakePair(3, " world")
|
||||
|
||||
result1 := monoid1.Concat(p1, p2)
|
||||
result2 := monoid2.Concat(p1, p2)
|
||||
|
||||
assert.Equal(t, result1, result2)
|
||||
assert.Equal(t, 8, Head(result1))
|
||||
assert.Equal(t, "hello world", Tail(result1))
|
||||
})
|
||||
|
||||
t.Run("empty values are identical", func(t *testing.T) {
|
||||
intAdd := N.MonoidSum[int]()
|
||||
strConcat := S.Monoid
|
||||
|
||||
monoid1 := ApplicativeMonoid(intAdd, strConcat)
|
||||
monoid2 := ApplicativeMonoidTail(intAdd, strConcat)
|
||||
|
||||
assert.Equal(t, monoid1.Empty(), monoid2.Empty())
|
||||
})
|
||||
}
|
||||
|
||||
// TestMonoidHeadVsTail compares ApplicativeMonoidHead and ApplicativeMonoidTail
|
||||
func TestMonoidHeadVsTail(t *testing.T) {
|
||||
t.Run("same result with commutative operations", func(t *testing.T) {
|
||||
intAdd := N.MonoidSum[int]()
|
||||
intMul := N.MonoidProduct[int]()
|
||||
|
||||
headMonoid := ApplicativeMonoidHead(intMul, intAdd)
|
||||
tailMonoid := ApplicativeMonoidTail(intMul, intAdd)
|
||||
|
||||
p1 := MakePair(2, 3)
|
||||
p2 := MakePair(4, 5)
|
||||
|
||||
resultHead := headMonoid.Concat(p1, p2)
|
||||
resultTail := tailMonoid.Concat(p1, p2)
|
||||
|
||||
// Both should give same result since operations are commutative
|
||||
assert.Equal(t, 8, Head(resultHead)) // 2 * 4
|
||||
assert.Equal(t, 8, Tail(resultHead)) // 3 + 5
|
||||
assert.Equal(t, 8, Head(resultTail)) // 2 * 4
|
||||
assert.Equal(t, 8, Tail(resultTail)) // 3 + 5
|
||||
})
|
||||
|
||||
t.Run("different empty values", func(t *testing.T) {
|
||||
intAdd := N.MonoidSum[int]()
|
||||
intMul := N.MonoidProduct[int]()
|
||||
|
||||
headMonoid := ApplicativeMonoidHead(intMul, intAdd)
|
||||
tailMonoid := ApplicativeMonoidTail(intAdd, intMul)
|
||||
|
||||
emptyHead := headMonoid.Empty()
|
||||
emptyTail := tailMonoid.Empty()
|
||||
|
||||
assert.Equal(t, 1, Head(emptyHead)) // intMul empty
|
||||
assert.Equal(t, 0, Tail(emptyHead)) // intAdd empty
|
||||
assert.Equal(t, 0, Head(emptyTail)) // intAdd empty
|
||||
assert.Equal(t, 1, Tail(emptyTail)) // intMul empty
|
||||
})
|
||||
}
|
||||
|
||||
// TestMonoidLaws verifies monoid laws for all implementations
|
||||
func TestMonoidLaws(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
monoid M.Monoid[Pair[int, int]]
|
||||
p1, p2, p3 Pair[int, int]
|
||||
}{
|
||||
{
|
||||
name: "ApplicativeMonoidTail",
|
||||
monoid: ApplicativeMonoidTail(N.MonoidSum[int](), N.MonoidProduct[int]()),
|
||||
p1: MakePair(1, 2),
|
||||
p2: MakePair(3, 4),
|
||||
p3: MakePair(5, 6),
|
||||
},
|
||||
{
|
||||
name: "ApplicativeMonoidHead",
|
||||
monoid: ApplicativeMonoidHead(N.MonoidProduct[int](), N.MonoidSum[int]()),
|
||||
p1: MakePair(2, 1),
|
||||
p2: MakePair(3, 2),
|
||||
p3: MakePair(4, 3),
|
||||
},
|
||||
{
|
||||
name: "ApplicativeMonoid",
|
||||
monoid: ApplicativeMonoid(N.MonoidSum[int](), N.MonoidSum[int]()),
|
||||
p1: MakePair(1, 2),
|
||||
p2: MakePair(3, 4),
|
||||
p3: MakePair(5, 6),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Run("associativity", func(t *testing.T) {
|
||||
left := tc.monoid.Concat(tc.monoid.Concat(tc.p1, tc.p2), tc.p3)
|
||||
right := tc.monoid.Concat(tc.p1, tc.monoid.Concat(tc.p2, tc.p3))
|
||||
assert.Equal(t, left, right)
|
||||
})
|
||||
|
||||
t.Run("left identity", func(t *testing.T) {
|
||||
result := tc.monoid.Concat(tc.monoid.Empty(), tc.p1)
|
||||
assert.Equal(t, tc.p1, result)
|
||||
})
|
||||
|
||||
t.Run("right identity", func(t *testing.T) {
|
||||
result := tc.monoid.Concat(tc.p1, tc.monoid.Empty())
|
||||
assert.Equal(t, tc.p1, result)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMonoidEdgeCases tests edge cases for monoid operations
|
||||
func TestMonoidEdgeCases(t *testing.T) {
|
||||
t.Run("concatenating empty with empty", func(t *testing.T) {
|
||||
intAdd := N.MonoidSum[int]()
|
||||
strConcat := S.Monoid
|
||||
|
||||
pairMonoid := ApplicativeMonoidTail(intAdd, strConcat)
|
||||
|
||||
result := pairMonoid.Concat(pairMonoid.Empty(), pairMonoid.Empty())
|
||||
assert.Equal(t, pairMonoid.Empty(), result)
|
||||
})
|
||||
|
||||
t.Run("chain of operations", func(t *testing.T) {
|
||||
intAdd := N.MonoidSum[int]()
|
||||
intMul := N.MonoidProduct[int]()
|
||||
|
||||
pairMonoid := ApplicativeMonoidTail(intAdd, intMul)
|
||||
|
||||
result := pairMonoid.Concat(
|
||||
pairMonoid.Concat(
|
||||
pairMonoid.Concat(MakePair(1, 2), MakePair(2, 3)),
|
||||
MakePair(3, 4),
|
||||
),
|
||||
MakePair(4, 5),
|
||||
)
|
||||
|
||||
assert.Equal(t, 10, Head(result)) // 1 + 2 + 3 + 4
|
||||
assert.Equal(t, 120, Tail(result)) // 2 * 3 * 4 * 5
|
||||
})
|
||||
|
||||
t.Run("zero values", func(t *testing.T) {
|
||||
intAdd := N.MonoidSum[int]()
|
||||
intMul := N.MonoidProduct[int]()
|
||||
|
||||
pairMonoid := ApplicativeMonoidTail(intAdd, intMul)
|
||||
|
||||
p1 := MakePair(0, 0)
|
||||
p2 := MakePair(5, 10)
|
||||
|
||||
result := pairMonoid.Concat(p1, p2)
|
||||
assert.Equal(t, 5, Head(result))
|
||||
assert.Equal(t, 0, Tail(result)) // 0 * 10 = 0
|
||||
})
|
||||
|
||||
t.Run("negative values", func(t *testing.T) {
|
||||
intAdd := N.MonoidSum[int]()
|
||||
intMul := N.MonoidProduct[int]()
|
||||
|
||||
pairMonoid := ApplicativeMonoidTail(intAdd, intMul)
|
||||
|
||||
p1 := MakePair(-5, -2)
|
||||
p2 := MakePair(3, 4)
|
||||
|
||||
result := pairMonoid.Concat(p1, p2)
|
||||
assert.Equal(t, -2, Head(result)) // -5 + 3
|
||||
assert.Equal(t, -8, Tail(result)) // -2 * 4
|
||||
})
|
||||
}
|
||||
|
||||
// TestMonoidWithDifferentTypes tests monoids with various type combinations
|
||||
func TestMonoidWithDifferentTypes(t *testing.T) {
|
||||
t.Run("string and boolean", func(t *testing.T) {
|
||||
strConcat := S.Monoid
|
||||
boolAnd := M.MakeMonoid(func(a, b bool) bool { return a && b }, true)
|
||||
|
||||
pairMonoid := ApplicativeMonoidTail(strConcat, boolAnd)
|
||||
|
||||
p1 := MakePair("hello", true)
|
||||
p2 := MakePair(" world", true)
|
||||
|
||||
result := pairMonoid.Concat(p1, p2)
|
||||
// Note: The order depends on the applicative implementation
|
||||
assert.Equal(t, " worldhello", Head(result))
|
||||
assert.Equal(t, true, Tail(result))
|
||||
})
|
||||
|
||||
t.Run("boolean and string", func(t *testing.T) {
|
||||
boolOr := M.MakeMonoid(func(a, b bool) bool { return a || b }, false)
|
||||
strConcat := S.Monoid
|
||||
|
||||
pairMonoid := ApplicativeMonoidTail(boolOr, strConcat)
|
||||
|
||||
p1 := MakePair(false, "foo")
|
||||
p2 := MakePair(true, "bar")
|
||||
|
||||
result := pairMonoid.Concat(p1, p2)
|
||||
assert.Equal(t, true, Head(result))
|
||||
assert.Equal(t, "foobar", Tail(result))
|
||||
})
|
||||
|
||||
t.Run("float64 addition and multiplication", func(t *testing.T) {
|
||||
floatAdd := N.MonoidSum[float64]()
|
||||
floatMul := N.MonoidProduct[float64]()
|
||||
|
||||
pairMonoid := ApplicativeMonoidTail(floatAdd, floatMul)
|
||||
|
||||
p1 := MakePair(1.5, 2.0)
|
||||
p2 := MakePair(2.5, 3.0)
|
||||
|
||||
result := pairMonoid.Concat(p1, p2)
|
||||
assert.Equal(t, 4.0, Head(result))
|
||||
assert.Equal(t, 6.0, Tail(result))
|
||||
})
|
||||
}
|
||||
|
||||
// TestMonoidCommutativity tests behavior with non-commutative operations
|
||||
func TestMonoidCommutativity(t *testing.T) {
|
||||
t.Run("string concatenation is not commutative", func(t *testing.T) {
|
||||
strConcat := S.Monoid
|
||||
|
||||
pairMonoid := ApplicativeMonoidTail(strConcat, strConcat)
|
||||
|
||||
p1 := MakePair("hello", "foo")
|
||||
p2 := MakePair(" world", "bar")
|
||||
|
||||
result1 := pairMonoid.Concat(p1, p2)
|
||||
result2 := pairMonoid.Concat(p2, p1)
|
||||
|
||||
// The applicative implementation reverses the order for head values
|
||||
assert.Equal(t, " worldhello", Head(result1))
|
||||
assert.Equal(t, "foobar", Tail(result1))
|
||||
assert.Equal(t, "hello world", Head(result2))
|
||||
assert.Equal(t, "barfoo", Tail(result2))
|
||||
assert.NotEqual(t, result1, result2)
|
||||
})
|
||||
}
|
||||
@@ -52,3 +52,61 @@ func AlternativeMonoid[A any](m M.Monoid[A]) Monoid[A] {
|
||||
func AltMonoid[A any](zero Lazy[Result[A]]) Monoid[A] {
|
||||
return either.AltMonoid(zero)
|
||||
}
|
||||
|
||||
// FirstMonoid creates a Monoid for Result[A] that returns the first Ok (Right) value.
|
||||
// This monoid prefers the left operand when it is Ok, otherwise returns the right operand.
|
||||
// The empty value is provided as a lazy computation.
|
||||
//
|
||||
// This is equivalent to AltMonoid but implemented more directly.
|
||||
//
|
||||
// Truth table:
|
||||
//
|
||||
// | x | y | concat(x, y) |
|
||||
// | --------- | --------- | ------------ |
|
||||
// | err(e1) | err(e2) | err(e2) |
|
||||
// | ok(a) | err(e) | ok(a) |
|
||||
// | err(e) | ok(b) | ok(b) |
|
||||
// | ok(a) | ok(b) | ok(a) |
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import "errors"
|
||||
// zero := func() result.Result[int] { return result.Error[int](errors.New("empty")) }
|
||||
// m := result.FirstMonoid[int](zero)
|
||||
// m.Concat(result.Of(2), result.Of(3)) // Ok(2) - returns first Ok
|
||||
// m.Concat(result.Error[int](errors.New("err")), result.Of(3)) // Ok(3)
|
||||
// m.Concat(result.Of(2), result.Error[int](errors.New("err"))) // Ok(2)
|
||||
// m.Empty() // Error(error("empty"))
|
||||
//
|
||||
//go:inline
|
||||
func FirstMonoid[A any](zero Lazy[Result[A]]) M.Monoid[Result[A]] {
|
||||
return either.FirstMonoid(zero)
|
||||
}
|
||||
|
||||
// LastMonoid creates a Monoid for Result[A] that returns the last Ok (Right) value.
|
||||
// This monoid prefers the right operand when it is Ok, otherwise returns the left operand.
|
||||
// The empty value is provided as a lazy computation.
|
||||
//
|
||||
// Truth table:
|
||||
//
|
||||
// | x | y | concat(x, y) |
|
||||
// | --------- | --------- | ------------ |
|
||||
// | err(e1) | err(e2) | err(e1) |
|
||||
// | ok(a) | err(e) | ok(a) |
|
||||
// | err(e) | ok(b) | ok(b) |
|
||||
// | ok(a) | ok(b) | ok(b) |
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import "errors"
|
||||
// zero := func() result.Result[int] { return result.Error[int](errors.New("empty")) }
|
||||
// m := result.LastMonoid[int](zero)
|
||||
// m.Concat(result.Of(2), result.Of(3)) // Ok(3) - returns last Ok
|
||||
// m.Concat(result.Error[int](errors.New("err")), result.Of(3)) // Ok(3)
|
||||
// m.Concat(result.Of(2), result.Error[int](errors.New("err"))) // Ok(2)
|
||||
// m.Empty() // Error(error("empty"))
|
||||
//
|
||||
//go:inline
|
||||
func LastMonoid[A any](zero Lazy[Result[A]]) M.Monoid[Result[A]] {
|
||||
return either.LastMonoid(zero)
|
||||
}
|
||||
|
||||
498
v2/result/monoid_test.go
Normal file
498
v2/result/monoid_test.go
Normal file
@@ -0,0 +1,498 @@
|
||||
// Copyright (c) 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 result
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestFirstMonoid tests the FirstMonoid implementation
|
||||
func TestFirstMonoid(t *testing.T) {
|
||||
zero := func() Result[int] { return Left[int](errors.New("empty")) }
|
||||
m := FirstMonoid[int](zero)
|
||||
|
||||
t.Run("both Right values - returns first", func(t *testing.T) {
|
||||
result := m.Concat(Right(2), Right(3))
|
||||
assert.Equal(t, Right(2), result)
|
||||
})
|
||||
|
||||
t.Run("left Right, right Left", func(t *testing.T) {
|
||||
result := m.Concat(Right(2), Left[int](errors.New("err")))
|
||||
assert.Equal(t, Right(2), result)
|
||||
})
|
||||
|
||||
t.Run("left Left, right Right", func(t *testing.T) {
|
||||
result := m.Concat(Left[int](errors.New("err")), Right(3))
|
||||
assert.Equal(t, Right(3), result)
|
||||
})
|
||||
|
||||
t.Run("both Left", func(t *testing.T) {
|
||||
err1 := errors.New("err1")
|
||||
err2 := errors.New("err2")
|
||||
result := m.Concat(Left[int](err1), Left[int](err2))
|
||||
// Should return the second Left
|
||||
assert.True(t, IsLeft(result))
|
||||
_, leftErr := Unwrap(result)
|
||||
assert.Equal(t, err2, leftErr)
|
||||
})
|
||||
|
||||
t.Run("empty value", func(t *testing.T) {
|
||||
empty := m.Empty()
|
||||
assert.True(t, IsLeft(empty))
|
||||
_, leftErr := Unwrap(empty)
|
||||
assert.Equal(t, "empty", leftErr.Error())
|
||||
})
|
||||
|
||||
t.Run("left identity", func(t *testing.T) {
|
||||
x := Right(5)
|
||||
result := m.Concat(m.Empty(), x)
|
||||
assert.Equal(t, x, result)
|
||||
})
|
||||
|
||||
t.Run("right identity", func(t *testing.T) {
|
||||
x := Right(5)
|
||||
result := m.Concat(x, m.Empty())
|
||||
assert.Equal(t, x, result)
|
||||
})
|
||||
|
||||
t.Run("associativity", func(t *testing.T) {
|
||||
a := Right(1)
|
||||
b := Right(2)
|
||||
c := Right(3)
|
||||
|
||||
left := m.Concat(m.Concat(a, b), c)
|
||||
right := m.Concat(a, m.Concat(b, c))
|
||||
|
||||
assert.Equal(t, left, right)
|
||||
assert.Equal(t, Right(1), left)
|
||||
})
|
||||
|
||||
t.Run("multiple concatenations", func(t *testing.T) {
|
||||
// Should return the first Right value encountered
|
||||
result := m.Concat(
|
||||
m.Concat(Left[int](errors.New("err1")), Right(1)),
|
||||
m.Concat(Right(2), Right(3)),
|
||||
)
|
||||
assert.Equal(t, Right(1), result)
|
||||
})
|
||||
|
||||
t.Run("with strings", func(t *testing.T) {
|
||||
zeroStr := func() Result[string] { return Left[string](errors.New("empty")) }
|
||||
strMonoid := FirstMonoid[string](zeroStr)
|
||||
|
||||
result := strMonoid.Concat(Right("first"), Right("second"))
|
||||
assert.Equal(t, Right("first"), result)
|
||||
|
||||
result = strMonoid.Concat(Left[string](errors.New("err")), Right("second"))
|
||||
assert.Equal(t, Right("second"), result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestLastMonoid tests the LastMonoid implementation
|
||||
func TestLastMonoid(t *testing.T) {
|
||||
zero := func() Result[int] { return Left[int](errors.New("empty")) }
|
||||
m := LastMonoid[int](zero)
|
||||
|
||||
t.Run("both Right values - returns last", func(t *testing.T) {
|
||||
result := m.Concat(Right(2), Right(3))
|
||||
assert.Equal(t, Right(3), result)
|
||||
})
|
||||
|
||||
t.Run("left Right, right Left", func(t *testing.T) {
|
||||
result := m.Concat(Right(2), Left[int](errors.New("err")))
|
||||
assert.Equal(t, Right(2), result)
|
||||
})
|
||||
|
||||
t.Run("left Left, right Right", func(t *testing.T) {
|
||||
result := m.Concat(Left[int](errors.New("err")), Right(3))
|
||||
assert.Equal(t, Right(3), result)
|
||||
})
|
||||
|
||||
t.Run("both Left", func(t *testing.T) {
|
||||
err1 := errors.New("err1")
|
||||
err2 := errors.New("err2")
|
||||
result := m.Concat(Left[int](err1), Left[int](err2))
|
||||
// Should return the first Left
|
||||
assert.True(t, IsLeft(result))
|
||||
_, leftErr := Unwrap(result)
|
||||
assert.Equal(t, err1, leftErr)
|
||||
})
|
||||
|
||||
t.Run("empty value", func(t *testing.T) {
|
||||
empty := m.Empty()
|
||||
assert.True(t, IsLeft(empty))
|
||||
_, leftErr := Unwrap(empty)
|
||||
assert.Equal(t, "empty", leftErr.Error())
|
||||
})
|
||||
|
||||
t.Run("left identity", func(t *testing.T) {
|
||||
x := Right(5)
|
||||
result := m.Concat(m.Empty(), x)
|
||||
assert.Equal(t, x, result)
|
||||
})
|
||||
|
||||
t.Run("right identity", func(t *testing.T) {
|
||||
x := Right(5)
|
||||
result := m.Concat(x, m.Empty())
|
||||
assert.Equal(t, x, result)
|
||||
})
|
||||
|
||||
t.Run("associativity", func(t *testing.T) {
|
||||
a := Right(1)
|
||||
b := Right(2)
|
||||
c := Right(3)
|
||||
|
||||
left := m.Concat(m.Concat(a, b), c)
|
||||
right := m.Concat(a, m.Concat(b, c))
|
||||
|
||||
assert.Equal(t, left, right)
|
||||
assert.Equal(t, Right(3), left)
|
||||
})
|
||||
|
||||
t.Run("multiple concatenations", func(t *testing.T) {
|
||||
// Should return the last Right value encountered
|
||||
result := m.Concat(
|
||||
m.Concat(Right(1), Right(2)),
|
||||
m.Concat(Right(3), Left[int](errors.New("err"))),
|
||||
)
|
||||
assert.Equal(t, Right(3), result)
|
||||
})
|
||||
|
||||
t.Run("with strings", func(t *testing.T) {
|
||||
zeroStr := func() Result[string] { return Left[string](errors.New("empty")) }
|
||||
strMonoid := LastMonoid[string](zeroStr)
|
||||
|
||||
result := strMonoid.Concat(Right("first"), Right("second"))
|
||||
assert.Equal(t, Right("second"), result)
|
||||
|
||||
result = strMonoid.Concat(Right("first"), Left[string](errors.New("err")))
|
||||
assert.Equal(t, Right("first"), result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestAltMonoid tests the AltMonoid implementation
|
||||
func TestAltMonoid(t *testing.T) {
|
||||
zero := func() Result[int] { return Left[int](errors.New("empty")) }
|
||||
m := AltMonoid[int](zero)
|
||||
|
||||
t.Run("both Right values - returns first", func(t *testing.T) {
|
||||
result := m.Concat(Right(2), Right(3))
|
||||
assert.Equal(t, Right(2), result)
|
||||
})
|
||||
|
||||
t.Run("left Right, right Left", func(t *testing.T) {
|
||||
result := m.Concat(Right(2), Left[int](errors.New("err")))
|
||||
assert.Equal(t, Right(2), result)
|
||||
})
|
||||
|
||||
t.Run("left Left, right Right", func(t *testing.T) {
|
||||
result := m.Concat(Left[int](errors.New("err")), Right(3))
|
||||
assert.Equal(t, Right(3), result)
|
||||
})
|
||||
|
||||
t.Run("both Left", func(t *testing.T) {
|
||||
err1 := errors.New("err1")
|
||||
err2 := errors.New("err2")
|
||||
result := m.Concat(Left[int](err1), Left[int](err2))
|
||||
// Should return the second Left
|
||||
assert.True(t, IsLeft(result))
|
||||
_, leftErr := Unwrap(result)
|
||||
assert.Equal(t, err2, leftErr)
|
||||
})
|
||||
|
||||
t.Run("empty value", func(t *testing.T) {
|
||||
empty := m.Empty()
|
||||
assert.True(t, IsLeft(empty))
|
||||
_, leftErr := Unwrap(empty)
|
||||
assert.Equal(t, "empty", leftErr.Error())
|
||||
})
|
||||
|
||||
t.Run("left identity", func(t *testing.T) {
|
||||
x := Right(5)
|
||||
result := m.Concat(m.Empty(), x)
|
||||
assert.Equal(t, x, result)
|
||||
})
|
||||
|
||||
t.Run("right identity", func(t *testing.T) {
|
||||
x := Right(5)
|
||||
result := m.Concat(x, m.Empty())
|
||||
assert.Equal(t, x, result)
|
||||
})
|
||||
|
||||
t.Run("associativity", func(t *testing.T) {
|
||||
a := Right(1)
|
||||
b := Left[int](errors.New("err"))
|
||||
c := Right(3)
|
||||
|
||||
left := m.Concat(m.Concat(a, b), c)
|
||||
right := m.Concat(a, m.Concat(b, c))
|
||||
|
||||
assert.Equal(t, left, right)
|
||||
assert.Equal(t, Right(1), left)
|
||||
})
|
||||
}
|
||||
|
||||
// TestFirstMonoidVsAltMonoid verifies FirstMonoid and AltMonoid have the same behavior
|
||||
func TestFirstMonoidVsAltMonoid(t *testing.T) {
|
||||
zero := func() Result[int] { return Left[int](errors.New("empty")) }
|
||||
firstMonoid := FirstMonoid[int](zero)
|
||||
altMonoid := AltMonoid[int](zero)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
left Result[int]
|
||||
right Result[int]
|
||||
}{
|
||||
{"both Right", Right(1), Right(2)},
|
||||
{"left Right, right Left", Right(1), Left[int](errors.New("err"))},
|
||||
{"left Left, right Right", Left[int](errors.New("err")), Right(2)},
|
||||
{"both Left", Left[int](errors.New("err1")), Left[int](errors.New("err2"))},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
firstResult := firstMonoid.Concat(tc.left, tc.right)
|
||||
altResult := altMonoid.Concat(tc.left, tc.right)
|
||||
|
||||
// Both should have the same Right/Left status
|
||||
assert.Equal(t, IsRight(firstResult), IsRight(altResult), "FirstMonoid and AltMonoid should have same Right/Left status")
|
||||
|
||||
if IsRight(firstResult) {
|
||||
rightVal1, _ := Unwrap(firstResult)
|
||||
rightVal2, _ := Unwrap(altResult)
|
||||
assert.Equal(t, rightVal1, rightVal2, "FirstMonoid and AltMonoid should have same Right value")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestFirstMonoidVsLastMonoid verifies the difference between FirstMonoid and LastMonoid
|
||||
func TestFirstMonoidVsLastMonoid(t *testing.T) {
|
||||
zero := func() Result[int] { return Left[int](errors.New("empty")) }
|
||||
firstMonoid := FirstMonoid[int](zero)
|
||||
lastMonoid := LastMonoid[int](zero)
|
||||
|
||||
t.Run("both Right - different results", func(t *testing.T) {
|
||||
firstResult := firstMonoid.Concat(Right(1), Right(2))
|
||||
lastResult := lastMonoid.Concat(Right(1), Right(2))
|
||||
|
||||
assert.Equal(t, Right(1), firstResult)
|
||||
assert.Equal(t, Right(2), lastResult)
|
||||
assert.NotEqual(t, firstResult, lastResult)
|
||||
})
|
||||
|
||||
t.Run("with Left values - different behavior", func(t *testing.T) {
|
||||
err1 := errors.New("err1")
|
||||
err2 := errors.New("err2")
|
||||
|
||||
// Both Left: FirstMonoid returns second, LastMonoid returns first
|
||||
firstResult := firstMonoid.Concat(Left[int](err1), Left[int](err2))
|
||||
lastResult := lastMonoid.Concat(Left[int](err1), Left[int](err2))
|
||||
|
||||
assert.True(t, IsLeft(firstResult))
|
||||
assert.True(t, IsLeft(lastResult))
|
||||
_, leftErr1 := Unwrap(firstResult)
|
||||
_, leftErr2 := Unwrap(lastResult)
|
||||
assert.Equal(t, err2, leftErr1)
|
||||
assert.Equal(t, err1, leftErr2)
|
||||
})
|
||||
|
||||
t.Run("mixed values - same results", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
left Result[int]
|
||||
right Result[int]
|
||||
expected Result[int]
|
||||
}{
|
||||
{"left Right, right Left", Right(1), Left[int](errors.New("err")), Right(1)},
|
||||
{"left Left, right Right", Left[int](errors.New("err")), Right(2), Right(2)},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
firstResult := firstMonoid.Concat(tc.left, tc.right)
|
||||
lastResult := lastMonoid.Concat(tc.left, tc.right)
|
||||
|
||||
assert.Equal(t, tc.expected, firstResult)
|
||||
assert.Equal(t, tc.expected, lastResult)
|
||||
assert.Equal(t, firstResult, lastResult)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestMonoidLaws verifies monoid laws for all monoid implementations
|
||||
func TestMonoidLaws(t *testing.T) {
|
||||
t.Run("FirstMonoid laws", func(t *testing.T) {
|
||||
zero := func() Result[int] { return Left[int](errors.New("empty")) }
|
||||
m := FirstMonoid[int](zero)
|
||||
|
||||
a := Right(1)
|
||||
b := Right(2)
|
||||
c := Right(3)
|
||||
|
||||
// Associativity: (a • b) • c = a • (b • c)
|
||||
left := m.Concat(m.Concat(a, b), c)
|
||||
right := m.Concat(a, m.Concat(b, c))
|
||||
assert.Equal(t, left, right)
|
||||
|
||||
// Left identity: Empty() • a = a
|
||||
leftId := m.Concat(m.Empty(), a)
|
||||
assert.Equal(t, a, leftId)
|
||||
|
||||
// Right identity: a • Empty() = a
|
||||
rightId := m.Concat(a, m.Empty())
|
||||
assert.Equal(t, a, rightId)
|
||||
})
|
||||
|
||||
t.Run("LastMonoid laws", func(t *testing.T) {
|
||||
zero := func() Result[int] { return Left[int](errors.New("empty")) }
|
||||
m := LastMonoid[int](zero)
|
||||
|
||||
a := Right(1)
|
||||
b := Right(2)
|
||||
c := Right(3)
|
||||
|
||||
// Associativity: (a • b) • c = a • (b • c)
|
||||
left := m.Concat(m.Concat(a, b), c)
|
||||
right := m.Concat(a, m.Concat(b, c))
|
||||
assert.Equal(t, left, right)
|
||||
|
||||
// Left identity: Empty() • a = a
|
||||
leftId := m.Concat(m.Empty(), a)
|
||||
assert.Equal(t, a, leftId)
|
||||
|
||||
// Right identity: a • Empty() = a
|
||||
rightId := m.Concat(a, m.Empty())
|
||||
assert.Equal(t, a, rightId)
|
||||
})
|
||||
|
||||
t.Run("AltMonoid laws", func(t *testing.T) {
|
||||
zero := func() Result[int] { return Left[int](errors.New("empty")) }
|
||||
m := AltMonoid[int](zero)
|
||||
|
||||
a := Right(1)
|
||||
b := Right(2)
|
||||
c := Right(3)
|
||||
|
||||
// Associativity: (a • b) • c = a • (b • c)
|
||||
left := m.Concat(m.Concat(a, b), c)
|
||||
right := m.Concat(a, m.Concat(b, c))
|
||||
assert.Equal(t, left, right)
|
||||
|
||||
// Left identity: Empty() • a = a
|
||||
leftId := m.Concat(m.Empty(), a)
|
||||
assert.Equal(t, a, leftId)
|
||||
|
||||
// Right identity: a • Empty() = a
|
||||
rightId := m.Concat(a, m.Empty())
|
||||
assert.Equal(t, a, rightId)
|
||||
})
|
||||
|
||||
t.Run("FirstMonoid laws with Left values", func(t *testing.T) {
|
||||
zero := func() Result[int] { return Left[int](errors.New("empty")) }
|
||||
m := FirstMonoid[int](zero)
|
||||
|
||||
a := Left[int](errors.New("err1"))
|
||||
b := Left[int](errors.New("err2"))
|
||||
c := Left[int](errors.New("err3"))
|
||||
|
||||
// Associativity with Left values
|
||||
left := m.Concat(m.Concat(a, b), c)
|
||||
right := m.Concat(a, m.Concat(b, c))
|
||||
assert.Equal(t, left, right)
|
||||
})
|
||||
|
||||
t.Run("LastMonoid laws with Left values", func(t *testing.T) {
|
||||
zero := func() Result[int] { return Left[int](errors.New("empty")) }
|
||||
m := LastMonoid[int](zero)
|
||||
|
||||
a := Left[int](errors.New("err1"))
|
||||
b := Left[int](errors.New("err2"))
|
||||
c := Left[int](errors.New("err3"))
|
||||
|
||||
// Associativity with Left values
|
||||
left := m.Concat(m.Concat(a, b), c)
|
||||
right := m.Concat(a, m.Concat(b, c))
|
||||
assert.Equal(t, left, right)
|
||||
})
|
||||
}
|
||||
|
||||
// TestMonoidEdgeCases tests edge cases for monoid operations
|
||||
func TestMonoidEdgeCases(t *testing.T) {
|
||||
t.Run("FirstMonoid with empty concatenations", func(t *testing.T) {
|
||||
zero := func() Result[int] { return Left[int](errors.New("empty")) }
|
||||
m := FirstMonoid[int](zero)
|
||||
|
||||
// Empty with empty
|
||||
result := m.Concat(m.Empty(), m.Empty())
|
||||
assert.True(t, IsLeft(result))
|
||||
})
|
||||
|
||||
t.Run("LastMonoid with empty concatenations", func(t *testing.T) {
|
||||
zero := func() Result[int] { return Left[int](errors.New("empty")) }
|
||||
m := LastMonoid[int](zero)
|
||||
|
||||
// Empty with empty
|
||||
result := m.Concat(m.Empty(), m.Empty())
|
||||
assert.True(t, IsLeft(result))
|
||||
})
|
||||
|
||||
t.Run("FirstMonoid chain of operations", func(t *testing.T) {
|
||||
zero := func() Result[int] { return Left[int](errors.New("empty")) }
|
||||
m := FirstMonoid[int](zero)
|
||||
|
||||
// Chain multiple operations
|
||||
result := m.Concat(
|
||||
m.Concat(
|
||||
m.Concat(Left[int](errors.New("err1")), Left[int](errors.New("err2"))),
|
||||
Right(1),
|
||||
),
|
||||
m.Concat(Right(2), Right(3)),
|
||||
)
|
||||
assert.Equal(t, Right(1), result)
|
||||
})
|
||||
|
||||
t.Run("LastMonoid chain of operations", func(t *testing.T) {
|
||||
zero := func() Result[int] { return Left[int](errors.New("empty")) }
|
||||
m := LastMonoid[int](zero)
|
||||
|
||||
// Chain multiple operations
|
||||
result := m.Concat(
|
||||
m.Concat(Right(1), Right(2)),
|
||||
m.Concat(
|
||||
Right(3),
|
||||
m.Concat(Right(4), Left[int](errors.New("err"))),
|
||||
),
|
||||
)
|
||||
assert.Equal(t, Right(4), result)
|
||||
})
|
||||
|
||||
t.Run("AltMonoid chain of operations", func(t *testing.T) {
|
||||
zero := func() Result[int] { return Left[int](errors.New("empty")) }
|
||||
m := AltMonoid[int](zero)
|
||||
|
||||
// Chain multiple operations - should return first Right
|
||||
result := m.Concat(
|
||||
m.Concat(Left[int](errors.New("err1")), Right(1)),
|
||||
m.Concat(Right(2), Right(3)),
|
||||
)
|
||||
assert.Equal(t, Right(1), result)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user