mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-23 22:14:53 +02:00
270 lines
7.9 KiB
Go
270 lines
7.9 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 lazy provides a functional programming abstraction for synchronous computations
|
|
// without side effects. It represents deferred computations that are evaluated only when
|
|
// their result is needed.
|
|
//
|
|
// # Overview
|
|
//
|
|
// A Lazy[A] is simply a function that takes no arguments and returns a value of type A:
|
|
//
|
|
// type Lazy[A any] = func() A
|
|
//
|
|
// This allows you to defer the evaluation of a computation until it's actually needed,
|
|
// which is useful for:
|
|
// - Avoiding unnecessary computations
|
|
// - Creating infinite data structures
|
|
// - Implementing memoization
|
|
// - Composing computations in a pure functional style
|
|
//
|
|
// # Core Concepts
|
|
//
|
|
// The lazy package implements several functional programming patterns:
|
|
//
|
|
// **Functor**: Transform values inside a Lazy context using Map
|
|
//
|
|
// **Applicative**: Combine multiple Lazy computations using Ap and ApS
|
|
//
|
|
// **Monad**: Chain dependent computations using Chain and Bind
|
|
//
|
|
// **Memoization**: Cache computation results using Memoize
|
|
//
|
|
// # Basic Usage
|
|
//
|
|
// Creating and evaluating lazy computations:
|
|
//
|
|
// import (
|
|
// "fmt"
|
|
// "github.com/IBM/fp-go/v2/lazy"
|
|
// F "github.com/IBM/fp-go/v2/function"
|
|
// )
|
|
//
|
|
// // Create a lazy computation
|
|
// computation := lazy.Of(42)
|
|
//
|
|
// // Transform it
|
|
// doubled := F.Pipe1(
|
|
// computation,
|
|
// lazy.Map(func(x int) int { return x * 2 }),
|
|
// )
|
|
//
|
|
// // Evaluate when needed
|
|
// result := doubled() // 84
|
|
//
|
|
// # Memoization
|
|
//
|
|
// Lazy computations can be memoized to ensure they're evaluated only once:
|
|
//
|
|
// import "math/rand"
|
|
//
|
|
// // Without memoization - generates different values each time
|
|
// random := lazy.FromLazy(rand.Int)
|
|
// value1 := random() // e.g., 12345
|
|
// value2 := random() // e.g., 67890 (different)
|
|
//
|
|
// // With memoization - caches the first result
|
|
// memoized := lazy.Memoize(rand.Int)
|
|
// value1 := memoized() // e.g., 12345
|
|
// value2 := memoized() // 12345 (same as value1)
|
|
//
|
|
// # Chaining Computations
|
|
//
|
|
// Use Chain to compose dependent computations:
|
|
//
|
|
// getUserId := lazy.Of(123)
|
|
//
|
|
// getUser := F.Pipe1(
|
|
// getUserId,
|
|
// lazy.Chain(func(id int) lazy.Lazy[User] {
|
|
// return lazy.Of(fetchUser(id))
|
|
// }),
|
|
// )
|
|
//
|
|
// user := getUser()
|
|
//
|
|
// # Do-Notation Style
|
|
//
|
|
// The package supports do-notation style composition using Bind and ApS:
|
|
//
|
|
// type Config struct {
|
|
// Host string
|
|
// Port int
|
|
// }
|
|
//
|
|
// result := F.Pipe2(
|
|
// lazy.Do(Config{}),
|
|
// lazy.Bind(
|
|
// func(host string) func(Config) Config {
|
|
// return func(c Config) Config { c.Host = host; return c }
|
|
// },
|
|
// func(c Config) lazy.Lazy[string] {
|
|
// return lazy.Of("localhost")
|
|
// },
|
|
// ),
|
|
// lazy.Bind(
|
|
// func(port int) func(Config) Config {
|
|
// return func(c Config) Config { c.Port = port; return c }
|
|
// },
|
|
// func(c Config) lazy.Lazy[int] {
|
|
// return lazy.Of(8080)
|
|
// },
|
|
// ),
|
|
// )
|
|
//
|
|
// config := result() // Config{Host: "localhost", Port: 8080}
|
|
//
|
|
// # Traverse and Sequence
|
|
//
|
|
// Transform collections of values into lazy computations:
|
|
//
|
|
// // Transform array elements
|
|
// numbers := []int{1, 2, 3}
|
|
// doubled := F.Pipe1(
|
|
// numbers,
|
|
// lazy.TraverseArray(func(x int) lazy.Lazy[int] {
|
|
// return lazy.Of(x * 2)
|
|
// }),
|
|
// )
|
|
// result := doubled() // []int{2, 4, 6}
|
|
//
|
|
// // Sequence array of lazy computations
|
|
// computations := []lazy.Lazy[int]{
|
|
// lazy.Of(1),
|
|
// lazy.Of(2),
|
|
// lazy.Of(3),
|
|
// }
|
|
// result := lazy.SequenceArray(computations)() // []int{1, 2, 3}
|
|
//
|
|
// # Retry Logic
|
|
//
|
|
// The package includes retry functionality for computations that may fail:
|
|
//
|
|
// import (
|
|
// R "github.com/IBM/fp-go/v2/retry"
|
|
// "time"
|
|
// )
|
|
//
|
|
// policy := R.CapDelay(
|
|
// 2*time.Second,
|
|
// R.Monoid.Concat(
|
|
// R.ExponentialBackoff(10),
|
|
// R.LimitRetries(5),
|
|
// ),
|
|
// )
|
|
//
|
|
// action := func(status R.RetryStatus) lazy.Lazy[string] {
|
|
// return lazy.Of(fetchData())
|
|
// }
|
|
//
|
|
// check := func(value string) bool {
|
|
// return value == "" // retry if empty
|
|
// }
|
|
//
|
|
// result := lazy.Retrying(policy, action, check)()
|
|
//
|
|
// # Algebraic Structures
|
|
//
|
|
// The package provides algebraic structures for combining lazy computations:
|
|
//
|
|
// **Semigroup**: Combine two lazy values using a semigroup operation
|
|
//
|
|
// import M "github.com/IBM/fp-go/v2/monoid"
|
|
//
|
|
// intAddSemigroup := lazy.ApplySemigroup(M.MonoidSum[int]())
|
|
// result := intAddSemigroup.Concat(lazy.Of(5), lazy.Of(10))() // 15
|
|
//
|
|
// **Monoid**: Combine lazy values with an identity element
|
|
//
|
|
// intAddMonoid := lazy.ApplicativeMonoid(M.MonoidSum[int]())
|
|
// empty := intAddMonoid.Empty()() // 0
|
|
// result := intAddMonoid.Concat(lazy.Of(5), lazy.Of(10))() // 15
|
|
//
|
|
// # Comparison
|
|
//
|
|
// Compare lazy computations by evaluating and comparing their results:
|
|
//
|
|
// import EQ "github.com/IBM/fp-go/v2/eq"
|
|
//
|
|
// eq := lazy.Eq(EQ.FromEquals[int]())
|
|
// result := eq.Equals(lazy.Of(42), lazy.Of(42)) // true
|
|
//
|
|
// # Key Functions
|
|
//
|
|
// **Creation**:
|
|
// - Of: Create a lazy computation from a value
|
|
// - FromLazy: Create a lazy computation from another lazy computation
|
|
// - FromImpure: Convert a side effect into a lazy computation
|
|
// - Defer: Create a lazy computation from a generator function
|
|
//
|
|
// **Transformation**:
|
|
// - Map: Transform the value inside a lazy computation
|
|
// - MapTo: Replace the value with a constant
|
|
// - Chain: Chain dependent computations
|
|
// - ChainFirst: Chain computations but keep the first result
|
|
// - Flatten: Flatten nested lazy computations
|
|
//
|
|
// **Combination**:
|
|
// - Ap: Apply a lazy function to a lazy value
|
|
// - ApFirst: Combine two computations, keeping the first result
|
|
// - ApSecond: Combine two computations, keeping the second result
|
|
//
|
|
// **Memoization**:
|
|
// - Memoize: Cache the result of a computation
|
|
//
|
|
// **Do-Notation**:
|
|
// - Do: Start a do-notation context
|
|
// - Bind: Bind a computation result to a context
|
|
// - Let: Attach a pure value to a context
|
|
// - LetTo: Attach a constant to a context
|
|
// - BindTo: Initialize a context from a value
|
|
// - ApS: Attach a value using applicative style
|
|
//
|
|
// **Lens-Based Operations**:
|
|
// - BindL: Bind using a lens
|
|
// - LetL: Let using a lens
|
|
// - LetToL: LetTo using a lens
|
|
// - ApSL: ApS using a lens
|
|
//
|
|
// **Collections**:
|
|
// - TraverseArray: Transform array elements into lazy computations
|
|
// - SequenceArray: Convert array of lazy computations to lazy array
|
|
// - TraverseRecord: Transform record values into lazy computations
|
|
// - SequenceRecord: Convert record of lazy computations to lazy record
|
|
//
|
|
// **Tuples**:
|
|
// - SequenceT1, SequenceT2, SequenceT3, SequenceT4: Combine lazy computations into tuples
|
|
//
|
|
// **Retry**:
|
|
// - Retrying: Retry a computation according to a policy
|
|
//
|
|
// **Algebraic**:
|
|
// - ApplySemigroup: Create a semigroup for lazy values
|
|
// - ApplicativeMonoid: Create a monoid for lazy values
|
|
// - Eq: Create an equality predicate for lazy values
|
|
//
|
|
// # Relationship to IO
|
|
//
|
|
// The lazy package is built on top of the io package and shares the same underlying
|
|
// implementation. The key difference is conceptual:
|
|
// - lazy.Lazy[A] represents a pure, synchronous computation without side effects
|
|
// - io.IO[A] represents a computation that may have side effects
|
|
//
|
|
// In practice, they are the same type, but the lazy package provides a more focused
|
|
// API for pure computations.
|
|
package lazy
|
|
|
|
// Made with Bob
|