Documentation
¶
Overview ¶
Package semigroup provides implementations of the Semigroup algebraic structure.
Semigroup ¶
A Semigroup is an algebraic structure consisting of a set together with an associative binary operation. It extends the Magma structure by adding the associativity law.
Mathematical Definition:
A semigroup is a pair (S, •) where:
- S is a set
- • is a binary operation: S × S → S
- The operation must be associative: (a • b) • c = a • (b • c)
The key difference from Magma is the associativity requirement, which allows operations to be chained without worrying about parentheses.
Basic Usage ¶
Creating and using a semigroup:
import (
"fmt"
SG "github.com/IBM/fp-go/v2/semigroup"
)
// Create a semigroup for string concatenation
stringConcat := SG.MakeSemigroup(func(a, b string) string {
return a + b
})
result := stringConcat.Concat("Hello, ", "World!")
fmt.Println(result) // Output: Hello, World!
// Associativity holds
s1 := stringConcat.Concat(stringConcat.Concat("a", "b"), "c")
s2 := stringConcat.Concat("a", stringConcat.Concat("b", "c"))
fmt.Println(s1 == s2) // Output: true
Built-in Semigroups ¶
The package provides several pre-defined semigroups:
First - Always returns the first argument:
first := SG.First[int]() result := first.Concat(1, 2) // Returns: 1
Last - Always returns the last argument:
last := SG.Last[int]() result := last.Concat(1, 2) // Returns: 2
Semigroup Transformations ¶
Reverse - Swaps the order of arguments:
import N "github.com/IBM/fp-go/v2/number"
sub := SG.MakeSemigroup(func(a, b int) int { return a - b })
reversed := SG.Reverse(sub)
result1 := sub.Concat(10, 3) // 10 - 3 = 7
result2 := reversed.Concat(10, 3) // 3 - 10 = -7
FunctionSemigroup - Lifts a semigroup to work with functions:
import N "github.com/IBM/fp-go/v2/number"
// Semigroup for integers
intSum := N.SemigroupSum[int]()
// Lift to functions that return integers
funcSG := SG.FunctionSemigroup[string](intSum)
f := func(s string) int { return len(s) }
g := func(s string) int { return len(s) * 2 }
// Combine functions
combined := funcSG.Concat(f, g)
result := combined("hello") // len("hello") + len("hello")*2 = 5 + 10 = 15
Array Operations ¶
ConcatAll - Concatenates all elements in an array with a starting value:
import N "github.com/IBM/fp-go/v2/number"
sum := N.SemigroupSum[int]()
concatAll := SG.ConcatAll(sum)
result := concatAll(10)([]int{1, 2, 3, 4}) // 10 + 1 + 2 + 3 + 4 = 20
MonadConcatAll - Concatenates all elements with a starting value (uncurried):
import N "github.com/IBM/fp-go/v2/number"
sum := N.SemigroupSum[int]()
result := SG.MonadConcatAll(sum)([]int{1, 2, 3, 4}, 10) // 20
GenericConcatAll - Generic version for custom slice types:
type MyInts []int
sum := N.SemigroupSum[int]()
concatAll := SG.GenericConcatAll[MyInts](sum)
result := concatAll(0)(MyInts{1, 2, 3}) // 6
Higher-Kinded Type Semigroups ¶
ApplySemigroup - Creates a semigroup for applicative functors:
// For a type HKT<A> with map and ap operations applySG := SG.ApplySemigroup( fmap, // func(HKT<A>, func(A) func(A) A) HKT<func(A) A> fap, // func(HKT<func(A) A>, HKT<A>) HKT<A> baseSemigroup, )
AltSemigroup - Creates a semigroup for alternative functors:
// For a type HKT<A> with an alt operation altSG := SG.AltSemigroup( falt, // func(HKT<A>, func() HKT<A>) HKT<A> )
Practical Examples ¶
Example 1: Merging Configurations
type Config struct {
Timeout int
Retries int
}
configSG := SG.MakeSemigroup(func(a, b Config) Config {
return Config{
Timeout: max(a.Timeout, b.Timeout),
Retries: a.Retries + b.Retries,
}
})
default := Config{Timeout: 30, Retries: 3}
user := Config{Timeout: 60, Retries: 5}
override := Config{Timeout: 45, Retries: 2}
// Merge configurations (associative)
final := configSG.Concat(configSG.Concat(default, user), override)
// Result: Config{Timeout: 60, Retries: 10}
Example 2: Combining Validators
import S "github.com/IBM/fp-go/v2/string"
type Validator func(string) []string // Returns list of errors
validatorSG := SG.MakeSemigroup(func(v1, v2 Validator) Validator {
return func(s string) []string {
errors1 := v1(s)
errors2 := v2(s)
return append(errors1, errors2...)
}
})
notEmpty := func(s string) []string {
if S.IsEmpty(s) {
return []string{"must not be empty"}
}
return nil
}
minLength := func(s string) []string {
if len(s) < 3 {
return []string{"must be at least 3 characters"}
}
return nil
}
// Combine validators
combined := validatorSG.Concat(notEmpty, minLength)
errors := combined("ab") // ["must be at least 3 characters"]
Example 3: Aggregating Statistics
type Stats struct {
Count int
Sum float64
Min float64
Max float64
}
statsSG := SG.MakeSemigroup(func(a, b Stats) Stats {
return Stats{
Count: a.Count + b.Count,
Sum: a.Sum + b.Sum,
Min: min(a.Min, b.Min),
Max: max(a.Max, b.Max),
}
})
s1 := Stats{Count: 3, Sum: 15.0, Min: 2.0, Max: 8.0}
s2 := Stats{Count: 2, Sum: 12.0, Min: 5.0, Max: 7.0}
s3 := Stats{Count: 4, Sum: 20.0, Min: 1.0, Max: 9.0}
// Aggregate statistics (order doesn't matter due to associativity)
total := statsSG.Concat(statsSG.Concat(s1, s2), s3)
// Result: Stats{Count: 9, Sum: 47.0, Min: 1.0, Max: 9.0}
Example 4: Building Query Strings
import S "github.com/IBM/fp-go/v2/string"
querySG := SG.MakeSemigroup(func(a, b string) string {
if S.IsEmpty(a) {
return b
}
if S.IsEmpty(b) {
return a
}
return a + "&" + b
})
base := "api/users"
filter := "status=active"
sort := "sort=name"
page := "page=1"
// Build query string
query := querySG.Concat(querySG.Concat(base+"?"+filter, sort), page)
// Result: "api/users?status=active&sort=name&page=1"
Relationship to Other Structures ¶
Semigroup extends Magma by adding the associativity law:
- Magma: Has a binary operation
- Semigroup: Has an associative binary operation
- Monoid: Semigroup with an identity element
Converting between structures:
// Semigroup to Magma magma := SG.ToMagma(semigroup) // Semigroup to Monoid (requires identity element) // See the monoid package
Laws ¶
A valid Semigroup must satisfy the associativity law:
// Associativity: (a • b) • c = a • (b • c) s.Concat(s.Concat(a, b), c) == s.Concat(a, s.Concat(b, c))
This law ensures that the order of evaluation doesn't matter, allowing for parallel computation and optimization.
Function Reference ¶
Core Functions:
- MakeSemigroup[A](func(A, A) A) Semigroup[A] - Creates a semigroup from a binary operation
- Reverse[A](Semigroup[A]) Semigroup[A] - Returns the dual semigroup with swapped arguments
- ToMagma[A](Semigroup[A]) Magma[A] - Converts a semigroup to a magma
Built-in Semigroups:
- First[A]() Semigroup[A] - Always returns the first argument
- Last[A]() Semigroup[A] - Always returns the last argument
Higher-Order Functions:
- FunctionSemigroup[A, B](Semigroup[B]) Semigroup[func(A) B] - Lifts a semigroup to functions
Array Operations:
- ConcatAll[A](Semigroup[A]) func(A) func([]A) A - Concatenates array elements with initial value
- MonadConcatAll[A](Semigroup[A]) func([]A, A) A - Uncurried version of ConcatAll
- GenericConcatAll[GA ~[]A, A](Semigroup[A]) func(A) func(GA) A - Generic version for custom slices
- GenericMonadConcatAll[GA ~[]A, A](Semigroup[A]) func(GA, A) A - Generic uncurried version
Higher-Kinded Type Operations:
- ApplySemigroup[A, HKTA, HKTFA](fmap, fap, Semigroup[A]) Semigroup[HKTA] - Semigroup for applicatives
- AltSemigroup[HKTA, LAZYHKTA](falt) Semigroup[HKTA] - Semigroup for alternatives
Related Packages ¶
- github.com/IBM/fp-go/v2/magma - Base algebraic structure without associativity
- github.com/IBM/fp-go/v2/monoid - Semigroup with identity element
- github.com/IBM/fp-go/v2/number - Numeric semigroups (sum, product, min, max)
- github.com/IBM/fp-go/v2/string - String semigroups
- github.com/IBM/fp-go/v2/array - Array operations using semigroups
- github.com/IBM/fp-go/v2/function - Function composition utilities
Index ¶
- func ConcatAll[A any](s Semigroup[A]) func(A) func([]A) A
- func GenericConcatAll[GA ~[]A, A any](s Semigroup[A]) func(A) func(GA) A
- func GenericMonadConcatAll[GA ~[]A, A any](s Semigroup[A]) func(GA, A) A
- func MonadConcatAll[A any](s Semigroup[A]) func([]A, A) A
- func ToMagma[A any](s Semigroup[A]) M.Magma[A]
- type Semigroup
- func AltSemigroup[HKTA any, LAZYHKTA ~func() HKTA](falt func(HKTA, LAZYHKTA) HKTA) Semigroup[HKTA]
- func ApplySemigroup[A, HKTA, HKTFA any](fmap func(HKTA, func(A) func(A) A) HKTFA, fap func(HKTFA, HKTA) HKTA, ...) Semigroup[HKTA]
- func First[A any]() Semigroup[A]
- func FunctionSemigroup[A, B any](s Semigroup[B]) Semigroup[func(A) B]
- func Last[A any]() Semigroup[A]
- func MakeSemigroup[A any](c func(A, A) A) Semigroup[A]
- func Reverse[A any](m Semigroup[A]) Semigroup[A]
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ConcatAll ¶
ConcatAll creates a curried function that concatenates all elements in a slice with an initial value using the provided semigroup operation.
The returned function first takes the initial value, then takes the slice, and finally returns the result of combining all elements left-to-right with the initial value.
This is a convenience wrapper around GenericConcatAll for standard slices.
Example:
import N "github.com/IBM/fp-go/v2/number"
sum := N.SemigroupSum[int]()
concatAll := semigroup.ConcatAll(sum)
result := concatAll(10)([]int{1, 2, 3}) // 10 + 1 + 2 + 3 = 16
func GenericConcatAll ¶
GenericConcatAll creates a curried function that concatenates all elements in a generic slice with an initial value using the provided semigroup operation.
The returned function first takes the initial value, then takes the slice, and finally returns the result of combining all elements left-to-right with the initial value.
Example:
type MyInts []int
import N "github.com/IBM/fp-go/v2/number"
sum := N.SemigroupSum[int]()
concatAll := semigroup.GenericConcatAll[MyInts](sum)
result := concatAll(10)(MyInts{1, 2, 3}) // 10 + 1 + 2 + 3 = 16
func GenericMonadConcatAll ¶
GenericMonadConcatAll creates a function that concatenates all elements in a generic slice with an initial value using the provided semigroup operation. This is the uncurried version that takes both the slice and initial value as parameters.
The function processes elements left-to-right, combining them with the initial value.
Example:
type MyInts []int
import N "github.com/IBM/fp-go/v2/number"
sum := N.SemigroupSum[int]()
concatAll := semigroup.GenericMonadConcatAll[MyInts](sum)
result := concatAll(MyInts{1, 2, 3}, 10) // 10 + 1 + 2 + 3 = 16
func MonadConcatAll ¶
MonadConcatAll creates a function that concatenates all elements in a slice with an initial value using the provided semigroup operation. This is the uncurried version that takes both the slice and initial value as parameters.
This is a convenience wrapper around GenericMonadConcatAll for standard slices.
Example:
import N "github.com/IBM/fp-go/v2/number"
sum := N.SemigroupSum[int]()
concatAll := semigroup.MonadConcatAll(sum)
result := concatAll([]int{1, 2, 3}, 10) // 10 + 1 + 2 + 3 = 16
func ToMagma ¶
ToMagma converts a Semigroup to a Magma. Since Semigroup extends Magma, this is simply an identity conversion that changes the type perspective without modifying the underlying structure.
Example:
sg := semigroup.First[int]() magma := semigroup.ToMagma(sg)
Example ¶
package main
import (
"fmt"
"github.com/IBM/fp-go/v2/semigroup"
)
func main() {
sg := semigroup.First[int]()
magma := semigroup.ToMagma(sg)
result := magma.Concat(1, 2)
fmt.Println(result)
}
Output: 1
Types ¶
type Semigroup ¶
Semigroup represents an algebraic structure with an associative binary operation. It extends the Magma interface by requiring that the Concat operation be associative, meaning (a • b) • c = a • (b • c) for all values a, b, c.
Example:
import N "github.com/IBM/fp-go/v2/number" sum := N.SemigroupSum[int]() result := sum.Concat(sum.Concat(1, 2), 3) // Same as sum.Concat(1, sum.Concat(2, 3))
Example ¶
package main
import (
"fmt"
N "github.com/IBM/fp-go/v2/number"
)
func main() {
sum := N.SemigroupSum[int]()
result := sum.Concat(sum.Concat(1, 2), 3)
fmt.Println(result)
}
Output: 6
func AltSemigroup ¶
func AltSemigroup[HKTA any, LAZYHKTA ~func() HKTA]( falt func(HKTA, LAZYHKTA) HKTA, ) Semigroup[HKTA]
AltSemigroup creates a Semigroup for alternative functors (types with an alt operation). The alt operation provides a way to combine two values of the same higher-kinded type, typically representing alternative computations or choices.
The function takes an alt operation that accepts a value and a lazy (thunked) value, and returns a Semigroup that eagerly evaluates both values before combining them.
Type parameters:
- HKTA: The higher-kinded type (e.g., Option[A], Either[E, A])
- LAZYHKTA: A lazy/thunked version of HKTA (must be func() HKTA)
Example:
import O "github.com/IBM/fp-go/v2/option"
// Alt operation for Option: returns first if Some, otherwise evaluates second
optionAlt := func(first O.Option[int], second func() O.Option[int]) O.Option[int] {
return O.Alt(first, second)
}
sg := semigroup.AltSemigroup(optionAlt)
result := sg.Concat(O.Some(1), O.Some(2)) // Returns: Some(1)
result2 := sg.Concat(O.None[int](), O.Some(2)) // Returns: Some(2)
func ApplySemigroup ¶
func ApplySemigroup[A, HKTA, HKTFA any]( fmap func(HKTA, func(A) func(A) A) HKTFA, fap func(HKTFA, HKTA) HKTA, s Semigroup[A], ) Semigroup[HKTA]
ApplySemigroup creates a Semigroup for applicative functors (types with map and ap operations). Given a Semigroup[A], it lifts it to work with higher-kinded types containing A values.
The resulting semigroup combines two HKTA values by:
- Mapping the curried concat operation over the first value
- Applying the result to the second value using the ap operation
This allows semigroup operations to be performed inside applicative contexts like Option, Either, or Array.
Type parameters:
- A: The base type with a semigroup operation
- HKTA: The higher-kinded type containing A (e.g., Option[A], Either[E, A])
- HKTFA: The higher-kinded type containing a function (e.g., Option[func(A) A])
Parameters:
- fmap: Maps a function over the applicative functor
- fap: Applies a function in the applicative context to a value in the context
- s: The base semigroup for type A
Example:
import (
O "github.com/IBM/fp-go/v2/option"
N "github.com/IBM/fp-go/v2/number"
)
intSum := N.SemigroupSum[int]()
optionSG := semigroup.ApplySemigroup(
O.Map[int, func(int) int],
O.Ap[int, int],
intSum,
)
result := optionSG.Concat(O.Some(5), O.Some(10)) // Some(15)
result2 := optionSG.Concat(O.None[int](), O.Some(10)) // None
func First ¶
First creates a Semigroup that always returns the first argument. This is useful when you want a semigroup where earlier values take precedence.
Example:
first := semigroup.First[int]() result := first.Concat(1, 2) // Returns: 1
Example ¶
package main
import (
"fmt"
"github.com/IBM/fp-go/v2/semigroup"
)
func main() {
first := semigroup.First[int]()
result := first.Concat(1, 2)
fmt.Println(result)
}
Output: 1
func FunctionSemigroup ¶
FunctionSemigroup lifts a Semigroup to work with functions that return values in that semigroup. Given a Semigroup[B], it creates a Semigroup[func(A) B] where functions are combined by applying both functions to the same input and combining the results using the original semigroup.
Example:
import N "github.com/IBM/fp-go/v2/number"
intSum := N.SemigroupSum[int]()
funcSG := semigroup.FunctionSemigroup[string](intSum)
f := func(s string) int { return len(s) }
g := func(s string) int { return len(s) * 2 }
combined := funcSG.Concat(f, g)
result := combined("hello") // 5 + 10 = 15
Example ¶
package main
import (
"fmt"
N "github.com/IBM/fp-go/v2/number"
"github.com/IBM/fp-go/v2/semigroup"
)
func main() {
intSum := N.SemigroupSum[int]()
funcSG := semigroup.FunctionSemigroup[string](intSum)
f := func(s string) int { return len(s) }
g := func(s string) int { return len(s) * 2 }
combined := funcSG.Concat(f, g)
result := combined("hello")
fmt.Println(result)
}
Output: 15
func Last ¶
Last creates a Semigroup that always returns the last argument. This is useful when you want a semigroup where later values take precedence.
Example:
last := semigroup.Last[int]() result := last.Concat(1, 2) // Returns: 2
Example ¶
package main
import (
"fmt"
"github.com/IBM/fp-go/v2/semigroup"
)
func main() {
last := semigroup.Last[int]()
result := last.Concat(1, 2)
fmt.Println(result)
}
Output: 2
func MakeSemigroup ¶
MakeSemigroup creates a Semigroup from a binary operation function. The provided function must be associative to form a valid semigroup.
Example:
// Create a string concatenation semigroup
strConcat := semigroup.MakeSemigroup(func(a, b string) string {
return a + b
})
result := strConcat.Concat("Hello, ", "World!") // "Hello, World!"
Example ¶
package main
import (
"fmt"
"github.com/IBM/fp-go/v2/semigroup"
)
func main() {
strConcat := semigroup.MakeSemigroup(func(a, b string) string {
return a + b
})
result := strConcat.Concat("Hello, ", "World!")
fmt.Println(result)
}
Output: Hello, World!
func Reverse ¶
Reverse returns the dual of a Semigroup, obtained by swapping the arguments of Concat. If the original operation is a • b, the reversed operation is b • a.
Example:
sub := semigroup.MakeSemigroup(func(a, b int) int { return a - b })
reversed := semigroup.Reverse(sub)
result1 := sub.Concat(10, 3) // 10 - 3 = 7
result2 := reversed.Concat(10, 3) // 3 - 10 = -7
Example ¶
package main
import (
"fmt"
"github.com/IBM/fp-go/v2/semigroup"
)
func main() {
sub := semigroup.MakeSemigroup(func(a, b int) int { return a - b })
reversed := semigroup.Reverse(sub)
result1 := sub.Concat(10, 3)
result2 := reversed.Concat(10, 3)
fmt.Println(result1, result2)
}
Output: 7 -7