semigroup

package
v2.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 18, 2025 License: Apache-2.0 Imports: 2 Imported by: 0

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
  • 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

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ConcatAll

func ConcatAll[A any](s Semigroup[A]) func(A) func([]A) A

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

func GenericConcatAll[GA ~[]A, A any](s Semigroup[A]) func(A) func(GA) A

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

func GenericMonadConcatAll[GA ~[]A, A any](s Semigroup[A]) func(GA, A) A

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

func MonadConcatAll[A any](s Semigroup[A]) func([]A, A) A

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

func ToMagma[A any](s Semigroup[A]) M.Magma[A]

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

type Semigroup[A any] interface {
	M.Magma[A]
}

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:

  1. Mapping the curried concat operation over the first value
  2. 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

func First[A any]() Semigroup[A]

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

func FunctionSemigroup[A, B any](s Semigroup[B]) Semigroup[func(A) B]

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

func Last[A any]() Semigroup[A]

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

func MakeSemigroup[A any](c func(A, A) A) Semigroup[A]

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

func Reverse[A any](m Semigroup[A]) Semigroup[A]

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

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL