endomorphism

package
v2.1.0 Latest Latest
Warning

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

Go to latest
Published: Jan 4, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

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.

  • 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

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:

  1. Start with an initial (often empty) state
  2. Apply a series of transformations/configurations
  3. Return the final built object

Endomorphisms provide the same pattern functionally:

  1. Start with zero value: var a A
  2. Apply composed endomorphisms: e(a)
  3. 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

  1. **Composability**: Builders can be composed using monoid operations
  2. **Immutability**: Each transformation returns a new value (if implemented immutably)
  3. **Type Safety**: The type system ensures all transformations work on the same type
  4. **Reusability**: Individual builder functions can be reused and combined differently
  5. **Testability**: Each transformation can be tested independently
  6. **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:

  1. Starts with the zero value of type T
  2. Applies each endomorphism in the slice from left to right
  3. 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

  1. **Sequential Processing**: Apply transformations in order
  2. **Pipeline Execution**: Execute a pipeline from start to finish
  3. **Builder Pattern**: Build objects step by step
  4. **State Machines**: Apply state transitions in sequence
  5. **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

  1. **Pipeline Construction**: Build transformation pipelines from individual steps
  2. **Configuration Building**: Combine multiple configuration setters
  3. **Data Transformation**: Chain multiple data transformations
  4. **Middleware Composition**: Combine middleware functions
  5. **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

func FromSemigroup[A any](s S.Semigroup[A]) Kleisli[A]

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

Jump to

Keyboard shortcuts

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