// 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 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 with map and ap operations applySG := SG.ApplySemigroup( fmap, // func(HKT, func(A) func(A) A) HKT fap, // func(HKT, HKT) HKT baseSemigroup, ) AltSemigroup - Creates a semigroup for alternative functors: // For a type HKT with an alt operation altSG := SG.AltSemigroup( falt, // func(HKT, func() HKT) HKT ) # 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 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 == "" { 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 querySG := SG.MakeSemigroup(func(a, b string) string { if a == "" { return b } if 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 */ package semigroup