mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-23 22:14:53 +02:00
fix: better tests for Lazy
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
@@ -21,10 +21,74 @@ import (
|
|||||||
S "github.com/IBM/fp-go/v2/semigroup"
|
S "github.com/IBM/fp-go/v2/semigroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ApplySemigroup lifts a Semigroup[A] to a Semigroup[Lazy[A]].
|
||||||
|
// This allows you to combine lazy computations using the semigroup operation
|
||||||
|
// on their underlying values.
|
||||||
|
//
|
||||||
|
// The resulting semigroup's Concat operation will evaluate both lazy computations
|
||||||
|
// and combine their results using the original semigroup's operation.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - s: A semigroup for values of type A
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - A semigroup for lazy computations of type A
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// M "github.com/IBM/fp-go/v2/monoid"
|
||||||
|
// "github.com/IBM/fp-go/v2/lazy"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// // Create a semigroup for lazy integers using addition
|
||||||
|
// intAddSemigroup := lazy.ApplySemigroup(M.MonoidSum[int]())
|
||||||
|
//
|
||||||
|
// lazy1 := lazy.Of(5)
|
||||||
|
// lazy2 := lazy.Of(10)
|
||||||
|
//
|
||||||
|
// // Combine the lazy computations
|
||||||
|
// result := intAddSemigroup.Concat(lazy1, lazy2)() // 15
|
||||||
func ApplySemigroup[A any](s S.Semigroup[A]) S.Semigroup[Lazy[A]] {
|
func ApplySemigroup[A any](s S.Semigroup[A]) S.Semigroup[Lazy[A]] {
|
||||||
return IO.ApplySemigroup(s)
|
return IO.ApplySemigroup(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplicativeMonoid lifts a Monoid[A] to a Monoid[Lazy[A]].
|
||||||
|
// This allows you to combine lazy computations using the monoid operation
|
||||||
|
// on their underlying values, with an identity element.
|
||||||
|
//
|
||||||
|
// The resulting monoid's Concat operation will evaluate both lazy computations
|
||||||
|
// and combine their results using the original monoid's operation. The Empty
|
||||||
|
// operation returns a lazy computation that produces the monoid's identity element.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// - m: A monoid for values of type A
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// - A monoid for lazy computations of type A
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// M "github.com/IBM/fp-go/v2/monoid"
|
||||||
|
// "github.com/IBM/fp-go/v2/lazy"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// // Create a monoid for lazy integers using addition
|
||||||
|
// intAddMonoid := lazy.ApplicativeMonoid(M.MonoidSum[int]())
|
||||||
|
//
|
||||||
|
// // Get the identity element (0 wrapped in lazy)
|
||||||
|
// empty := intAddMonoid.Empty()() // 0
|
||||||
|
//
|
||||||
|
// lazy1 := lazy.Of(5)
|
||||||
|
// lazy2 := lazy.Of(10)
|
||||||
|
//
|
||||||
|
// // Combine the lazy computations
|
||||||
|
// result := intAddMonoid.Concat(lazy1, lazy2)() // 15
|
||||||
|
//
|
||||||
|
// // Identity laws hold:
|
||||||
|
// // Concat(Empty(), x) == x
|
||||||
|
// // Concat(x, Empty()) == x
|
||||||
func ApplicativeMonoid[A any](m M.Monoid[A]) M.Monoid[Lazy[A]] {
|
func ApplicativeMonoid[A any](m M.Monoid[A]) M.Monoid[Lazy[A]] {
|
||||||
return IO.ApplicativeMonoid(m)
|
return IO.ApplicativeMonoid(m)
|
||||||
}
|
}
|
||||||
|
|||||||
269
v2/lazy/doc.go
Normal file
269
v2/lazy/doc.go
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
// 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
|
||||||
101
v2/lazy/lazy.go
101
v2/lazy/lazy.go
@@ -21,10 +21,28 @@ import (
|
|||||||
"github.com/IBM/fp-go/v2/io"
|
"github.com/IBM/fp-go/v2/io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Of creates a lazy computation that returns the given value.
|
||||||
|
// This is the most basic way to lift a value into the Lazy context.
|
||||||
|
//
|
||||||
|
// The computation is pure and will always return the same value when evaluated.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// computation := lazy.Of(42)
|
||||||
|
// result := computation() // 42
|
||||||
func Of[A any](a A) Lazy[A] {
|
func Of[A any](a A) Lazy[A] {
|
||||||
return io.Of(a)
|
return io.Of(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FromLazy creates a lazy computation from another lazy computation.
|
||||||
|
// This is an identity function that can be useful for type conversions or
|
||||||
|
// making the intent explicit in code.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// original := func() int { return 42 }
|
||||||
|
// wrapped := lazy.FromLazy(original)
|
||||||
|
// result := wrapped() // 42
|
||||||
func FromLazy[A any](a Lazy[A]) Lazy[A] {
|
func FromLazy[A any](a Lazy[A]) Lazy[A] {
|
||||||
return io.FromIO(a)
|
return io.FromIO(a)
|
||||||
}
|
}
|
||||||
@@ -34,22 +52,73 @@ func FromImpure(f func()) Lazy[any] {
|
|||||||
return io.FromImpure(f)
|
return io.FromImpure(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MonadOf creates a lazy computation that returns the given value.
|
||||||
|
// This is an alias for Of, provided for consistency with monadic naming conventions.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// computation := lazy.MonadOf(42)
|
||||||
|
// result := computation() // 42
|
||||||
func MonadOf[A any](a A) Lazy[A] {
|
func MonadOf[A any](a A) Lazy[A] {
|
||||||
return io.MonadOf(a)
|
return io.MonadOf(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MonadMap transforms the value inside a lazy computation using the provided function.
|
||||||
|
// The transformation is not applied until the lazy computation is evaluated.
|
||||||
|
//
|
||||||
|
// This is the monadic version of Map, taking the lazy computation as the first parameter.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// computation := lazy.Of(5)
|
||||||
|
// doubled := lazy.MonadMap(computation, func(x int) int { return x * 2 })
|
||||||
|
// result := doubled() // 10
|
||||||
func MonadMap[A, B any](fa Lazy[A], f func(A) B) Lazy[B] {
|
func MonadMap[A, B any](fa Lazy[A], f func(A) B) Lazy[B] {
|
||||||
return io.MonadMap(fa, f)
|
return io.MonadMap(fa, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Map transforms the value inside a lazy computation using the provided function.
|
||||||
|
// Returns a function that can be applied to a lazy computation.
|
||||||
|
//
|
||||||
|
// This is the curried version of MonadMap, useful for function composition.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// double := lazy.Map(func(x int) int { return x * 2 })
|
||||||
|
// computation := lazy.Of(5)
|
||||||
|
// result := double(computation)() // 10
|
||||||
|
//
|
||||||
|
// // Or with pipe:
|
||||||
|
// result := F.Pipe1(lazy.Of(5), double)() // 10
|
||||||
func Map[A, B any](f func(A) B) func(fa Lazy[A]) Lazy[B] {
|
func Map[A, B any](f func(A) B) func(fa Lazy[A]) Lazy[B] {
|
||||||
return io.Map(f)
|
return io.Map(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MonadMapTo replaces the value inside a lazy computation with a constant value.
|
||||||
|
// The original computation is still evaluated, but its result is discarded.
|
||||||
|
//
|
||||||
|
// This is useful when you want to sequence computations but only care about
|
||||||
|
// the side effects (though Lazy should represent pure computations).
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// computation := lazy.Of("ignored")
|
||||||
|
// replaced := lazy.MonadMapTo(computation, 42)
|
||||||
|
// result := replaced() // 42
|
||||||
func MonadMapTo[A, B any](fa Lazy[A], b B) Lazy[B] {
|
func MonadMapTo[A, B any](fa Lazy[A], b B) Lazy[B] {
|
||||||
return io.MonadMapTo(fa, b)
|
return io.MonadMapTo(fa, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MapTo replaces the value inside a lazy computation with a constant value.
|
||||||
|
// Returns a function that can be applied to a lazy computation.
|
||||||
|
//
|
||||||
|
// This is the curried version of MonadMapTo.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// replaceWith42 := lazy.MapTo[string](42)
|
||||||
|
// computation := lazy.Of("ignored")
|
||||||
|
// result := replaceWith42(computation)() // 42
|
||||||
func MapTo[A, B any](b B) Kleisli[Lazy[A], B] {
|
func MapTo[A, B any](b B) Kleisli[Lazy[A], B] {
|
||||||
return io.MapTo[A](b)
|
return io.MapTo[A](b)
|
||||||
}
|
}
|
||||||
@@ -64,10 +133,32 @@ func Chain[A, B any](f Kleisli[A, B]) Kleisli[Lazy[A], B] {
|
|||||||
return io.Chain(f)
|
return io.Chain(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MonadAp applies a lazy function to a lazy value.
|
||||||
|
// Both the function and the value are evaluated when the result is evaluated.
|
||||||
|
//
|
||||||
|
// This is the applicative functor operation, allowing you to apply functions
|
||||||
|
// that are themselves wrapped in a lazy context.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// lazyFunc := lazy.Of(func(x int) int { return x * 2 })
|
||||||
|
// lazyValue := lazy.Of(5)
|
||||||
|
// result := lazy.MonadAp(lazyFunc, lazyValue)() // 10
|
||||||
func MonadAp[B, A any](mab Lazy[func(A) B], ma Lazy[A]) Lazy[B] {
|
func MonadAp[B, A any](mab Lazy[func(A) B], ma Lazy[A]) Lazy[B] {
|
||||||
return io.MonadApSeq(mab, ma)
|
return io.MonadApSeq(mab, ma)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ap applies a lazy function to a lazy value.
|
||||||
|
// Returns a function that takes a lazy function and returns a lazy result.
|
||||||
|
//
|
||||||
|
// This is the curried version of MonadAp, useful for function composition.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// lazyValue := lazy.Of(5)
|
||||||
|
// applyTo5 := lazy.Ap[int](lazyValue)
|
||||||
|
// lazyFunc := lazy.Of(func(x int) int { return x * 2 })
|
||||||
|
// result := applyTo5(lazyFunc)() // 10
|
||||||
func Ap[B, A any](ma Lazy[A]) func(Lazy[func(A) B]) Lazy[B] {
|
func Ap[B, A any](ma Lazy[A]) func(Lazy[func(A) B]) Lazy[B] {
|
||||||
return io.ApSeq[B](ma)
|
return io.ApSeq[B](ma)
|
||||||
}
|
}
|
||||||
@@ -123,7 +214,15 @@ func ChainTo[A, B any](fb Lazy[B]) Kleisli[Lazy[A], B] {
|
|||||||
return io.ChainTo[A](fb)
|
return io.ChainTo[A](fb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now returns the current timestamp
|
// Now is a lazy computation that returns the current timestamp when evaluated.
|
||||||
|
// Each evaluation will return the current time at the moment of evaluation.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// time1 := lazy.Now()
|
||||||
|
// // ... some time passes ...
|
||||||
|
// time2 := lazy.Now()
|
||||||
|
// // time1 and time2 will be different
|
||||||
var Now Lazy[time.Time] = io.Now
|
var Now Lazy[time.Time] = io.Now
|
||||||
|
|
||||||
// Defer creates an IO by creating a brand new IO via a generator function, each time
|
// Defer creates an IO by creating a brand new IO via a generator function, each time
|
||||||
|
|||||||
505
v2/lazy/lazy_extended_test.go
Normal file
505
v2/lazy/lazy_extended_test.go
Normal file
@@ -0,0 +1,505 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
EQ "github.com/IBM/fp-go/v2/eq"
|
||||||
|
F "github.com/IBM/fp-go/v2/function"
|
||||||
|
"github.com/IBM/fp-go/v2/internal/utils"
|
||||||
|
M "github.com/IBM/fp-go/v2/monoid"
|
||||||
|
L "github.com/IBM/fp-go/v2/optics/lens"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOf(t *testing.T) {
|
||||||
|
result := Of(42)
|
||||||
|
assert.Equal(t, 42, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromLazy(t *testing.T) {
|
||||||
|
original := func() int { return 42 }
|
||||||
|
wrapped := FromLazy(original)
|
||||||
|
assert.Equal(t, 42, wrapped())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromImpure(t *testing.T) {
|
||||||
|
counter := 0
|
||||||
|
impure := func() {
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
lazy := FromImpure(impure)
|
||||||
|
lazy()
|
||||||
|
assert.Equal(t, 1, counter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMonadOf(t *testing.T) {
|
||||||
|
result := MonadOf(42)
|
||||||
|
assert.Equal(t, 42, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMonadMap(t *testing.T) {
|
||||||
|
result := MonadMap(Of(5), func(x int) int { return x * 2 })
|
||||||
|
assert.Equal(t, 10, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMonadMapTo(t *testing.T) {
|
||||||
|
result := MonadMapTo(Of("ignored"), 42)
|
||||||
|
assert.Equal(t, 42, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapTo(t *testing.T) {
|
||||||
|
mapper := MapTo[string](42)
|
||||||
|
result := mapper(Of("ignored"))
|
||||||
|
assert.Equal(t, 42, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMonadChain(t *testing.T) {
|
||||||
|
result := MonadChain(Of(5), func(x int) Lazy[int] {
|
||||||
|
return Of(x * 2)
|
||||||
|
})
|
||||||
|
assert.Equal(t, 10, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMonadChainFirst(t *testing.T) {
|
||||||
|
result := MonadChainFirst(Of(5), func(x int) Lazy[string] {
|
||||||
|
return Of("ignored")
|
||||||
|
})
|
||||||
|
assert.Equal(t, 5, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainFirst(t *testing.T) {
|
||||||
|
chainer := ChainFirst(func(x int) Lazy[string] {
|
||||||
|
return Of("ignored")
|
||||||
|
})
|
||||||
|
result := chainer(Of(5))
|
||||||
|
assert.Equal(t, 5, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMonadChainTo(t *testing.T) {
|
||||||
|
result := MonadChainTo(Of(5), Of(10))
|
||||||
|
assert.Equal(t, 10, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainTo(t *testing.T) {
|
||||||
|
chainer := ChainTo[int](Of(10))
|
||||||
|
result := chainer(Of(5))
|
||||||
|
assert.Equal(t, 10, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMonadAp(t *testing.T) {
|
||||||
|
lazyFunc := Of(func(x int) int { return x * 2 })
|
||||||
|
lazyValue := Of(5)
|
||||||
|
result := MonadAp(lazyFunc, lazyValue)
|
||||||
|
assert.Equal(t, 10, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMonadApFirst(t *testing.T) {
|
||||||
|
result := MonadApFirst(Of(5), Of(10))
|
||||||
|
assert.Equal(t, 5, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMonadApSecond(t *testing.T) {
|
||||||
|
result := MonadApSecond(Of(5), Of(10))
|
||||||
|
assert.Equal(t, 10, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNow(t *testing.T) {
|
||||||
|
before := time.Now()
|
||||||
|
result := Now()
|
||||||
|
after := time.Now()
|
||||||
|
|
||||||
|
assert.True(t, result.After(before) || result.Equal(before))
|
||||||
|
assert.True(t, result.Before(after) || result.Equal(after))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefer(t *testing.T) {
|
||||||
|
counter := 0
|
||||||
|
deferred := Defer(func() Lazy[int] {
|
||||||
|
counter++
|
||||||
|
return Of(counter)
|
||||||
|
})
|
||||||
|
|
||||||
|
// First execution
|
||||||
|
result1 := deferred()
|
||||||
|
assert.Equal(t, 1, result1)
|
||||||
|
|
||||||
|
// Second execution should generate a new computation
|
||||||
|
result2 := deferred()
|
||||||
|
assert.Equal(t, 2, result2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDo(t *testing.T) {
|
||||||
|
type State struct {
|
||||||
|
Value int
|
||||||
|
}
|
||||||
|
result := Do(State{Value: 42})
|
||||||
|
assert.Equal(t, State{Value: 42}, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLet(t *testing.T) {
|
||||||
|
type State struct {
|
||||||
|
Value int
|
||||||
|
}
|
||||||
|
|
||||||
|
result := F.Pipe2(
|
||||||
|
Do(State{}),
|
||||||
|
Let(
|
||||||
|
func(v int) func(State) State {
|
||||||
|
return func(s State) State { s.Value = v; return s }
|
||||||
|
},
|
||||||
|
func(s State) int { return 42 },
|
||||||
|
),
|
||||||
|
Map(func(s State) int { return s.Value }),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, 42, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLetTo(t *testing.T) {
|
||||||
|
type State struct {
|
||||||
|
Value int
|
||||||
|
}
|
||||||
|
|
||||||
|
result := F.Pipe2(
|
||||||
|
Do(State{}),
|
||||||
|
LetTo(
|
||||||
|
func(v int) func(State) State {
|
||||||
|
return func(s State) State { s.Value = v; return s }
|
||||||
|
},
|
||||||
|
42,
|
||||||
|
),
|
||||||
|
Map(func(s State) int { return s.Value }),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, 42, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindTo(t *testing.T) {
|
||||||
|
type State struct {
|
||||||
|
Value int
|
||||||
|
}
|
||||||
|
|
||||||
|
result := F.Pipe2(
|
||||||
|
Of(42),
|
||||||
|
BindTo(func(v int) State { return State{Value: v} }),
|
||||||
|
Map(func(s State) int { return s.Value }),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, 42, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBindL(t *testing.T) {
|
||||||
|
type Config struct {
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
type State struct {
|
||||||
|
Config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a lens manually
|
||||||
|
configLens := L.MakeLens(
|
||||||
|
func(s State) Config { return s.Config },
|
||||||
|
func(s State, cfg Config) State { s.Config = cfg; return s },
|
||||||
|
)
|
||||||
|
|
||||||
|
result := F.Pipe2(
|
||||||
|
Do(State{Config: Config{Port: 8080}}),
|
||||||
|
BindL(configLens, func(cfg Config) Lazy[Config] {
|
||||||
|
return Of(Config{Port: cfg.Port + 1})
|
||||||
|
}),
|
||||||
|
Map(func(s State) int { return s.Config.Port }),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, 8081, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLetL(t *testing.T) {
|
||||||
|
type Config struct {
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
type State struct {
|
||||||
|
Config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a lens manually
|
||||||
|
configLens := L.MakeLens(
|
||||||
|
func(s State) Config { return s.Config },
|
||||||
|
func(s State, cfg Config) State { s.Config = cfg; return s },
|
||||||
|
)
|
||||||
|
|
||||||
|
result := F.Pipe2(
|
||||||
|
Do(State{Config: Config{Port: 8080}}),
|
||||||
|
LetL(configLens, func(cfg Config) Config {
|
||||||
|
return Config{Port: cfg.Port + 1}
|
||||||
|
}),
|
||||||
|
Map(func(s State) int { return s.Config.Port }),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, 8081, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLetToL(t *testing.T) {
|
||||||
|
type Config struct {
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
type State struct {
|
||||||
|
Config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a lens manually
|
||||||
|
configLens := L.MakeLens(
|
||||||
|
func(s State) Config { return s.Config },
|
||||||
|
func(s State, cfg Config) State { s.Config = cfg; return s },
|
||||||
|
)
|
||||||
|
|
||||||
|
result := F.Pipe2(
|
||||||
|
Do(State{}),
|
||||||
|
LetToL(configLens, Config{Port: 8080}),
|
||||||
|
Map(func(s State) int { return s.Config.Port }),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, 8080, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApSL(t *testing.T) {
|
||||||
|
type Config struct {
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
type State struct {
|
||||||
|
Config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a lens manually
|
||||||
|
configLens := L.MakeLens(
|
||||||
|
func(s State) Config { return s.Config },
|
||||||
|
func(s State, cfg Config) State { s.Config = cfg; return s },
|
||||||
|
)
|
||||||
|
|
||||||
|
result := F.Pipe2(
|
||||||
|
Do(State{}),
|
||||||
|
ApSL(configLens, Of(Config{Port: 8080})),
|
||||||
|
Map(func(s State) int { return s.Config.Port }),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, 8080, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSequenceT1(t *testing.T) {
|
||||||
|
result := SequenceT1(Of(42))
|
||||||
|
tuple := result()
|
||||||
|
assert.Equal(t, 42, tuple.F1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSequenceT2(t *testing.T) {
|
||||||
|
result := SequenceT2(Of(42), Of("hello"))
|
||||||
|
tuple := result()
|
||||||
|
assert.Equal(t, 42, tuple.F1)
|
||||||
|
assert.Equal(t, "hello", tuple.F2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSequenceT3(t *testing.T) {
|
||||||
|
result := SequenceT3(Of(42), Of("hello"), Of(true))
|
||||||
|
tuple := result()
|
||||||
|
assert.Equal(t, 42, tuple.F1)
|
||||||
|
assert.Equal(t, "hello", tuple.F2)
|
||||||
|
assert.Equal(t, true, tuple.F3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSequenceT4(t *testing.T) {
|
||||||
|
result := SequenceT4(Of(42), Of("hello"), Of(true), Of(3.14))
|
||||||
|
tuple := result()
|
||||||
|
assert.Equal(t, 42, tuple.F1)
|
||||||
|
assert.Equal(t, "hello", tuple.F2)
|
||||||
|
assert.Equal(t, true, tuple.F3)
|
||||||
|
assert.Equal(t, 3.14, tuple.F4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraverseArray(t *testing.T) {
|
||||||
|
numbers := []int{1, 2, 3}
|
||||||
|
result := F.Pipe1(
|
||||||
|
numbers,
|
||||||
|
TraverseArray(func(x int) Lazy[int] {
|
||||||
|
return Of(x * 2)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
assert.Equal(t, []int{2, 4, 6}, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraverseArrayWithIndex(t *testing.T) {
|
||||||
|
numbers := []int{10, 20, 30}
|
||||||
|
result := F.Pipe1(
|
||||||
|
numbers,
|
||||||
|
TraverseArrayWithIndex(func(i int, x int) Lazy[int] {
|
||||||
|
return Of(x + i)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
assert.Equal(t, []int{10, 21, 32}, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSequenceArray(t *testing.T) {
|
||||||
|
lazies := []Lazy[int]{Of(1), Of(2), Of(3)}
|
||||||
|
result := SequenceArray(lazies)
|
||||||
|
assert.Equal(t, []int{1, 2, 3}, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMonadTraverseArray(t *testing.T) {
|
||||||
|
numbers := []int{1, 2, 3}
|
||||||
|
result := MonadTraverseArray(numbers, func(x int) Lazy[int] {
|
||||||
|
return Of(x * 2)
|
||||||
|
})
|
||||||
|
assert.Equal(t, []int{2, 4, 6}, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraverseRecord(t *testing.T) {
|
||||||
|
record := map[string]int{"a": 1, "b": 2}
|
||||||
|
result := F.Pipe1(
|
||||||
|
record,
|
||||||
|
TraverseRecord[string](func(x int) Lazy[int] {
|
||||||
|
return Of(x * 2)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
resultMap := result()
|
||||||
|
assert.Equal(t, 2, resultMap["a"])
|
||||||
|
assert.Equal(t, 4, resultMap["b"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraverseRecordWithIndex(t *testing.T) {
|
||||||
|
record := map[string]int{"a": 10, "b": 20}
|
||||||
|
result := F.Pipe1(
|
||||||
|
record,
|
||||||
|
TraverseRecordWithIndex(func(k string, x int) Lazy[int] {
|
||||||
|
if k == "a" {
|
||||||
|
return Of(x + 1)
|
||||||
|
}
|
||||||
|
return Of(x + 2)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
resultMap := result()
|
||||||
|
assert.Equal(t, 11, resultMap["a"])
|
||||||
|
assert.Equal(t, 22, resultMap["b"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSequenceRecord(t *testing.T) {
|
||||||
|
record := map[string]Lazy[int]{
|
||||||
|
"a": Of(1),
|
||||||
|
"b": Of(2),
|
||||||
|
}
|
||||||
|
result := SequenceRecord(record)
|
||||||
|
resultMap := result()
|
||||||
|
assert.Equal(t, 1, resultMap["a"])
|
||||||
|
assert.Equal(t, 2, resultMap["b"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMonadTraverseRecord(t *testing.T) {
|
||||||
|
record := map[string]int{"a": 1, "b": 2}
|
||||||
|
result := MonadTraverseRecord(record, func(x int) Lazy[int] {
|
||||||
|
return Of(x * 2)
|
||||||
|
})
|
||||||
|
resultMap := result()
|
||||||
|
assert.Equal(t, 2, resultMap["a"])
|
||||||
|
assert.Equal(t, 4, resultMap["b"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplySemigroup(t *testing.T) {
|
||||||
|
sg := ApplySemigroup(M.MakeMonoid(
|
||||||
|
func(a, b int) int { return a + b },
|
||||||
|
0,
|
||||||
|
))
|
||||||
|
|
||||||
|
result := sg.Concat(Of(5), Of(10))
|
||||||
|
assert.Equal(t, 15, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplicativeMonoid(t *testing.T) {
|
||||||
|
mon := ApplicativeMonoid(M.MakeMonoid(
|
||||||
|
func(a, b int) int { return a + b },
|
||||||
|
0,
|
||||||
|
))
|
||||||
|
|
||||||
|
// Test Empty
|
||||||
|
empty := mon.Empty()
|
||||||
|
assert.Equal(t, 0, empty())
|
||||||
|
|
||||||
|
// Test Concat
|
||||||
|
result := mon.Concat(Of(5), Of(10))
|
||||||
|
assert.Equal(t, 15, result())
|
||||||
|
|
||||||
|
// Test identity laws
|
||||||
|
left := mon.Concat(mon.Empty(), Of(5))
|
||||||
|
assert.Equal(t, 5, left())
|
||||||
|
|
||||||
|
right := mon.Concat(Of(5), mon.Empty())
|
||||||
|
assert.Equal(t, 5, right())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEq(t *testing.T) {
|
||||||
|
eq := Eq(EQ.FromEquals(func(a, b int) bool { return a == b }))
|
||||||
|
|
||||||
|
assert.True(t, eq.Equals(Of(42), Of(42)))
|
||||||
|
assert.False(t, eq.Equals(Of(42), Of(43)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComplexDoNotation(t *testing.T) {
|
||||||
|
// Test a more complex do-notation scenario
|
||||||
|
result := F.Pipe3(
|
||||||
|
Do(utils.Empty),
|
||||||
|
Bind(utils.SetLastName, func(s utils.Initial) Lazy[string] {
|
||||||
|
return Of("Doe")
|
||||||
|
}),
|
||||||
|
Bind(utils.SetGivenName, func(s utils.WithLastName) Lazy[string] {
|
||||||
|
return Of("John")
|
||||||
|
}),
|
||||||
|
Map(utils.GetFullName),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, "John Doe", result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainComposition(t *testing.T) {
|
||||||
|
// Test chaining multiple operations
|
||||||
|
double := func(x int) Lazy[int] {
|
||||||
|
return Of(x * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
addTen := func(x int) Lazy[int] {
|
||||||
|
return Of(x + 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := F.Pipe2(
|
||||||
|
Of(5),
|
||||||
|
Chain(double),
|
||||||
|
Chain(addTen),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, 20, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapComposition(t *testing.T) {
|
||||||
|
// Test mapping multiple transformations
|
||||||
|
result := F.Pipe3(
|
||||||
|
Of(5),
|
||||||
|
Map(func(x int) int { return x * 2 }),
|
||||||
|
Map(func(x int) int { return x + 10 }),
|
||||||
|
Map(func(x int) int { return x }),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Equal(t, 20, result())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Made with Bob
|
||||||
@@ -22,18 +22,56 @@ import (
|
|||||||
|
|
||||||
// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple
|
// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple
|
||||||
|
|
||||||
|
// SequenceT1 combines a single lazy computation into a lazy tuple.
|
||||||
|
// This is mainly useful for consistency with the other SequenceT functions.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// lazy1 := lazy.Of(42)
|
||||||
|
// result := lazy.SequenceT1(lazy1)()
|
||||||
|
// // result is tuple.Tuple1[int]{F1: 42}
|
||||||
func SequenceT1[A any](a Lazy[A]) Lazy[tuple.Tuple1[A]] {
|
func SequenceT1[A any](a Lazy[A]) Lazy[tuple.Tuple1[A]] {
|
||||||
return io.SequenceT1(a)
|
return io.SequenceT1(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SequenceT2 combines two lazy computations into a lazy tuple of two elements.
|
||||||
|
// Both computations are evaluated when the result is evaluated.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// lazy1 := lazy.Of(42)
|
||||||
|
// lazy2 := lazy.Of("hello")
|
||||||
|
// result := lazy.SequenceT2(lazy1, lazy2)()
|
||||||
|
// // result is tuple.Tuple2[int, string]{F1: 42, F2: "hello"}
|
||||||
func SequenceT2[A, B any](a Lazy[A], b Lazy[B]) Lazy[tuple.Tuple2[A, B]] {
|
func SequenceT2[A, B any](a Lazy[A], b Lazy[B]) Lazy[tuple.Tuple2[A, B]] {
|
||||||
return io.SequenceT2(a, b)
|
return io.SequenceT2(a, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SequenceT3 combines three lazy computations into a lazy tuple of three elements.
|
||||||
|
// All computations are evaluated when the result is evaluated.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// lazy1 := lazy.Of(42)
|
||||||
|
// lazy2 := lazy.Of("hello")
|
||||||
|
// lazy3 := lazy.Of(true)
|
||||||
|
// result := lazy.SequenceT3(lazy1, lazy2, lazy3)()
|
||||||
|
// // result is tuple.Tuple3[int, string, bool]{F1: 42, F2: "hello", F3: true}
|
||||||
func SequenceT3[A, B, C any](a Lazy[A], b Lazy[B], c Lazy[C]) Lazy[tuple.Tuple3[A, B, C]] {
|
func SequenceT3[A, B, C any](a Lazy[A], b Lazy[B], c Lazy[C]) Lazy[tuple.Tuple3[A, B, C]] {
|
||||||
return io.SequenceT3(a, b, c)
|
return io.SequenceT3(a, b, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SequenceT4 combines four lazy computations into a lazy tuple of four elements.
|
||||||
|
// All computations are evaluated when the result is evaluated.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// lazy1 := lazy.Of(42)
|
||||||
|
// lazy2 := lazy.Of("hello")
|
||||||
|
// lazy3 := lazy.Of(true)
|
||||||
|
// lazy4 := lazy.Of(3.14)
|
||||||
|
// result := lazy.SequenceT4(lazy1, lazy2, lazy3, lazy4)()
|
||||||
|
// // result is tuple.Tuple4[int, string, bool, float64]{F1: 42, F2: "hello", F3: true, F4: 3.14}
|
||||||
func SequenceT4[A, B, C, D any](a Lazy[A], b Lazy[B], c Lazy[C], d Lazy[D]) Lazy[tuple.Tuple4[A, B, C, D]] {
|
func SequenceT4[A, B, C, D any](a Lazy[A], b Lazy[B], c Lazy[C], d Lazy[D]) Lazy[tuple.Tuple4[A, B, C, D]] {
|
||||||
return io.SequenceT4(a, b, c, d)
|
return io.SequenceT4(a, b, c, d)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,18 @@ package lazy
|
|||||||
|
|
||||||
import "github.com/IBM/fp-go/v2/io"
|
import "github.com/IBM/fp-go/v2/io"
|
||||||
|
|
||||||
|
// MonadTraverseArray applies a function returning a lazy computation to all elements
|
||||||
|
// in an array and transforms this into a lazy computation of that array.
|
||||||
|
//
|
||||||
|
// This is the monadic version of TraverseArray, taking the array as the first parameter.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// numbers := []int{1, 2, 3}
|
||||||
|
// result := lazy.MonadTraverseArray(numbers, func(x int) lazy.Lazy[int] {
|
||||||
|
// return lazy.Of(x * 2)
|
||||||
|
// })()
|
||||||
|
// // result is []int{2, 4, 6}
|
||||||
func MonadTraverseArray[A, B any](tas []A, f Kleisli[A, B]) Lazy[[]B] {
|
func MonadTraverseArray[A, B any](tas []A, f Kleisli[A, B]) Lazy[[]B] {
|
||||||
return io.MonadTraverseArray(tas, f)
|
return io.MonadTraverseArray(tas, f)
|
||||||
}
|
}
|
||||||
@@ -38,6 +50,18 @@ func SequenceArray[A any](tas []Lazy[A]) Lazy[[]A] {
|
|||||||
return io.SequenceArray(tas)
|
return io.SequenceArray(tas)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MonadTraverseRecord applies a function returning a lazy computation to all values
|
||||||
|
// in a record (map) and transforms this into a lazy computation of that record.
|
||||||
|
//
|
||||||
|
// This is the monadic version of TraverseRecord, taking the record as the first parameter.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// record := map[string]int{"a": 1, "b": 2}
|
||||||
|
// result := lazy.MonadTraverseRecord(record, func(x int) lazy.Lazy[int] {
|
||||||
|
// return lazy.Of(x * 2)
|
||||||
|
// })()
|
||||||
|
// // result is map[string]int{"a": 2, "b": 4}
|
||||||
func MonadTraverseRecord[K comparable, A, B any](tas map[K]A, f Kleisli[A, B]) Lazy[map[K]B] {
|
func MonadTraverseRecord[K comparable, A, B any](tas map[K]A, f Kleisli[A, B]) Lazy[map[K]B] {
|
||||||
return io.MonadTraverseRecord(tas, f)
|
return io.MonadTraverseRecord(tas, f)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,60 @@
|
|||||||
package lazy
|
package lazy
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// Lazy represents a synchronous computation without side effects
|
// Lazy represents a synchronous computation without side effects.
|
||||||
|
// It is a function that takes no arguments and returns a value of type A.
|
||||||
|
//
|
||||||
|
// Lazy computations are evaluated only when their result is needed (lazy evaluation).
|
||||||
|
// This allows for:
|
||||||
|
// - Deferring expensive computations until they're actually required
|
||||||
|
// - Creating infinite data structures
|
||||||
|
// - Implementing memoization patterns
|
||||||
|
// - Composing pure computations in a functional style
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// // Create a lazy computation
|
||||||
|
// computation := lazy.Of(42)
|
||||||
|
//
|
||||||
|
// // Transform it (not evaluated yet)
|
||||||
|
// doubled := lazy.Map(func(x int) int { return x * 2 })(computation)
|
||||||
|
//
|
||||||
|
// // Evaluate when needed
|
||||||
|
// result := doubled() // 84
|
||||||
|
//
|
||||||
|
// Note: Lazy is an alias for io.IO[A] but represents pure computations
|
||||||
|
// without side effects, whereas IO represents computations that may have side effects.
|
||||||
Lazy[A any] = func() A
|
Lazy[A any] = func() A
|
||||||
|
|
||||||
|
// Kleisli represents a function that takes a value of type A and returns
|
||||||
|
// a lazy computation producing a value of type B.
|
||||||
|
//
|
||||||
|
// Kleisli arrows are used for composing monadic computations. They allow
|
||||||
|
// you to chain operations where each step depends on the result of the previous step.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// // A Kleisli arrow that doubles a number lazily
|
||||||
|
// double := func(x int) lazy.Lazy[int] {
|
||||||
|
// return lazy.Of(x * 2)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Chain it with another operation
|
||||||
|
// result := lazy.Chain(double)(lazy.Of(5))() // 10
|
||||||
Kleisli[A, B any] = func(A) Lazy[B]
|
Kleisli[A, B any] = func(A) Lazy[B]
|
||||||
|
|
||||||
|
// Operator represents a function that takes a lazy computation of type A
|
||||||
|
// and returns a lazy computation of type B.
|
||||||
|
//
|
||||||
|
// Operators are used to transform lazy computations. They are essentially
|
||||||
|
// Kleisli arrows where the input is already wrapped in a Lazy context.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// // An operator that doubles the value in a lazy computation
|
||||||
|
// doubleOp := lazy.Map(func(x int) int { return x * 2 })
|
||||||
|
//
|
||||||
|
// // Apply it to a lazy computation
|
||||||
|
// result := doubleOp(lazy.Of(5))() // 10
|
||||||
Operator[A, B any] = Kleisli[Lazy[A], B]
|
Operator[A, B any] = Kleisli[Lazy[A], B]
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user