mirror of
https://github.com/IBM/fp-go.git
synced 2025-12-19 23:42:05 +02:00
407 lines
12 KiB
Go
407 lines
12 KiB
Go
// Copyright (c) 2023 - 2025 IBM Corp.
|
|
// All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package endomorphism
|
|
|
|
import (
|
|
"github.com/IBM/fp-go/v2/function"
|
|
A "github.com/IBM/fp-go/v2/internal/array"
|
|
)
|
|
|
|
// 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 Build[A any](e Endomorphism[A]) A {
|
|
var a A
|
|
return e(a)
|
|
}
|
|
|
|
// 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 ConcatAll[T any](es []Endomorphism[T]) Endomorphism[T] {
|
|
return A.Reduce(es, MonadCompose[T], function.Identity[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 Reduce[T any](es []Endomorphism[T]) T {
|
|
var t T
|
|
return A.Reduce(es, func(t T, e Endomorphism[T]) T { return e(t) }, t)
|
|
}
|