Documentation
¶
Overview ¶
Package endomorphism provides functional programming utilities for working with endomorphisms.
An endomorphism is a function from a type to itself: func(A) A. This package provides various operations and algebraic structures for composing and manipulating endomorphisms.
Core Concepts ¶
An Endomorphism[A] is simply a function that takes a value of type A and returns a value of the same type A. This simple concept has powerful algebraic properties:
- Identity: The identity function is an endomorphism
- Composition: Endomorphisms can be composed to form new endomorphisms
- Monoid: Endomorphisms form a monoid under composition with identity as the empty element
Basic Usage ¶
Creating and composing endomorphisms:
import ( "github.com/IBM/fp-go/v2/endomorphism" ) // Define some endomorphisms double := N.Mul(2) increment := N.Add(1) // Compose them (RIGHT-TO-LEFT execution) composed := endomorphism.Compose(double, increment) result := composed(5) // increment(5) then double: (5 + 1) * 2 = 12 // Chain them (LEFT-TO-RIGHT execution) chained := endomorphism.MonadChain(double, increment) result2 := chained(5) // double(5) then increment: (5 * 2) + 1 = 11
Monoid Operations ¶
Endomorphisms form a monoid, which means you can combine multiple endomorphisms. The monoid uses Compose, which executes RIGHT-TO-LEFT:
import ( "github.com/IBM/fp-go/v2/endomorphism" M "github.com/IBM/fp-go/v2/monoid" ) // Get the monoid for int endomorphisms monoid := endomorphism.Monoid[int]() // Combine multiple endomorphisms (RIGHT-TO-LEFT execution) combined := M.ConcatAll(monoid)( N.Mul(2), // applied third N.Add(1), // applied second N.Mul(3), // applied first ) result := combined(5) // (5 * 3) = 15, (15 + 1) = 16, (16 * 2) = 32
Monad Operations ¶
The package also provides monadic operations for endomorphisms. MonadChain executes LEFT-TO-RIGHT, unlike Compose:
// Chain allows sequencing of endomorphisms (LEFT-TO-RIGHT) f := N.Mul(2) g := N.Add(1) chained := endomorphism.MonadChain(f, g) // f first, then g result := chained(5) // (5 * 2) + 1 = 11
Compose vs Chain ¶
The key difference between Compose and Chain/MonadChain is execution order:
double := N.Mul(2) increment := N.Add(1) // Compose: RIGHT-TO-LEFT (mathematical composition) composed := endomorphism.Compose(double, increment) result1 := composed(5) // increment(5) * 2 = (5 + 1) * 2 = 12 // MonadChain: LEFT-TO-RIGHT (sequential application) chained := endomorphism.MonadChain(double, increment) result2 := chained(5) // double(5) + 1 = (5 * 2) + 1 = 11
Type Safety ¶
The package uses Go generics to ensure type safety. All operations preserve the type of the endomorphism, preventing type mismatches at compile time.
Related Packages ¶
- function: Provides general function composition utilities
- identity: Provides identity functor operations
- monoid: Provides monoid algebraic structure
- semigroup: Provides semigroup algebraic structure
Example (Build_basicUsage) ¶
Example_build_basicUsage demonstrates basic usage of the Build function to construct a value from the zero value using endomorphisms.
package main
import (
"fmt"
A "github.com/IBM/fp-go/v2/array"
"github.com/IBM/fp-go/v2/endomorphism"
M "github.com/IBM/fp-go/v2/monoid"
N "github.com/IBM/fp-go/v2/number"
)
func main() {
// Define simple endomorphisms
addTen := N.Add(10)
double := N.Mul(2)
// Compose them using monoid (RIGHT-TO-LEFT execution)
// double is applied first, then addTen
builder := M.ConcatAll(endomorphism.Monoid[int]())(A.From(
addTen,
double,
))
// Build from zero value: 0 * 2 = 0, 0 + 10 = 10
result := endomorphism.Build(builder)
fmt.Println(result)
}
Output: 10
Example (Build_conditionalBuilder) ¶
Example_build_conditionalBuilder demonstrates conditional building using endomorphisms.
package main
import (
"fmt"
"github.com/IBM/fp-go/v2/endomorphism"
M "github.com/IBM/fp-go/v2/monoid"
)
func main() {
type Settings struct {
Theme string
FontSize int
AutoSave bool
Animations bool
}
withTheme := func(theme string) endomorphism.Endomorphism[Settings] {
return func(s Settings) Settings {
s.Theme = theme
return s
}
}
withFontSize := func(size int) endomorphism.Endomorphism[Settings] {
return func(s Settings) Settings {
s.FontSize = size
return s
}
}
withAutoSave := func(enabled bool) endomorphism.Endomorphism[Settings] {
return func(s Settings) Settings {
s.AutoSave = enabled
return s
}
}
withAnimations := func(enabled bool) endomorphism.Endomorphism[Settings] {
return func(s Settings) Settings {
s.Animations = enabled
return s
}
}
// Build settings conditionally
isDarkMode := true
isAccessibilityMode := true
// Note: Monoid executes RIGHT-TO-LEFT, so later items in the slice are applied first
// We need to add items in reverse order for the desired effect
builders := []endomorphism.Endomorphism[Settings]{}
if isAccessibilityMode {
builders = append(builders, withFontSize(18)) // Will be applied last (overrides)
builders = append(builders, withAnimations(false))
}
if isDarkMode {
builders = append(builders, withTheme("dark"))
} else {
builders = append(builders, withTheme("light"))
}
builders = append(builders, withAutoSave(true))
builders = append(builders, withFontSize(14)) // Will be applied first
settingsBuilder := M.ConcatAll(endomorphism.Monoid[Settings]())(builders)
settings := endomorphism.Build(settingsBuilder)
fmt.Printf("Theme: %s\n", settings.Theme)
fmt.Printf("FontSize: %d\n", settings.FontSize)
fmt.Printf("AutoSave: %v\n", settings.AutoSave)
fmt.Printf("Animations: %v\n", settings.Animations)
}
Output: Theme: dark FontSize: 18 AutoSave: true Animations: false
Example (Build_configBuilder) ¶
Example_build_configBuilder demonstrates using Build as a configuration builder pattern.
package main
import (
"fmt"
"time"
"github.com/IBM/fp-go/v2/endomorphism"
M "github.com/IBM/fp-go/v2/monoid"
)
func main() {
type Config struct {
Host string
Port int
Timeout time.Duration
Debug bool
}
// Define builder functions as endomorphisms
withHost := func(host string) endomorphism.Endomorphism[Config] {
return func(c Config) Config {
c.Host = host
return c
}
}
withPort := func(port int) endomorphism.Endomorphism[Config] {
return func(c Config) Config {
c.Port = port
return c
}
}
withTimeout := func(d time.Duration) endomorphism.Endomorphism[Config] {
return func(c Config) Config {
c.Timeout = d
return c
}
}
withDebug := func(debug bool) endomorphism.Endomorphism[Config] {
return func(c Config) Config {
c.Debug = debug
return c
}
}
// Compose builders using monoid
configBuilder := M.ConcatAll(endomorphism.Monoid[Config]())([]endomorphism.Endomorphism[Config]{
withHost("localhost"),
withPort(8080),
withTimeout(30 * time.Second),
withDebug(true),
})
// Build the configuration from zero value
config := endomorphism.Build(configBuilder)
fmt.Printf("Host: %s\n", config.Host)
fmt.Printf("Port: %d\n", config.Port)
fmt.Printf("Timeout: %v\n", config.Timeout)
fmt.Printf("Debug: %v\n", config.Debug)
}
Output: Host: localhost Port: 8080 Timeout: 30s Debug: true
Example (Build_personBuilder) ¶
Example_build_personBuilder demonstrates building a complex struct using the builder pattern.
package main
import (
"fmt"
"github.com/IBM/fp-go/v2/endomorphism"
M "github.com/IBM/fp-go/v2/monoid"
)
func main() {
type Person struct {
FirstName string
LastName string
Age int
Email string
}
// Define builder functions
withFirstName := func(name string) endomorphism.Endomorphism[Person] {
return func(p Person) Person {
p.FirstName = name
return p
}
}
withLastName := func(name string) endomorphism.Endomorphism[Person] {
return func(p Person) Person {
p.LastName = name
return p
}
}
withAge := func(age int) endomorphism.Endomorphism[Person] {
return func(p Person) Person {
p.Age = age
return p
}
}
withEmail := func(email string) endomorphism.Endomorphism[Person] {
return func(p Person) Person {
p.Email = email
return p
}
}
// Build a person
personBuilder := M.ConcatAll(endomorphism.Monoid[Person]())([]endomorphism.Endomorphism[Person]{
withFirstName("Alice"),
withLastName("Smith"),
withAge(30),
withEmail("alice.smith@example.com"),
})
person := endomorphism.Build(personBuilder)
fmt.Printf("%s %s, Age: %d, Email: %s\n",
person.FirstName, person.LastName, person.Age, person.Email)
}
Output: Alice Smith, Age: 30, Email: alice.smith@example.com
Example (Build_stringBuilder) ¶
Example_build_stringBuilder demonstrates building a string using endomorphisms.
package main
import (
"fmt"
"github.com/IBM/fp-go/v2/endomorphism"
M "github.com/IBM/fp-go/v2/monoid"
)
func main() {
// Define string transformation endomorphisms
appendHello := func(s string) string { return s + "Hello" }
appendSpace := func(s string) string { return s + " " }
appendWorld := func(s string) string { return s + "World" }
appendExclamation := func(s string) string { return s + "!" }
// Compose transformations (RIGHT-TO-LEFT execution)
stringBuilder := M.ConcatAll(endomorphism.Monoid[string]())([]endomorphism.Endomorphism[string]{
appendHello,
appendSpace,
appendWorld,
appendExclamation,
})
// Build the string from empty string
result := endomorphism.Build(stringBuilder)
fmt.Println(result)
}
Output: !World Hello
Index ¶
- func Build[A any](e Endomorphism[A]) A
- func Curry2[FCT ~func(T0, T1) T1, T0, T1 any](f FCT) func(T0) Endomorphism[T1]deprecated
- func Curry3[FCT ~func(T0, T1, T2) T2, T0, T1, T2 any](f FCT) func(T0) func(T1) Endomorphism[T2]deprecated
- func Monoid[A any]() M.Monoid[Endomorphism[A]]
- func Reduce[T any](es []Endomorphism[T]) T
- func Semigroup[A any]() S.Semigroup[Endomorphism[A]]
- func Unwrap[F ~func(A) A, A any](f Endomorphism[A]) Fdeprecated
- type Endomorphism
- func ConcatAll[T any](es []Endomorphism[T]) Endomorphism[T]
- func Flatten[A any](mma Endomorphism[Endomorphism[A]]) Endomorphism[A]
- func Identity[A any]() Endomorphism[A]
- func Join[A any](f Kleisli[A]) Endomorphism[A]
- func MonadAp[A any](fab, fa Endomorphism[A]) Endomorphism[A]
- func MonadChain[A any](ma, f Endomorphism[A]) Endomorphism[A]
- func MonadChainFirst[A any](ma, f Endomorphism[A]) Endomorphism[A]
- func MonadCompose[A any](f, g Endomorphism[A]) Endomorphism[A]
- func MonadMap[A any](f, g Endomorphism[A]) Endomorphism[A]
- func Of[F ~func(A) A, A any](f F) Endomorphism[A]
- func Wrap[F ~func(A) A, A any](f F) Endomorphism[A]deprecated
- type Kleisli
- type Operator
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Build ¶
func Build[A any](e Endomorphism[A]) A
Build applies an endomorphism to the zero value of type A, effectively using the endomorphism as a builder pattern.
Endomorphism as Builder Pattern ¶
An endomorphism (a function from type A to type A) can be viewed as a builder pattern because it transforms a value of a type into another value of the same type. When you compose multiple endomorphisms together, you create a pipeline of transformations that build up a final value step by step.
The Build function starts with the zero value of type A and applies the endomorphism to it, making it particularly useful for building complex values from scratch using a functional composition of transformations.
Builder Pattern Characteristics ¶
Traditional builder patterns have these characteristics:
- Start with an initial (often empty) state
- Apply a series of transformations/configurations
- Return the final built object
Endomorphisms provide the same pattern functionally:
- Start with zero value: var a A
- Apply composed endomorphisms: e(a)
- Return the transformed value
Type Parameters ¶
- A: The type being built/transformed
Parameters ¶
- e: An endomorphism (or composition of endomorphisms) that transforms type A
Returns ¶
The result of applying the endomorphism to the zero value of type A
Example - Building a Configuration Object ¶
type Config struct {
Host string
Port int
Timeout time.Duration
Debug bool
}
// Define builder functions as endomorphisms
withHost := func(host string) Endomorphism[Config] {
return func(c Config) Config {
c.Host = host
return c
}
}
withPort := func(port int) Endomorphism[Config] {
return func(c Config) Config {
c.Port = port
return c
}
}
withTimeout := func(d time.Duration) Endomorphism[Config] {
return func(c Config) Config {
c.Timeout = d
return c
}
}
withDebug := func(debug bool) Endomorphism[Config] {
return func(c Config) Config {
c.Debug = debug
return c
}
}
// Compose builders using monoid operations
import M "github.com/IBM/fp-go/v2/monoid"
configBuilder := M.ConcatAll(Monoid[Config]())(
withHost("localhost"),
withPort(8080),
withTimeout(30 * time.Second),
withDebug(true),
)
// Build the final configuration
config := Build(configBuilder)
// Result: Config{Host: "localhost", Port: 8080, Timeout: 30s, Debug: true}
Example - Building a String with Transformations ¶
import (
"strings"
M "github.com/IBM/fp-go/v2/monoid"
)
// Define string transformation endomorphisms
appendHello := func(s string) string { return s + "Hello" }
appendSpace := func(s string) string { return s + " " }
appendWorld := func(s string) string { return s + "World" }
toUpper := strings.ToUpper
// Compose transformations
stringBuilder := M.ConcatAll(Monoid[string]())(
appendHello,
appendSpace,
appendWorld,
toUpper,
)
// Build the final string from empty string
result := Build(stringBuilder)
// Result: "HELLO WORLD"
Example - Building a Slice with Operations ¶
type IntSlice []int
appendValue := func(v int) Endomorphism[IntSlice] {
return func(s IntSlice) IntSlice {
return append(s, v)
}
}
sortSlice := func(s IntSlice) IntSlice {
sorted := make(IntSlice, len(s))
copy(sorted, s)
sort.Ints(sorted)
return sorted
}
// Build a sorted slice
sliceBuilder := M.ConcatAll(Monoid[IntSlice]())(
appendValue(5),
appendValue(2),
appendValue(8),
appendValue(1),
sortSlice,
)
result := Build(sliceBuilder)
// Result: IntSlice{1, 2, 5, 8}
Advantages of Endomorphism Builder Pattern ¶
- **Composability**: Builders can be composed using monoid operations
- **Immutability**: Each transformation returns a new value (if implemented immutably)
- **Type Safety**: The type system ensures all transformations work on the same type
- **Reusability**: Individual builder functions can be reused and combined differently
- **Testability**: Each transformation can be tested independently
- **Declarative**: The composition clearly expresses the building process
Comparison with Traditional Builder Pattern ¶
Traditional OOP Builder:
config := NewConfigBuilder().
WithHost("localhost").
WithPort(8080).
WithTimeout(30 * time.Second).
Build()
Endomorphism Builder:
config := Build(M.ConcatAll(Monoid[Config]())(
withHost("localhost"),
withPort(8080),
withTimeout(30 * time.Second),
))
Both achieve the same goal, but the endomorphism approach:
- Uses pure functions instead of methods
- Leverages algebraic properties (monoid) for composition
- Allows for more flexible composition patterns
- Integrates naturally with other functional programming constructs
func Curry2
deprecated
func Curry2[FCT ~func(T0, T1) T1, T0, T1 any](f FCT) func(T0) Endomorphism[T1]
Curry2 curries a binary function that returns an endomorphism.
This function takes a binary function f(T0, T1) T1 and converts it into a curried form that returns an endomorphism. The result is a function that takes the first argument and returns an endomorphism (a function T1 -> T1).
Deprecated: This function is no longer needed. Use function.Curry2 directly from the function package instead.
Parameters:
- f: A binary function where the return type matches the second parameter type
Returns:
- A curried function that takes T0 and returns an Endomorphism[T1]
Example:
// A binary function that adds two numbers
add := func(x, y int) int { return x + y }
curriedAdd := endomorphism.Curry2(add)
addFive := curriedAdd(5) // Returns an endomorphism that adds 5
result := addFive(10) // Returns: 15
func Curry3
deprecated
func Curry3[FCT ~func(T0, T1, T2) T2, T0, T1, T2 any](f FCT) func(T0) func(T1) Endomorphism[T2]
Curry3 curries a ternary function that returns an endomorphism.
This function takes a ternary function f(T0, T1, T2) T2 and converts it into a curried form. The result is a function that takes the first argument, returns a function that takes the second argument, and finally returns an endomorphism (a function T2 -> T2).
Deprecated: This function is no longer needed. Use function.Curry3 directly from the function package instead.
Parameters:
- f: A ternary function where the return type matches the third parameter type
Returns:
- A curried function that takes T0, then T1, and returns an Endomorphism[T2]
Example:
// A ternary function
combine := func(x, y, z int) int { return x + y + z }
curriedCombine := endomorphism.Curry3(combine)
addTen := curriedCombine(5)(5) // Returns an endomorphism that adds 10
result := addTen(20) // Returns: 30
func Monoid ¶
func Monoid[A any]() M.Monoid[Endomorphism[A]]
Monoid returns a Monoid for endomorphisms where concat is composition and empty is identity.
A monoid is a semigroup with an identity element. For endomorphisms:
- The binary operation is composition (Compose)
- The identity element is the identity function (Identity)
IMPORTANT: Concat uses Compose, which executes RIGHT-TO-LEFT:
- Concat(f, g) applies g first, then f
- ConcatAll applies functions from right to left
This satisfies the monoid laws:
- Right identity: Concat(x, Empty) = x
- Left identity: Concat(Empty, x) = x
- Associativity: Concat(x, Concat(y, z)) = Concat(Concat(x, y), z)
The returned monoid can be used with monoid operations like ConcatAll to combine multiple endomorphisms.
Returns:
- A Monoid[Endomorphism[A]] with composition (right-to-left) and identity
Example:
import M "github.com/IBM/fp-go/v2/monoid"
monoid := endomorphism.Monoid[int]()
double := N.Mul(2)
increment := N.Add(1)
square := func(x int) int { return x * x }
// Combine multiple endomorphisms (RIGHT-TO-LEFT execution)
combined := M.ConcatAll(monoid)(double, increment, square)
result := combined(5) // square(increment(double(5))) = square(increment(10)) = square(11) = 121
func Reduce ¶
func Reduce[T any](es []Endomorphism[T]) T
Reduce applies a slice of endomorphisms to the zero value of type T in LEFT-TO-RIGHT order.
This function is a convenience wrapper that:
- Starts with the zero value of type T
- Applies each endomorphism in the slice from left to right
- Returns the final transformed value
IMPORTANT: Execution order is LEFT-TO-RIGHT:
- Reduce([]Endomorphism{f, g, h}) applies f first, then g, then h
- This is the opposite of ConcatAll's RIGHT-TO-LEFT order
- Each endomorphism receives the result of the previous one
This is equivalent to: Build(ConcatAll(reverse(es))) but more efficient and clearer for left-to-right sequential application.
Type Parameters ¶
- T: The type being transformed
Parameters ¶
- es: A slice of endomorphisms to apply sequentially
Returns ¶
The final value after applying all endomorphisms to the zero value
Example - Sequential Transformations ¶
double := N.Mul(2)
increment := N.Add(1)
square := func(x int) int { return x * x }
// Apply transformations LEFT-TO-RIGHT
result := Reduce([]Endomorphism[int]{double, increment, square})
// Execution: 0 -> double(0) = 0 -> increment(0) = 1 -> square(1) = 1
// Result: 1
// With a non-zero starting point, use a custom initial value:
addTen := N.Add(10)
result2 := Reduce([]Endomorphism[int]{addTen, double, increment})
// Execution: 0 -> addTen(0) = 10 -> double(10) = 20 -> increment(20) = 21
// Result: 21
Example - Building a String ¶
appendHello := func(s string) string { return s + "Hello" }
appendSpace := func(s string) string { return s + " " }
appendWorld := func(s string) string { return s + "World" }
// Build string LEFT-TO-RIGHT
result := Reduce([]Endomorphism[string]{
appendHello,
appendSpace,
appendWorld,
})
// Execution: "" -> "Hello" -> "Hello " -> "Hello World"
// Result: "Hello World"
Example - Configuration Building ¶
type Settings struct {
Theme string
FontSize int
}
withTheme := func(theme string) Endomorphism[Settings] {
return func(s Settings) Settings {
s.Theme = theme
return s
}
}
withFontSize := func(size int) Endomorphism[Settings] {
return func(s Settings) Settings {
s.FontSize = size
return s
}
}
// Build settings LEFT-TO-RIGHT
settings := Reduce([]Endomorphism[Settings]{
withTheme("dark"),
withFontSize(14),
})
// Result: Settings{Theme: "dark", FontSize: 14}
Comparison with ConcatAll ¶
// ConcatAll: RIGHT-TO-LEFT composition, returns endomorphism
endo := ConcatAll([]Endomorphism[int]{f, g, h})
result1 := endo(value) // Applies h, then g, then f
// Reduce: LEFT-TO-RIGHT application, returns final value
result2 := Reduce([]Endomorphism[int]{f, g, h})
// Applies f to zero, then g, then h
Use Cases ¶
- **Sequential Processing**: Apply transformations in order
- **Pipeline Execution**: Execute a pipeline from start to finish
- **Builder Pattern**: Build objects step by step
- **State Machines**: Apply state transitions in sequence
- **Data Flow**: Transform data through multiple stages
func Semigroup ¶
func Semigroup[A any]() S.Semigroup[Endomorphism[A]]
Semigroup returns a Semigroup for endomorphisms where the concat operation is function composition.
A semigroup is an algebraic structure with an associative binary operation. For endomorphisms, this operation is composition (Compose). This means:
- Concat(f, Concat(g, h)) = Concat(Concat(f, g), h)
IMPORTANT: Concat uses Compose, which executes RIGHT-TO-LEFT:
- Concat(f, g) applies g first, then f
- This is equivalent to Compose(f, g)
The returned semigroup can be used with semigroup operations to combine multiple endomorphisms.
Returns:
- A Semigroup[Endomorphism[A]] where concat is composition (right-to-left)
Example:
import S "github.com/IBM/fp-go/v2/semigroup" sg := endomorphism.Semigroup[int]() double := N.Mul(2) increment := N.Add(1) // Combine using the semigroup (RIGHT-TO-LEFT execution) combined := sg.Concat(double, increment) result := combined(5) // (5 + 1) * 2 = 12 (increment first, then double)
func Unwrap
deprecated
func Unwrap[F ~func(A) A, A any](f Endomorphism[A]) F
Unwrap converts any Endomorphism to a function.
Deprecated: This function is no longer needed due to Go's type compatibility. Endomorphisms can be used directly as functions.
Types ¶
type Endomorphism ¶
type Endomorphism[A any] = func(A) A
Endomorphism represents a function from a type to itself.
An endomorphism is a unary function that takes a value of type A and returns a value of the same type A. Mathematically, it's a function A → A.
Endomorphisms have several important properties:
- They can be composed: if f and g are endomorphisms, then f ∘ g is also an endomorphism
- The identity function is an endomorphism
- They form a monoid under composition
Example:
// Simple endomorphisms on integers double := N.Mul(2) increment := N.Add(1) // Both are endomorphisms of type Endomorphism[int] var f endomorphism.Endomorphism[int] = double var g endomorphism.Endomorphism[int] = increment
func ConcatAll ¶
func ConcatAll[T any](es []Endomorphism[T]) Endomorphism[T]
ConcatAll combines multiple endomorphisms into a single endomorphism using composition.
This function takes a slice of endomorphisms and combines them using the monoid's concat operation (which is composition). The resulting endomorphism, when applied, will execute all the input endomorphisms in RIGHT-TO-LEFT order (mathematical composition order).
IMPORTANT: Execution order is RIGHT-TO-LEFT:
- ConcatAll([]Endomorphism{f, g, h}) creates an endomorphism that applies h, then g, then f
- This is equivalent to f ∘ g ∘ h in mathematical notation
- The last endomorphism in the slice is applied first
If the slice is empty, returns the identity endomorphism.
Type Parameters ¶
- T: The type that the endomorphisms operate on
Parameters ¶
- es: A slice of endomorphisms to combine
Returns ¶
A single endomorphism that represents the composition of all input endomorphisms
Example - Basic Composition ¶
double := N.Mul(2)
increment := N.Add(1)
square := func(x int) int { return x * x }
// Combine endomorphisms (RIGHT-TO-LEFT execution)
combined := ConcatAll([]Endomorphism[int]{double, increment, square})
result := combined(5)
// Execution: square(5) = 25, increment(25) = 26, double(26) = 52
// Result: 52
Example - Building with ConcatAll ¶
type Config struct {
Host string
Port int
}
withHost := func(host string) Endomorphism[Config] {
return func(c Config) Config {
c.Host = host
return c
}
}
withPort := func(port int) Endomorphism[Config] {
return func(c Config) Config {
c.Port = port
return c
}
}
// Combine configuration builders
configBuilder := ConcatAll([]Endomorphism[Config]{
withHost("localhost"),
withPort(8080),
})
// Apply to zero value
config := Build(configBuilder)
// Result: Config{Host: "localhost", Port: 8080}
Example - Empty Slice ¶
// Empty slice returns identity
identity := ConcatAll([]Endomorphism[int]{})
result := identity(42) // Returns: 42
Relationship to Monoid ¶
ConcatAll is equivalent to using M.ConcatAll with the endomorphism Monoid:
import M "github.com/IBM/fp-go/v2/monoid" // These are equivalent: result1 := ConcatAll(endomorphisms) result2 := M.ConcatAll(Monoid[T]())(endomorphisms)
Use Cases ¶
- **Pipeline Construction**: Build transformation pipelines from individual steps
- **Configuration Building**: Combine multiple configuration setters
- **Data Transformation**: Chain multiple data transformations
- **Middleware Composition**: Combine middleware functions
- **Validation Chains**: Compose multiple validation functions
func Flatten ¶
func Flatten[A any](mma Endomorphism[Endomorphism[A]]) Endomorphism[A]
Flatten collapses a nested endomorphism into a single endomorphism.
Given an endomorphism that transforms endomorphisms (Endomorphism[Endomorphism[A]]), Flatten produces a simple endomorphism by applying the outer transformation to the identity function. This is the monadic join operation for the Endomorphism monad.
The function applies the nested endomorphism to Identity[A] to extract the inner endomorphism, effectively "flattening" the two layers into one.
Type Parameters:
- A: The type being transformed by the endomorphisms
Parameters:
- mma: A nested endomorphism that transforms endomorphisms
Returns:
- An endomorphism that applies the transformation directly to values of type A
Example:
type Counter struct {
Value int
}
// An endomorphism that wraps another endomorphism
addThenDouble := func(endo Endomorphism[Counter]) Endomorphism[Counter] {
return func(c Counter) Counter {
c = endo(c) // Apply the input endomorphism
c.Value = c.Value * 2 // Then double
return c
}
}
flattened := Flatten(addThenDouble)
result := flattened(Counter{Value: 5}) // Counter{Value: 10}
func Identity ¶
func Identity[A any]() Endomorphism[A]
Identity returns the identity endomorphism.
The identity endomorphism is a function that returns its input unchanged. It serves as the identity element for endomorphism composition, meaning:
- Compose(Identity(), f) = f
- Compose(f, Identity()) = f
This is the empty element of the endomorphism monoid.
Returns:
- An endomorphism that returns its input unchanged
Example:
id := endomorphism.Identity[int]() result := id(42) // Returns: 42 // Identity is neutral for composition double := N.Mul(2) composed := endomorphism.Compose(id, double) // composed behaves exactly like double
func Join ¶
func Join[A any](f Kleisli[A]) Endomorphism[A]
Join performs self-application of a function that produces endomorphisms.
Given a function that takes a value and returns an endomorphism of that same type, Join creates an endomorphism that applies the value to itself through the function. This operation is also known as the W combinator (warbler) in combinatory logic, or diagonal application.
The resulting endomorphism evaluates f(a)(a), applying the same value a to both the function f and the resulting endomorphism.
Type Parameters:
- A: The type being transformed
Parameters:
- f: A function that takes a value and returns an endomorphism of that type
Returns:
- An endomorphism that performs self-application: f(a)(a)
Example:
type Point struct {
X, Y int
}
// Create an endomorphism based on the input point
scaleBy := func(p Point) Endomorphism[Point] {
return func(p2 Point) Point {
return Point{
X: p2.X * p.X,
Y: p2.Y * p.Y,
}
}
}
selfScale := Join(scaleBy)
result := selfScale(Point{X: 3, Y: 4}) // Point{X: 9, Y: 16}
func MonadAp ¶
func MonadAp[A any](fab, fa Endomorphism[A]) Endomorphism[A]
MonadAp applies an endomorphism in a function to an endomorphism value.
For endomorphisms, Ap composes two endomorphisms using RIGHT-TO-LEFT composition. This is the applicative functor operation for endomorphisms.
IMPORTANT: Execution order is RIGHT-TO-LEFT (same as MonadCompose):
- fa is applied first to the input
- fab is applied to the result
Parameters:
- fab: An endomorphism to apply (outer function)
- fa: An endomorphism to apply first (inner function)
Returns:
- A new endomorphism that applies fa, then fab
Example:
double := N.Mul(2) increment := N.Add(1) result := endomorphism.MonadAp(double, increment) // Composes: double ∘ increment // result(5) = double(increment(5)) = double(6) = 12
func MonadChain ¶
func MonadChain[A any](ma, f Endomorphism[A]) Endomorphism[A]
MonadChain chains two endomorphisms together, executing them from left to right.
This is the monadic bind operation for endomorphisms. For endomorphisms, bind is simply left-to-right function composition: ma is applied first, then f.
IMPORTANT: The execution order is LEFT-TO-RIGHT:
- ma is applied first to the input
- f is applied to the result of ma
This is different from MonadCompose which executes RIGHT-TO-LEFT.
Parameters:
- ma: The first endomorphism to apply
- f: The second endomorphism to apply
Returns:
- A new endomorphism that applies ma, then f
Example:
double := N.Mul(2) increment := N.Add(1) // MonadChain executes LEFT-TO-RIGHT: double first, then increment chained := endomorphism.MonadChain(double, increment) result := chained(5) // (5 * 2) + 1 = 11 // Compare with MonadCompose which executes RIGHT-TO-LEFT: composed := endomorphism.MonadCompose(increment, double) result2 := composed(5) // (5 * 2) + 1 = 11 (same result, different parameter order)
func MonadChainFirst ¶
func MonadChainFirst[A any](ma, f Endomorphism[A]) Endomorphism[A]
MonadChainFirst chains two endomorphisms but returns the result of the first.
This applies ma first, then f, but discards the result of f and returns the result of ma. Useful for performing side-effects while preserving the original value.
Parameters:
- ma: The endomorphism whose result to keep
- f: The endomorphism to apply for its effect
Returns:
- A new endomorphism that applies both but returns ma's result
Example:
double := N.Mul(2)
log := func(x int) int { fmt.Println(x); return x }
chained := endomorphism.MonadChainFirst(double, log)
result := chained(5) // Prints 10, returns 10
func MonadCompose ¶
func MonadCompose[A any](f, g Endomorphism[A]) Endomorphism[A]
MonadCompose composes two endomorphisms, executing them from right to left.
MonadCompose creates a new endomorphism that applies f2 first, then f1. This follows the mathematical notation of function composition: (f1 ∘ f2)(x) = f1(f2(x))
IMPORTANT: The execution order is RIGHT-TO-LEFT:
- f2 is applied first to the input
- f1 is applied to the result of f2
This is different from Chain/MonadChain which executes LEFT-TO-RIGHT.
Parameters:
- f1: The second function to apply (outer function)
- f2: The first function to apply (inner function)
Returns:
- A new endomorphism that applies f2, then f1
Example:
double := N.Mul(2) increment := N.Add(1) // MonadCompose executes RIGHT-TO-LEFT: increment first, then double composed := endomorphism.MonadCompose(double, increment) result := composed(5) // (5 + 1) * 2 = 12 // Compare with Chain which executes LEFT-TO-RIGHT: chained := endomorphism.MonadChain(double, increment) result2 := chained(5) // (5 * 2) + 1 = 11
func MonadMap ¶
func MonadMap[A any](f, g Endomorphism[A]) Endomorphism[A]
MonadMap maps an endomorphism over another endomorphism using function composition.
For endomorphisms, Map is equivalent to Compose (RIGHT-TO-LEFT composition). This is the functor map operation for endomorphisms.
IMPORTANT: Execution order is RIGHT-TO-LEFT:
- g is applied first to the input
- f is applied to the result
Parameters:
- f: The function to map (outer function)
- g: The endomorphism to map over (inner function)
Returns:
- A new endomorphism that applies g, then f
Example:
double := N.Mul(2) increment := N.Add(1) mapped := endomorphism.MonadMap(double, increment) // mapped(5) = double(increment(5)) = double(6) = 12
func Of ¶
func Of[F ~func(A) A, A any](f F) Endomorphism[A]
Of converts any function to an Endomorphism.
This function provides a way to explicitly convert a function with the signature func(A) A into an Endomorphism[A] type. Due to Go's type system, this is often not necessary as the types are compatible, but it can be useful for clarity.
Parameters:
- f: A function from type A to type A
Returns:
- The same function as an Endomorphism[A]
Example:
myFunc := N.Mul(2) endo := endomorphism.Of(myFunc)
func Wrap
deprecated
func Wrap[F ~func(A) A, A any](f F) Endomorphism[A]
Wrap converts any function to an Endomorphism.
Deprecated: This function is no longer needed due to Go's type compatibility. You can directly use functions where Endomorphism is expected.
type Kleisli ¶
type Kleisli[A any] = func(A) Endomorphism[A]
Kleisli represents a Kleisli arrow for endomorphisms. It's a function from A to Endomorphism[A], used for composing endomorphic operations.
func FromSemigroup ¶
FromSemigroup converts a semigroup into a Kleisli arrow for endomorphisms.
This function takes a semigroup and returns a Kleisli arrow that, when given a value of type A, produces an endomorphism that concatenates that value with other values using the semigroup's Concat operation.
The resulting Kleisli arrow has the signature: func(A) Endomorphism[A] When called with a value 'x', it returns an endomorphism that concatenates 'x' with its input using the semigroup's binary operation.
Data Last Principle ¶
FromSemigroup follows the "data last" principle by using function.Bind2of2, which binds the second parameter of the semigroup's Concat operation. This means that for a semigroup with Concat(a, b), calling FromSemigroup(s)(x) creates an endomorphism that computes Concat(input, x), where the input data comes first and the bound value 'x' comes last.
For example, with string concatenation:
- Semigroup.Concat("Hello", "World") = "HelloWorld"
- FromSemigroup(semigroup)("World") creates: func(input) = Concat(input, "World")
- Applying it: endomorphism("Hello") = Concat("Hello", "World") = "HelloWorld"
This is particularly useful for creating endomorphisms from associative operations like string concatenation, number addition, list concatenation, etc.
Parameters:
- s: A semigroup providing the Concat operation for type A
Returns:
- A Kleisli arrow that converts values of type A into endomorphisms
Example:
import (
"github.com/IBM/fp-go/v2/endomorphism"
"github.com/IBM/fp-go/v2/semigroup"
)
// Create a semigroup for integer addition
addSemigroup := semigroup.MakeSemigroup(func(a, b int) int {
return a + b
})
// Convert it to a Kleisli arrow
addKleisli := endomorphism.FromSemigroup(addSemigroup)
// Use the Kleisli arrow to create an endomorphism that adds 5
// This follows "data last": the input data comes first, 5 comes last
addFive := addKleisli(5)
// Apply the endomorphism: Concat(10, 5) = 10 + 5 = 15
result := addFive(10) // result is 15
The function uses function.Bind2of2 to partially apply the semigroup's Concat operation, effectively currying it to create the desired Kleisli arrow while maintaining the "data last" principle.
type Operator ¶
type Operator[A any] = Endomorphism[Endomorphism[A]]
Operator represents a transformation from one endomorphism to another.
An Operator takes an endomorphism on type A and produces an endomorphism on type B. This is useful for lifting operations or transforming endomorphisms in a generic way.
Example:
// An operator that converts an int endomorphism to a string endomorphism
intToString := func(f endomorphism.Endomorphism[int]) endomorphism.Endomorphism[string] {
return func(s string) string {
n, _ := strconv.Atoi(s)
result := f(n)
return strconv.Itoa(result)
}
}
func Ap ¶
func Ap[A any](fa Endomorphism[A]) Operator[A]
Ap returns a function that applies an endomorphism to another endomorphism.
This is the curried version of MonadAp. It takes an endomorphism fa and returns a function that composes any endomorphism with fa using RIGHT-TO-LEFT composition.
IMPORTANT: Execution order is RIGHT-TO-LEFT:
- fa is applied first to the input
- The endomorphism passed to the returned function is applied to the result
Parameters:
- fa: The first endomorphism to apply (inner function)
Returns:
- A function that takes an endomorphism and composes it with fa (right-to-left)
Example:
increment := N.Add(1) applyIncrement := endomorphism.Ap(increment) double := N.Mul(2) composed := applyIncrement(double) // double ∘ increment // composed(5) = double(increment(5)) = double(6) = 12
func Chain ¶
func Chain[A any](f Endomorphism[A]) Operator[A]
Chain returns a function that chains an endomorphism with another, executing left to right.
This is the curried version of MonadChain. It takes an endomorphism f and returns a function that chains any endomorphism with f, applying the input endomorphism first, then f.
IMPORTANT: Execution order is LEFT-TO-RIGHT:
- The endomorphism passed to the returned function is applied first
- f is applied to the result
Parameters:
- f: The second endomorphism to apply
Returns:
- A function that takes an endomorphism and chains it with f (left-to-right)
Example:
increment := N.Add(1) chainWithIncrement := endomorphism.Chain(increment) double := N.Mul(2) // Chains double (first) with increment (second) chained := chainWithIncrement(double) result := chained(5) // (5 * 2) + 1 = 11
func ChainFirst ¶
func ChainFirst[A any](f Endomorphism[A]) Operator[A]
ChainFirst returns a function that chains for effect but preserves the original result.
This is the curried version of MonadChainFirst.
Parameters:
- f: The endomorphism to apply for its effect
Returns:
- A function that takes an endomorphism and chains it with f, keeping the first result
Example:
log := func(x int) int { fmt.Println(x); return x }
chainLog := endomorphism.ChainFirst(log)
double := N.Mul(2)
chained := chainLog(double)
result := chained(5) // Prints 10, returns 10
func Compose ¶
func Compose[A any](g Endomorphism[A]) Operator[A]
Compose returns a function that composes an endomorphism with another, executing right to left.
This is the curried version of MonadCompose. It takes an endomorphism g and returns a function that composes any endomorphism with g, applying g first (inner function), then the input endomorphism (outer function).
IMPORTANT: Execution order is RIGHT-TO-LEFT (mathematical composition):
- g is applied first to the input
- The endomorphism passed to the returned function is applied to the result of g
This follows the mathematical composition notation where Compose(g)(f) = f ∘ g
Parameters:
- g: The first endomorphism to apply (inner function)
Returns:
- A function that takes an endomorphism f and composes it with g (right-to-left)
Example:
increment := N.Add(1) composeWithIncrement := endomorphism.Compose(increment) double := N.Mul(2) // Composes double with increment (RIGHT-TO-LEFT: increment first, then double) composed := composeWithIncrement(double) result := composed(5) // (5 + 1) * 2 = 12 // Compare with Chain which executes LEFT-TO-RIGHT: chainWithIncrement := endomorphism.Chain(increment) chained := chainWithIncrement(double) result2 := chained(5) // (5 * 2) + 1 = 11
func Map ¶
func Map[A any](f Endomorphism[A]) Operator[A]
Map returns a function that maps an endomorphism over another endomorphism.
This is the curried version of MonadMap. It takes an endomorphism f and returns a function that maps f over any endomorphism using RIGHT-TO-LEFT composition.
IMPORTANT: Execution order is RIGHT-TO-LEFT (same as Compose):
- The endomorphism passed to the returned function is applied first
- f is applied to the result
For endomorphisms, Map is equivalent to Compose.
Parameters:
- f: The function to map (outer function)
Returns:
- A function that takes an endomorphism and maps f over it (right-to-left)
Example:
double := N.Mul(2) mapDouble := endomorphism.Map(double) increment := N.Add(1) mapped := mapDouble(increment) // mapped(5) = double(increment(5)) = double(6) = 12