mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-23 22:14:53 +02:00
fix: better endo and lens
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
@@ -41,7 +41,7 @@ import (
|
|||||||
// curriedAdd := endomorphism.Curry2(add)
|
// curriedAdd := endomorphism.Curry2(add)
|
||||||
// addFive := curriedAdd(5) // Returns an endomorphism that adds 5
|
// addFive := curriedAdd(5) // Returns an endomorphism that adds 5
|
||||||
// result := addFive(10) // Returns: 15
|
// result := addFive(10) // Returns: 15
|
||||||
func Curry2[FCT ~func(T0, T1) T1, T0, T1 any](f FCT) Kleisli[T0, T1] {
|
func Curry2[FCT ~func(T0, T1) T1, T0, T1 any](f FCT) func(T0) Endomorphism[T1] {
|
||||||
return function.Curry2(f)
|
return function.Curry2(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +68,6 @@ func Curry2[FCT ~func(T0, T1) T1, T0, T1 any](f FCT) Kleisli[T0, T1] {
|
|||||||
// curriedCombine := endomorphism.Curry3(combine)
|
// curriedCombine := endomorphism.Curry3(combine)
|
||||||
// addTen := curriedCombine(5)(5) // Returns an endomorphism that adds 10
|
// addTen := curriedCombine(5)(5) // Returns an endomorphism that adds 10
|
||||||
// result := addTen(20) // Returns: 30
|
// result := addTen(20) // Returns: 30
|
||||||
func Curry3[FCT ~func(T0, T1, T2) T2, T0, T1, T2 any](f FCT) func(T0) Kleisli[T1, T2] {
|
func Curry3[FCT ~func(T0, T1, T2) T2, T0, T1, T2 any](f FCT) func(T0) func(T1) Endomorphism[T2] {
|
||||||
return function.Curry3(f)
|
return function.Curry3(f)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,47 +17,58 @@ package endomorphism
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/IBM/fp-go/v2/function"
|
"github.com/IBM/fp-go/v2/function"
|
||||||
"github.com/IBM/fp-go/v2/identity"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MonadAp applies an endomorphism to a value in a monadic context.
|
// MonadAp applies an endomorphism in a function to an endomorphism value.
|
||||||
//
|
//
|
||||||
// This function applies the endomorphism fab to the value fa, returning the result.
|
// For endomorphisms, Ap composes two endomorphisms using RIGHT-TO-LEFT composition.
|
||||||
// It's the monadic application operation for endomorphisms.
|
// 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:
|
// Parameters:
|
||||||
// - fab: An endomorphism to apply
|
// - fab: An endomorphism to apply (outer function)
|
||||||
// - fa: The value to apply the endomorphism to
|
// - fa: An endomorphism to apply first (inner function)
|
||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// - The result of applying fab to fa
|
// - A new endomorphism that applies fa, then fab
|
||||||
//
|
//
|
||||||
// Example:
|
// Example:
|
||||||
//
|
//
|
||||||
// double := func(x int) int { return x * 2 }
|
// double := func(x int) int { return x * 2 }
|
||||||
// result := endomorphism.MonadAp(double, 5) // Returns: 10
|
// increment := func(x int) int { return x + 1 }
|
||||||
func MonadAp[A any](fab Endomorphism[A], fa A) A {
|
// result := endomorphism.MonadAp(double, increment) // Composes: double ∘ increment
|
||||||
return identity.MonadAp(fab, fa)
|
// // result(5) = double(increment(5)) = double(6) = 12
|
||||||
|
func MonadAp[A any](fab Endomorphism[A], fa Endomorphism[A]) Endomorphism[A] {
|
||||||
|
return MonadCompose(fab, fa)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ap returns a function that applies a value to an endomorphism.
|
// Ap returns a function that applies an endomorphism to another endomorphism.
|
||||||
//
|
//
|
||||||
// This is the curried version of MonadAp. It takes a value and returns a function
|
// This is the curried version of MonadAp. It takes an endomorphism fa and returns
|
||||||
// that applies that value to any endomorphism.
|
// 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:
|
// Parameters:
|
||||||
// - fa: The value to be applied
|
// - fa: The first endomorphism to apply (inner function)
|
||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// - A function that takes an endomorphism and applies fa to it
|
// - A function that takes an endomorphism and composes it with fa (right-to-left)
|
||||||
//
|
//
|
||||||
// Example:
|
// Example:
|
||||||
//
|
//
|
||||||
// applyFive := endomorphism.Ap(5)
|
// increment := func(x int) int { return x + 1 }
|
||||||
|
// applyIncrement := endomorphism.Ap(increment)
|
||||||
// double := func(x int) int { return x * 2 }
|
// double := func(x int) int { return x * 2 }
|
||||||
// result := applyFive(double) // Returns: 10
|
// composed := applyIncrement(double) // double ∘ increment
|
||||||
func Ap[A any](fa A) func(Endomorphism[A]) A {
|
// // composed(5) = double(increment(5)) = double(6) = 12
|
||||||
return identity.Ap[A](fa)
|
func Ap[A any](fa Endomorphism[A]) Operator[A] {
|
||||||
|
return Compose(fa)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MonadCompose composes two endomorphisms, executing them from right to left.
|
// MonadCompose composes two endomorphisms, executing them from right to left.
|
||||||
@@ -94,6 +105,32 @@ func MonadCompose[A any](f, g Endomorphism[A]) Endomorphism[A] {
|
|||||||
return function.Flow2(g, f)
|
return function.Flow2(g, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 := func(x int) int { return x * 2 }
|
||||||
|
// increment := func(x int) int { return x + 1 }
|
||||||
|
// mapped := endomorphism.MonadMap(double, increment)
|
||||||
|
// // mapped(5) = double(increment(5)) = double(6) = 12
|
||||||
|
func MonadMap[A any](f, g Endomorphism[A]) Endomorphism[A] {
|
||||||
|
return MonadCompose(f, g)
|
||||||
|
}
|
||||||
|
|
||||||
// Compose returns a function that composes an endomorphism with another, executing right to left.
|
// 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
|
// This is the curried version of MonadCompose. It takes an endomorphism g and returns
|
||||||
@@ -126,24 +163,52 @@ func MonadCompose[A any](f, g Endomorphism[A]) Endomorphism[A] {
|
|||||||
// chainWithIncrement := endomorphism.Chain(increment)
|
// chainWithIncrement := endomorphism.Chain(increment)
|
||||||
// chained := chainWithIncrement(double)
|
// chained := chainWithIncrement(double)
|
||||||
// result2 := chained(5) // (5 * 2) + 1 = 11
|
// result2 := chained(5) // (5 * 2) + 1 = 11
|
||||||
func Compose[A any](g Endomorphism[A]) Endomorphism[Endomorphism[A]] {
|
func Compose[A any](g Endomorphism[A]) Operator[A] {
|
||||||
return function.Bind2nd(MonadCompose, g)
|
return function.Bind2nd(MonadCompose, g)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 := func(x int) int { return x * 2 }
|
||||||
|
// mapDouble := endomorphism.Map(double)
|
||||||
|
// increment := func(x int) int { return x + 1 }
|
||||||
|
// mapped := mapDouble(increment)
|
||||||
|
// // mapped(5) = double(increment(5)) = double(6) = 12
|
||||||
|
func Map[A any](f Endomorphism[A]) Operator[A] {
|
||||||
|
return Compose(f)
|
||||||
|
}
|
||||||
|
|
||||||
// MonadChain chains two endomorphisms together, executing them from left to right.
|
// MonadChain chains two endomorphisms together, executing them from left to right.
|
||||||
//
|
//
|
||||||
// This is the monadic bind operation for endomorphisms. It composes two endomorphisms
|
// This is the monadic bind operation for endomorphisms. For endomorphisms, bind is
|
||||||
// ma and f, returning a new endomorphism that applies ma first, then f.
|
// simply left-to-right function composition: ma is applied first, then f.
|
||||||
//
|
//
|
||||||
// IMPORTANT: The execution order is LEFT-TO-RIGHT:
|
// IMPORTANT: The execution order is LEFT-TO-RIGHT:
|
||||||
// - f is applied first to the input
|
// - ma is applied first to the input
|
||||||
// - g is applied to the result of ma
|
// - f is applied to the result of ma
|
||||||
//
|
//
|
||||||
// This is different from Compose which executes RIGHT-TO-LEFT.
|
// This is different from MonadCompose which executes RIGHT-TO-LEFT.
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// - f: The first endomorphism to apply
|
// - ma: The first endomorphism to apply
|
||||||
// - g: The second endomorphism to apply
|
// - f: The second endomorphism to apply
|
||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// - A new endomorphism that applies ma, then f
|
// - A new endomorphism that applies ma, then f
|
||||||
@@ -157,11 +222,58 @@ func Compose[A any](g Endomorphism[A]) Endomorphism[Endomorphism[A]] {
|
|||||||
// chained := endomorphism.MonadChain(double, increment)
|
// chained := endomorphism.MonadChain(double, increment)
|
||||||
// result := chained(5) // (5 * 2) + 1 = 11
|
// result := chained(5) // (5 * 2) + 1 = 11
|
||||||
//
|
//
|
||||||
// // Compare with Compose which executes RIGHT-TO-LEFT:
|
// // Compare with MonadCompose which executes RIGHT-TO-LEFT:
|
||||||
// composed := endomorphism.Compose(increment, double)
|
// composed := endomorphism.MonadCompose(increment, double)
|
||||||
// result2 := composed(5) // (5 * 2) + 1 = 11 (same result, different parameter order)
|
// result2 := composed(5) // (5 * 2) + 1 = 11 (same result, different parameter order)
|
||||||
func MonadChain[A any](f Endomorphism[A], g Endomorphism[A]) Endomorphism[A] {
|
func MonadChain[A any](ma Endomorphism[A], f Endomorphism[A]) Endomorphism[A] {
|
||||||
return function.Flow2(f, g)
|
return function.Flow2(ma, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 := func(x int) int { return x * 2 }
|
||||||
|
// log := func(x int) int { fmt.Println(x); return x }
|
||||||
|
// chained := endomorphism.MonadChainFirst(double, log)
|
||||||
|
// result := chained(5) // Prints 10, returns 10
|
||||||
|
func MonadChainFirst[A any](ma Endomorphism[A], f Endomorphism[A]) Endomorphism[A] {
|
||||||
|
return func(a A) A {
|
||||||
|
result := ma(a)
|
||||||
|
f(result) // Apply f for its effect
|
||||||
|
return result // But return ma's result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 := func(x int) int { return x * 2 }
|
||||||
|
// chained := chainLog(double)
|
||||||
|
// result := chained(5) // Prints 10, returns 10
|
||||||
|
func ChainFirst[A any](f Endomorphism[A]) Operator[A] {
|
||||||
|
return function.Bind2nd(MonadChainFirst, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chain returns a function that chains an endomorphism with another, executing left to right.
|
// Chain returns a function that chains an endomorphism with another, executing left to right.
|
||||||
@@ -189,6 +301,6 @@ func MonadChain[A any](f Endomorphism[A], g Endomorphism[A]) Endomorphism[A] {
|
|||||||
// // Chains double (first) with increment (second)
|
// // Chains double (first) with increment (second)
|
||||||
// chained := chainWithIncrement(double)
|
// chained := chainWithIncrement(double)
|
||||||
// result := chained(5) // (5 * 2) + 1 = 11
|
// result := chained(5) // (5 * 2) + 1 = 11
|
||||||
func Chain[A any](f Endomorphism[A]) Endomorphism[Endomorphism[A]] {
|
func Chain[A any](f Endomorphism[A]) Operator[A] {
|
||||||
return function.Bind2nd(MonadChain, f)
|
return function.Bind2nd(MonadChain, f)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,29 +76,43 @@ func TestCurry3(t *testing.T) {
|
|||||||
|
|
||||||
// TestMonadAp tests the MonadAp function
|
// TestMonadAp tests the MonadAp function
|
||||||
func TestMonadAp(t *testing.T) {
|
func TestMonadAp(t *testing.T) {
|
||||||
result := MonadAp(double, 5)
|
// MonadAp composes two endomorphisms (RIGHT-TO-LEFT)
|
||||||
assert.Equal(t, 10, result, "MonadAp should apply endomorphism to value")
|
// MonadAp(double, increment) means: increment first, then double
|
||||||
|
composed := MonadAp(double, increment)
|
||||||
|
result := composed(5)
|
||||||
|
assert.Equal(t, 12, result, "MonadAp should compose right-to-left: (5 + 1) * 2 = 12")
|
||||||
|
|
||||||
result2 := MonadAp(increment, 10)
|
// Test with different order
|
||||||
assert.Equal(t, 11, result2, "MonadAp should work with different endomorphisms")
|
composed2 := MonadAp(increment, double)
|
||||||
|
result2 := composed2(5)
|
||||||
|
assert.Equal(t, 11, result2, "MonadAp should compose right-to-left: (5 * 2) + 1 = 11")
|
||||||
|
|
||||||
result3 := MonadAp(square, 4)
|
// Test with square
|
||||||
assert.Equal(t, 16, result3, "MonadAp should work with square function")
|
composed3 := MonadAp(square, increment)
|
||||||
|
result3 := composed3(5)
|
||||||
|
assert.Equal(t, 36, result3, "MonadAp should compose right-to-left: (5 + 1) ^ 2 = 36")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestAp tests the Ap function
|
// TestAp tests the Ap function
|
||||||
func TestAp(t *testing.T) {
|
func TestAp(t *testing.T) {
|
||||||
applyFive := Ap(5)
|
// Ap is the curried version of MonadAp
|
||||||
|
// Ap(increment) returns a function that composes with increment (RIGHT-TO-LEFT)
|
||||||
|
applyIncrement := Ap(increment)
|
||||||
|
|
||||||
result := applyFive(double)
|
composed := applyIncrement(double)
|
||||||
assert.Equal(t, 10, result, "Ap should apply value to endomorphism")
|
result := composed(5)
|
||||||
|
assert.Equal(t, 12, result, "Ap should compose right-to-left: (5 + 1) * 2 = 12")
|
||||||
|
|
||||||
result2 := applyFive(increment)
|
// Test with different endomorphism
|
||||||
assert.Equal(t, 6, result2, "Ap should work with different endomorphisms")
|
composed2 := applyIncrement(square)
|
||||||
|
result2 := composed2(5)
|
||||||
|
assert.Equal(t, 36, result2, "Ap should compose right-to-left: (5 + 1) ^ 2 = 36")
|
||||||
|
|
||||||
applyTen := Ap(10)
|
// Test with different base endomorphism
|
||||||
result3 := applyTen(square)
|
applyDouble := Ap(double)
|
||||||
assert.Equal(t, 100, result3, "Ap should work with different values")
|
composed3 := applyDouble(increment)
|
||||||
|
result3 := composed3(5)
|
||||||
|
assert.Equal(t, 11, result3, "Ap should compose right-to-left: (5 * 2) + 1 = 11")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestMonadCompose tests the MonadCompose function
|
// TestMonadCompose tests the MonadCompose function
|
||||||
@@ -409,30 +423,25 @@ func TestComplexCompositions(t *testing.T) {
|
|||||||
|
|
||||||
// TestOperatorType tests the Operator type
|
// TestOperatorType tests the Operator type
|
||||||
func TestOperatorType(t *testing.T) {
|
func TestOperatorType(t *testing.T) {
|
||||||
// Create an operator that lifts an int endomorphism to work on the length of strings
|
// Create an operator that transforms int endomorphisms
|
||||||
lengthOperator := func(f Endomorphism[int]) Endomorphism[string] {
|
// This operator takes an endomorphism and returns a new one that applies it twice
|
||||||
return func(s string) string {
|
applyTwice := func(f Endomorphism[int]) Endomorphism[int] {
|
||||||
newLen := f(len(s))
|
return func(x int) int {
|
||||||
if newLen > len(s) {
|
return f(f(x))
|
||||||
// Pad with spaces
|
|
||||||
for i := len(s); i < newLen; i++ {
|
|
||||||
s += " "
|
|
||||||
}
|
|
||||||
} else if newLen < len(s) {
|
|
||||||
// Truncate
|
|
||||||
s = s[:newLen]
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the operator
|
// Use the operator
|
||||||
var op Operator[int, string] = lengthOperator
|
var op Operator[int] = applyTwice
|
||||||
doubleLength := op(double)
|
doubleDouble := op(double)
|
||||||
|
|
||||||
result := doubleLength("hello") // len("hello") = 5, 5 * 2 = 10
|
result := doubleDouble(5) // double(double(5)) = double(10) = 20
|
||||||
assert.Equal(t, 10, len(result), "Operator should transform endomorphisms correctly")
|
assert.Equal(t, 20, result, "Operator should transform endomorphisms correctly")
|
||||||
assert.Equal(t, "hello ", result, "Operator should pad string correctly")
|
|
||||||
|
// Test with increment
|
||||||
|
incrementTwice := op(increment)
|
||||||
|
result2 := incrementTwice(5) // increment(increment(5)) = increment(6) = 7
|
||||||
|
assert.Equal(t, 7, result2, "Operator should work with different endomorphisms")
|
||||||
}
|
}
|
||||||
|
|
||||||
// BenchmarkCompose benchmarks the Compose function
|
// BenchmarkCompose benchmarks the Compose function
|
||||||
@@ -504,3 +513,211 @@ func BenchmarkChain(b *testing.B) {
|
|||||||
_ = chained(5)
|
_ = chained(5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestFunctorLaws tests that endomorphisms satisfy the functor laws
|
||||||
|
func TestFunctorLaws(t *testing.T) {
|
||||||
|
// Functor Law 1: Identity
|
||||||
|
// map(id) = id
|
||||||
|
t.Run("Identity", func(t *testing.T) {
|
||||||
|
id := Identity[int]()
|
||||||
|
endo := double
|
||||||
|
|
||||||
|
// map(id)(endo) should equal endo
|
||||||
|
mapped := MonadMap(id, endo)
|
||||||
|
testValue := 5
|
||||||
|
assert.Equal(t, endo(testValue), mapped(testValue), "map(id) should equal id")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Functor Law 2: Composition
|
||||||
|
// map(f . g) = map(f) . map(g)
|
||||||
|
t.Run("Composition", func(t *testing.T) {
|
||||||
|
f := double
|
||||||
|
g := increment
|
||||||
|
endo := square
|
||||||
|
|
||||||
|
// Left side: map(f . g)(endo)
|
||||||
|
composed := MonadCompose(f, g)
|
||||||
|
left := MonadMap(composed, endo)
|
||||||
|
|
||||||
|
// Right side: map(f)(map(g)(endo))
|
||||||
|
mappedG := MonadMap(g, endo)
|
||||||
|
right := MonadMap(f, mappedG)
|
||||||
|
|
||||||
|
testValue := 3
|
||||||
|
assert.Equal(t, left(testValue), right(testValue), "map(f . g) should equal map(f) . map(g)")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestApplicativeLaws tests that endomorphisms satisfy the applicative functor laws
|
||||||
|
func TestApplicativeLaws(t *testing.T) {
|
||||||
|
// Applicative Law 1: Identity
|
||||||
|
// ap(id, v) = v
|
||||||
|
t.Run("Identity", func(t *testing.T) {
|
||||||
|
id := Identity[int]()
|
||||||
|
v := double
|
||||||
|
|
||||||
|
applied := MonadAp(id, v)
|
||||||
|
testValue := 5
|
||||||
|
assert.Equal(t, v(testValue), applied(testValue), "ap(id, v) should equal v")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Applicative Law 2: Composition
|
||||||
|
// ap(ap(ap(compose, u), v), w) = ap(u, ap(v, w))
|
||||||
|
t.Run("Composition", func(t *testing.T) {
|
||||||
|
u := double
|
||||||
|
v := increment
|
||||||
|
w := square
|
||||||
|
|
||||||
|
// For endomorphisms, ap is just composition
|
||||||
|
// Left side: ap(ap(ap(compose, u), v), w) = compose(compose(u, v), w)
|
||||||
|
left := MonadCompose(MonadCompose(u, v), w)
|
||||||
|
|
||||||
|
// Right side: ap(u, ap(v, w)) = compose(u, compose(v, w))
|
||||||
|
right := MonadCompose(u, MonadCompose(v, w))
|
||||||
|
|
||||||
|
testValue := 3
|
||||||
|
assert.Equal(t, left(testValue), right(testValue), "Applicative composition law")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Applicative Law 3: Homomorphism
|
||||||
|
// ap(pure(f), pure(x)) = pure(f(x))
|
||||||
|
t.Run("Homomorphism", func(t *testing.T) {
|
||||||
|
// For endomorphisms, "pure" is just the identity function that returns a constant
|
||||||
|
// This law is trivially satisfied for endomorphisms
|
||||||
|
f := double
|
||||||
|
x := 5
|
||||||
|
|
||||||
|
// ap(f, id) applied to x should equal f(x)
|
||||||
|
id := Identity[int]()
|
||||||
|
applied := MonadAp(f, id)
|
||||||
|
assert.Equal(t, f(x), applied(x), "Homomorphism law")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMonadLaws tests that endomorphisms satisfy the monad laws
|
||||||
|
func TestMonadLaws(t *testing.T) {
|
||||||
|
// Monad Law 1: Left Identity
|
||||||
|
// chain(pure(a), f) = f(a)
|
||||||
|
t.Run("LeftIdentity", func(t *testing.T) {
|
||||||
|
// For endomorphisms, "pure" is the identity function
|
||||||
|
// chain(id, f) = f
|
||||||
|
id := Identity[int]()
|
||||||
|
f := double
|
||||||
|
|
||||||
|
chained := MonadChain(id, f)
|
||||||
|
testValue := 5
|
||||||
|
assert.Equal(t, f(testValue), chained(testValue), "chain(id, f) should equal f")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Monad Law 2: Right Identity
|
||||||
|
// chain(m, pure) = m
|
||||||
|
t.Run("RightIdentity", func(t *testing.T) {
|
||||||
|
m := double
|
||||||
|
id := Identity[int]()
|
||||||
|
|
||||||
|
chained := MonadChain(m, id)
|
||||||
|
testValue := 5
|
||||||
|
assert.Equal(t, m(testValue), chained(testValue), "chain(m, id) should equal m")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Monad Law 3: Associativity
|
||||||
|
// chain(chain(m, f), g) = chain(m, x => chain(f(x), g))
|
||||||
|
t.Run("Associativity", func(t *testing.T) {
|
||||||
|
m := square
|
||||||
|
f := double
|
||||||
|
g := increment
|
||||||
|
|
||||||
|
// Left side: chain(chain(m, f), g)
|
||||||
|
left := MonadChain(MonadChain(m, f), g)
|
||||||
|
|
||||||
|
// Right side: chain(m, chain(f, g))
|
||||||
|
// For simple endomorphisms (not Kleisli arrows), this simplifies to:
|
||||||
|
right := MonadChain(m, MonadChain(f, g))
|
||||||
|
|
||||||
|
testValue := 3
|
||||||
|
assert.Equal(t, left(testValue), right(testValue), "Monad associativity law")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMonadComposeVsMonadChain verifies the relationship between Compose and Chain
|
||||||
|
func TestMonadComposeVsMonadChain(t *testing.T) {
|
||||||
|
f := double
|
||||||
|
g := increment
|
||||||
|
|
||||||
|
// MonadCompose(f, g) should equal MonadChain(g, f)
|
||||||
|
// Because Compose is right-to-left and Chain is left-to-right
|
||||||
|
composed := MonadCompose(f, g)
|
||||||
|
chained := MonadChain(g, f)
|
||||||
|
|
||||||
|
testValue := 5
|
||||||
|
assert.Equal(t, composed(testValue), chained(testValue),
|
||||||
|
"MonadCompose(f, g) should equal MonadChain(g, f)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMapEqualsCompose verifies that Map is equivalent to Compose for endomorphisms
|
||||||
|
func TestMapEqualsCompose(t *testing.T) {
|
||||||
|
f := double
|
||||||
|
g := increment
|
||||||
|
|
||||||
|
// MonadMap(f, g) should equal MonadCompose(f, g)
|
||||||
|
mapped := MonadMap(f, g)
|
||||||
|
composed := MonadCompose(f, g)
|
||||||
|
|
||||||
|
testValue := 5
|
||||||
|
assert.Equal(t, composed(testValue), mapped(testValue),
|
||||||
|
"MonadMap should equal MonadCompose for endomorphisms")
|
||||||
|
|
||||||
|
// Curried versions
|
||||||
|
mapF := Map(f)
|
||||||
|
composeF := Compose(f)
|
||||||
|
|
||||||
|
mappedG := mapF(g)
|
||||||
|
composedG := composeF(g)
|
||||||
|
|
||||||
|
assert.Equal(t, composedG(testValue), mappedG(testValue),
|
||||||
|
"Map should equal Compose for endomorphisms (curried)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestApEqualsCompose verifies that Ap is equivalent to Compose for endomorphisms
|
||||||
|
func TestApEqualsCompose(t *testing.T) {
|
||||||
|
f := double
|
||||||
|
g := increment
|
||||||
|
|
||||||
|
// MonadAp(f, g) should equal MonadCompose(f, g)
|
||||||
|
applied := MonadAp(f, g)
|
||||||
|
composed := MonadCompose(f, g)
|
||||||
|
|
||||||
|
testValue := 5
|
||||||
|
assert.Equal(t, composed(testValue), applied(testValue),
|
||||||
|
"MonadAp should equal MonadCompose for endomorphisms")
|
||||||
|
|
||||||
|
// Curried versions
|
||||||
|
apG := Ap(g)
|
||||||
|
composeG := Compose(g)
|
||||||
|
|
||||||
|
appliedF := apG(f)
|
||||||
|
composedF := composeG(f)
|
||||||
|
|
||||||
|
assert.Equal(t, composedF(testValue), appliedF(testValue),
|
||||||
|
"Ap should equal Compose for endomorphisms (curried)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestChainFirst tests the ChainFirst operation
|
||||||
|
func TestChainFirst(t *testing.T) {
|
||||||
|
double := func(x int) int { return x * 2 }
|
||||||
|
|
||||||
|
// Track side effect
|
||||||
|
var sideEffect int
|
||||||
|
logEffect := func(x int) int {
|
||||||
|
sideEffect = x
|
||||||
|
return x + 100 // This result should be discarded
|
||||||
|
}
|
||||||
|
|
||||||
|
chained := MonadChainFirst(double, logEffect)
|
||||||
|
result := chained(5)
|
||||||
|
|
||||||
|
// Should return double's result (10), not logEffect's result
|
||||||
|
assert.Equal(t, 10, result, "ChainFirst should return first result")
|
||||||
|
// But side effect should have been executed with double's result
|
||||||
|
assert.Equal(t, 10, sideEffect, "ChainFirst should execute second function for effect")
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ type (
|
|||||||
// var g endomorphism.Endomorphism[int] = increment
|
// var g endomorphism.Endomorphism[int] = increment
|
||||||
Endomorphism[A any] = func(A) A
|
Endomorphism[A any] = func(A) A
|
||||||
|
|
||||||
Kleisli[A, B any] = func(A) Endomorphism[B]
|
Kleisli[A any] = func(A) Endomorphism[A]
|
||||||
|
|
||||||
// Operator represents a transformation from one endomorphism to another.
|
// Operator represents a transformation from one endomorphism to another.
|
||||||
//
|
//
|
||||||
@@ -54,5 +54,5 @@ type (
|
|||||||
// return strconv.Itoa(result)
|
// return strconv.Itoa(result)
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
Operator[A, B any] = Kleisli[Endomorphism[A], B]
|
Operator[A any] = Endomorphism[Endomorphism[A]]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -453,6 +453,8 @@ Core Lens Creation:
|
|||||||
- MakeLensCurried: Create a lens with curried setter
|
- MakeLensCurried: Create a lens with curried setter
|
||||||
- MakeLensRef: Create a lens for pointer-based structures
|
- MakeLensRef: Create a lens for pointer-based structures
|
||||||
- MakeLensRefCurried: Create a lens for pointers with curried setter
|
- MakeLensRefCurried: Create a lens for pointers with curried setter
|
||||||
|
- MakeLensWithEq: Create a lens with equality optimization for pointer structures
|
||||||
|
- MakeLensStrict: Create a lens with strict equality optimization for pointer structures
|
||||||
- Id: Create an identity lens
|
- Id: Create an identity lens
|
||||||
- IdRef: Create an identity lens for pointers
|
- IdRef: Create an identity lens for pointers
|
||||||
|
|
||||||
|
|||||||
640
v2/optics/lens/lens_laws_test.go
Normal file
640
v2/optics/lens/lens_laws_test.go
Normal file
@@ -0,0 +1,640 @@
|
|||||||
|
// 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 lens
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
EQ "github.com/IBM/fp-go/v2/eq"
|
||||||
|
F "github.com/IBM/fp-go/v2/function"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestModify tests the Modify function
|
||||||
|
func TestModify(t *testing.T) {
|
||||||
|
type Counter struct {
|
||||||
|
Value int
|
||||||
|
}
|
||||||
|
|
||||||
|
valueLens := MakeLens(
|
||||||
|
func(c Counter) int { return c.Value },
|
||||||
|
func(c Counter, v int) Counter {
|
||||||
|
c.Value = v
|
||||||
|
return c
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
counter := Counter{Value: 5}
|
||||||
|
|
||||||
|
// Test increment
|
||||||
|
increment := func(v int) int { return v + 1 }
|
||||||
|
modifyIncrement := Modify[Counter](increment)(valueLens)
|
||||||
|
incremented := modifyIncrement(counter)
|
||||||
|
assert.Equal(t, 6, incremented.Value)
|
||||||
|
assert.Equal(t, 5, counter.Value) // Original unchanged
|
||||||
|
|
||||||
|
// Test double
|
||||||
|
double := func(v int) int { return v * 2 }
|
||||||
|
modifyDouble := Modify[Counter](double)(valueLens)
|
||||||
|
doubled := modifyDouble(counter)
|
||||||
|
assert.Equal(t, 10, doubled.Value)
|
||||||
|
assert.Equal(t, 5, counter.Value) // Original unchanged
|
||||||
|
|
||||||
|
// Test identity (no change)
|
||||||
|
identity := func(v int) int { return v }
|
||||||
|
modifyIdentity := Modify[Counter](identity)(valueLens)
|
||||||
|
unchanged := modifyIdentity(counter)
|
||||||
|
assert.Equal(t, counter, unchanged)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModifyRef(t *testing.T) {
|
||||||
|
valueLens := MakeLensRef(
|
||||||
|
func(s *Street) int { return s.num },
|
||||||
|
func(s *Street, num int) *Street {
|
||||||
|
s.num = num
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
street := &Street{num: 10, name: "Main"}
|
||||||
|
|
||||||
|
// Test increment
|
||||||
|
increment := func(v int) int { return v + 1 }
|
||||||
|
modifyIncrement := Modify[*Street](increment)(valueLens)
|
||||||
|
incremented := modifyIncrement(street)
|
||||||
|
assert.Equal(t, 11, incremented.num)
|
||||||
|
assert.Equal(t, 10, street.num) // Original unchanged
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lens Laws Tests
|
||||||
|
|
||||||
|
func TestMakeLensLaws(t *testing.T) {
|
||||||
|
nameLens := MakeLens(
|
||||||
|
func(s Street) string { return s.name },
|
||||||
|
func(s Street, name string) Street {
|
||||||
|
s.name = name
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
street := Street{num: 1, name: "Main"}
|
||||||
|
newName := "Oak"
|
||||||
|
|
||||||
|
// Law 1: GetSet - lens.Set(lens.Get(s))(s) == s
|
||||||
|
t.Run("GetSet", func(t *testing.T) {
|
||||||
|
result := nameLens.Set(nameLens.Get(street))(street)
|
||||||
|
assert.Equal(t, street, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 2: SetGet - lens.Get(lens.Set(a)(s)) == a
|
||||||
|
t.Run("SetGet", func(t *testing.T) {
|
||||||
|
result := nameLens.Get(nameLens.Set(newName)(street))
|
||||||
|
assert.Equal(t, newName, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 3: SetSet - lens.Set(a2)(lens.Set(a1)(s)) == lens.Set(a2)(s)
|
||||||
|
t.Run("SetSet", func(t *testing.T) {
|
||||||
|
result1 := nameLens.Set("Elm")(nameLens.Set(newName)(street))
|
||||||
|
result2 := nameLens.Set("Elm")(street)
|
||||||
|
assert.Equal(t, result2, result1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeLensRefLaws(t *testing.T) {
|
||||||
|
nameLens := MakeLensRef(
|
||||||
|
(*Street).GetName,
|
||||||
|
(*Street).SetName,
|
||||||
|
)
|
||||||
|
|
||||||
|
street := &Street{num: 1, name: "Main"}
|
||||||
|
newName := "Oak"
|
||||||
|
|
||||||
|
// Law 1: GetSet - lens.Set(lens.Get(s))(s) == s
|
||||||
|
t.Run("GetSet", func(t *testing.T) {
|
||||||
|
result := nameLens.Set(nameLens.Get(street))(street)
|
||||||
|
assert.Equal(t, street.name, result.name)
|
||||||
|
assert.Equal(t, street.num, result.num)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 2: SetGet - lens.Get(lens.Set(a)(s)) == a
|
||||||
|
t.Run("SetGet", func(t *testing.T) {
|
||||||
|
result := nameLens.Get(nameLens.Set(newName)(street))
|
||||||
|
assert.Equal(t, newName, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 3: SetSet - lens.Set(a2)(lens.Set(a1)(s)) == lens.Set(a2)(s)
|
||||||
|
t.Run("SetSet", func(t *testing.T) {
|
||||||
|
result1 := nameLens.Set("Elm")(nameLens.Set(newName)(street))
|
||||||
|
result2 := nameLens.Set("Elm")(street)
|
||||||
|
assert.Equal(t, result2.name, result1.name)
|
||||||
|
assert.Equal(t, result2.num, result1.num)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeLensCurriedLaws(t *testing.T) {
|
||||||
|
nameLens := MakeLensCurried(
|
||||||
|
func(s Street) string { return s.name },
|
||||||
|
func(name string) func(Street) Street {
|
||||||
|
return func(s Street) Street {
|
||||||
|
s.name = name
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
street := Street{num: 1, name: "Main"}
|
||||||
|
newName := "Oak"
|
||||||
|
|
||||||
|
// Law 1: GetSet
|
||||||
|
t.Run("GetSet", func(t *testing.T) {
|
||||||
|
result := nameLens.Set(nameLens.Get(street))(street)
|
||||||
|
assert.Equal(t, street, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 2: SetGet
|
||||||
|
t.Run("SetGet", func(t *testing.T) {
|
||||||
|
result := nameLens.Get(nameLens.Set(newName)(street))
|
||||||
|
assert.Equal(t, newName, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 3: SetSet
|
||||||
|
t.Run("SetSet", func(t *testing.T) {
|
||||||
|
result1 := nameLens.Set("Elm")(nameLens.Set(newName)(street))
|
||||||
|
result2 := nameLens.Set("Elm")(street)
|
||||||
|
assert.Equal(t, result2, result1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeLensRefCurriedLaws(t *testing.T) {
|
||||||
|
nameLens := MakeLensRefCurried(
|
||||||
|
func(s *Street) string { return s.name },
|
||||||
|
func(name string) func(*Street) *Street {
|
||||||
|
return func(s *Street) *Street {
|
||||||
|
s.name = name
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
street := &Street{num: 1, name: "Main"}
|
||||||
|
newName := "Oak"
|
||||||
|
|
||||||
|
// Law 1: GetSet
|
||||||
|
t.Run("GetSet", func(t *testing.T) {
|
||||||
|
result := nameLens.Set(nameLens.Get(street))(street)
|
||||||
|
assert.Equal(t, street.name, result.name)
|
||||||
|
assert.Equal(t, street.num, result.num)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 2: SetGet
|
||||||
|
t.Run("SetGet", func(t *testing.T) {
|
||||||
|
result := nameLens.Get(nameLens.Set(newName)(street))
|
||||||
|
assert.Equal(t, newName, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 3: SetSet
|
||||||
|
t.Run("SetSet", func(t *testing.T) {
|
||||||
|
result1 := nameLens.Set("Elm")(nameLens.Set(newName)(street))
|
||||||
|
result2 := nameLens.Set("Elm")(street)
|
||||||
|
assert.Equal(t, result2.name, result1.name)
|
||||||
|
assert.Equal(t, result2.num, result1.num)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeLensWithEqLaws(t *testing.T) {
|
||||||
|
nameLens := MakeLensWithEq(
|
||||||
|
EQ.FromStrictEquals[string](),
|
||||||
|
func(s *Street) string { return s.name },
|
||||||
|
func(s *Street, name string) *Street {
|
||||||
|
s.name = name
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
street := &Street{num: 1, name: "Main"}
|
||||||
|
newName := "Oak"
|
||||||
|
|
||||||
|
// Law 1: GetSet
|
||||||
|
t.Run("GetSet", func(t *testing.T) {
|
||||||
|
result := nameLens.Set(nameLens.Get(street))(street)
|
||||||
|
assert.Equal(t, street.name, result.name)
|
||||||
|
assert.Equal(t, street.num, result.num)
|
||||||
|
// With Eq optimization, should return same pointer
|
||||||
|
assert.Same(t, street, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 2: SetGet
|
||||||
|
t.Run("SetGet", func(t *testing.T) {
|
||||||
|
result := nameLens.Get(nameLens.Set(newName)(street))
|
||||||
|
assert.Equal(t, newName, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 3: SetSet
|
||||||
|
t.Run("SetSet", func(t *testing.T) {
|
||||||
|
result1 := nameLens.Set("Elm")(nameLens.Set(newName)(street))
|
||||||
|
result2 := nameLens.Set("Elm")(street)
|
||||||
|
assert.Equal(t, result2.name, result1.name)
|
||||||
|
assert.Equal(t, result2.num, result1.num)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeLensStrictLaws(t *testing.T) {
|
||||||
|
nameLens := MakeLensStrict(
|
||||||
|
func(s *Street) string { return s.name },
|
||||||
|
func(s *Street, name string) *Street {
|
||||||
|
s.name = name
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
street := &Street{num: 1, name: "Main"}
|
||||||
|
newName := "Oak"
|
||||||
|
|
||||||
|
// Law 1: GetSet
|
||||||
|
t.Run("GetSet", func(t *testing.T) {
|
||||||
|
result := nameLens.Set(nameLens.Get(street))(street)
|
||||||
|
assert.Equal(t, street.name, result.name)
|
||||||
|
assert.Equal(t, street.num, result.num)
|
||||||
|
// With strict equality optimization, should return same pointer
|
||||||
|
assert.Same(t, street, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 2: SetGet
|
||||||
|
t.Run("SetGet", func(t *testing.T) {
|
||||||
|
result := nameLens.Get(nameLens.Set(newName)(street))
|
||||||
|
assert.Equal(t, newName, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 3: SetSet
|
||||||
|
t.Run("SetSet", func(t *testing.T) {
|
||||||
|
result1 := nameLens.Set("Elm")(nameLens.Set(newName)(street))
|
||||||
|
result2 := nameLens.Set("Elm")(street)
|
||||||
|
assert.Equal(t, result2.name, result1.name)
|
||||||
|
assert.Equal(t, result2.num, result1.num)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIdLaws(t *testing.T) {
|
||||||
|
idLens := Id[Street]()
|
||||||
|
street := Street{num: 1, name: "Main"}
|
||||||
|
newStreet := Street{num: 2, name: "Oak"}
|
||||||
|
|
||||||
|
// Law 1: GetSet
|
||||||
|
t.Run("GetSet", func(t *testing.T) {
|
||||||
|
result := idLens.Set(idLens.Get(street))(street)
|
||||||
|
assert.Equal(t, street, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 2: SetGet
|
||||||
|
t.Run("SetGet", func(t *testing.T) {
|
||||||
|
result := idLens.Get(idLens.Set(newStreet)(street))
|
||||||
|
assert.Equal(t, newStreet, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 3: SetSet
|
||||||
|
t.Run("SetSet", func(t *testing.T) {
|
||||||
|
anotherStreet := Street{num: 3, name: "Elm"}
|
||||||
|
result1 := idLens.Set(anotherStreet)(idLens.Set(newStreet)(street))
|
||||||
|
result2 := idLens.Set(anotherStreet)(street)
|
||||||
|
assert.Equal(t, result2, result1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIdRefLaws(t *testing.T) {
|
||||||
|
idLens := IdRef[Street]()
|
||||||
|
street := &Street{num: 1, name: "Main"}
|
||||||
|
newStreet := &Street{num: 2, name: "Oak"}
|
||||||
|
|
||||||
|
// Law 1: GetSet
|
||||||
|
t.Run("GetSet", func(t *testing.T) {
|
||||||
|
result := idLens.Set(idLens.Get(street))(street)
|
||||||
|
assert.Equal(t, street.name, result.name)
|
||||||
|
assert.Equal(t, street.num, result.num)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 2: SetGet
|
||||||
|
t.Run("SetGet", func(t *testing.T) {
|
||||||
|
result := idLens.Get(idLens.Set(newStreet)(street))
|
||||||
|
assert.Equal(t, newStreet, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 3: SetSet
|
||||||
|
t.Run("SetSet", func(t *testing.T) {
|
||||||
|
anotherStreet := &Street{num: 3, name: "Elm"}
|
||||||
|
result1 := idLens.Set(anotherStreet)(idLens.Set(newStreet)(street))
|
||||||
|
result2 := idLens.Set(anotherStreet)(street)
|
||||||
|
assert.Equal(t, result2, result1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComposeLaws(t *testing.T) {
|
||||||
|
streetLens := MakeLensRef((*Street).GetName, (*Street).SetName)
|
||||||
|
addrLens := MakeLensRef((*Address).GetStreet, (*Address).SetStreet)
|
||||||
|
|
||||||
|
// Compose to get street name from address
|
||||||
|
streetNameLens := Compose[*Address](streetLens)(addrLens)
|
||||||
|
|
||||||
|
sampleStreet := Street{num: 220, name: "Schönaicherstr"}
|
||||||
|
sampleAddress := Address{city: "Böblingen", street: &sampleStreet}
|
||||||
|
newName := "Böblingerstr"
|
||||||
|
|
||||||
|
// Law 1: GetSet
|
||||||
|
t.Run("GetSet", func(t *testing.T) {
|
||||||
|
result := streetNameLens.Set(streetNameLens.Get(&sampleAddress))(&sampleAddress)
|
||||||
|
assert.Equal(t, sampleAddress.street.name, result.street.name)
|
||||||
|
assert.Equal(t, sampleAddress.street.num, result.street.num)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 2: SetGet
|
||||||
|
t.Run("SetGet", func(t *testing.T) {
|
||||||
|
result := streetNameLens.Get(streetNameLens.Set(newName)(&sampleAddress))
|
||||||
|
assert.Equal(t, newName, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 3: SetSet
|
||||||
|
t.Run("SetSet", func(t *testing.T) {
|
||||||
|
result1 := streetNameLens.Set("Elm St")(streetNameLens.Set(newName)(&sampleAddress))
|
||||||
|
result2 := streetNameLens.Set("Elm St")(&sampleAddress)
|
||||||
|
assert.Equal(t, result2.street.name, result1.street.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComposeRefLaws(t *testing.T) {
|
||||||
|
streetLens := MakeLensRef((*Street).GetName, (*Street).SetName)
|
||||||
|
addrLens := MakeLensRef((*Address).GetStreet, (*Address).SetStreet)
|
||||||
|
|
||||||
|
// Compose using ComposeRef
|
||||||
|
streetNameLens := ComposeRef[Address](streetLens)(addrLens)
|
||||||
|
|
||||||
|
sampleStreet := Street{num: 220, name: "Schönaicherstr"}
|
||||||
|
sampleAddress := Address{city: "Böblingen", street: &sampleStreet}
|
||||||
|
newName := "Böblingerstr"
|
||||||
|
|
||||||
|
// Law 1: GetSet
|
||||||
|
t.Run("GetSet", func(t *testing.T) {
|
||||||
|
result := streetNameLens.Set(streetNameLens.Get(&sampleAddress))(&sampleAddress)
|
||||||
|
assert.Equal(t, sampleAddress.street.name, result.street.name)
|
||||||
|
assert.Equal(t, sampleAddress.street.num, result.street.num)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 2: SetGet
|
||||||
|
t.Run("SetGet", func(t *testing.T) {
|
||||||
|
result := streetNameLens.Get(streetNameLens.Set(newName)(&sampleAddress))
|
||||||
|
assert.Equal(t, newName, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 3: SetSet
|
||||||
|
t.Run("SetSet", func(t *testing.T) {
|
||||||
|
result1 := streetNameLens.Set("Elm St")(streetNameLens.Set(newName)(&sampleAddress))
|
||||||
|
result2 := streetNameLens.Set("Elm St")(&sampleAddress)
|
||||||
|
assert.Equal(t, result2.street.name, result1.street.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIMapLaws(t *testing.T) {
|
||||||
|
type Celsius float64
|
||||||
|
type Fahrenheit float64
|
||||||
|
|
||||||
|
celsiusToFahrenheit := func(c Celsius) Fahrenheit {
|
||||||
|
return Fahrenheit(c*9/5 + 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
fahrenheitToCelsius := func(f Fahrenheit) Celsius {
|
||||||
|
return Celsius((f - 32) * 5 / 9)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Weather struct {
|
||||||
|
Temperature Celsius
|
||||||
|
}
|
||||||
|
|
||||||
|
tempCelsiusLens := MakeLens(
|
||||||
|
func(w Weather) Celsius { return w.Temperature },
|
||||||
|
func(w Weather, t Celsius) Weather {
|
||||||
|
w.Temperature = t
|
||||||
|
return w
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create a lens that works with Fahrenheit
|
||||||
|
tempFahrenheitLens := F.Pipe1(
|
||||||
|
tempCelsiusLens,
|
||||||
|
IMap[Weather](celsiusToFahrenheit, fahrenheitToCelsius),
|
||||||
|
)
|
||||||
|
|
||||||
|
weather := Weather{Temperature: 20} // 20°C
|
||||||
|
newTempF := Fahrenheit(86) // 86°F (30°C)
|
||||||
|
|
||||||
|
// Law 1: GetSet
|
||||||
|
t.Run("GetSet", func(t *testing.T) {
|
||||||
|
result := tempFahrenheitLens.Set(tempFahrenheitLens.Get(weather))(weather)
|
||||||
|
// Allow small floating point differences
|
||||||
|
assert.InDelta(t, float64(weather.Temperature), float64(result.Temperature), 0.0001)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 2: SetGet
|
||||||
|
t.Run("SetGet", func(t *testing.T) {
|
||||||
|
result := tempFahrenheitLens.Get(tempFahrenheitLens.Set(newTempF)(weather))
|
||||||
|
assert.InDelta(t, float64(newTempF), float64(result), 0.0001)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Law 3: SetSet
|
||||||
|
t.Run("SetSet", func(t *testing.T) {
|
||||||
|
anotherTempF := Fahrenheit(95) // 95°F (35°C)
|
||||||
|
result1 := tempFahrenheitLens.Set(anotherTempF)(tempFahrenheitLens.Set(newTempF)(weather))
|
||||||
|
result2 := tempFahrenheitLens.Set(anotherTempF)(weather)
|
||||||
|
assert.InDelta(t, float64(result2.Temperature), float64(result1.Temperature), 0.0001)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIMapIdentity(t *testing.T) {
|
||||||
|
// IMap with identity functions should behave like the original lens
|
||||||
|
type S struct {
|
||||||
|
a int
|
||||||
|
}
|
||||||
|
|
||||||
|
originalLens := MakeLens(
|
||||||
|
func(s S) int { return s.a },
|
||||||
|
func(s S, a int) S {
|
||||||
|
s.a = a
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Apply IMap with identity functions
|
||||||
|
identityMappedLens := F.Pipe1(
|
||||||
|
originalLens,
|
||||||
|
IMap[S](F.Identity[int], F.Identity[int]),
|
||||||
|
)
|
||||||
|
|
||||||
|
s := S{a: 42}
|
||||||
|
|
||||||
|
// Both lenses should behave identically
|
||||||
|
assert.Equal(t, originalLens.Get(s), identityMappedLens.Get(s))
|
||||||
|
assert.Equal(t, originalLens.Set(100)(s), identityMappedLens.Set(100)(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIMapComposition(t *testing.T) {
|
||||||
|
// IMap(f, g) ∘ IMap(h, k) = IMap(f ∘ h, k ∘ g)
|
||||||
|
type S struct {
|
||||||
|
value int
|
||||||
|
}
|
||||||
|
|
||||||
|
baseLens := MakeLens(
|
||||||
|
func(s S) int { return s.value },
|
||||||
|
func(s S, v int) S {
|
||||||
|
s.value = v
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// First transformation: int -> float64
|
||||||
|
intToFloat := func(i int) float64 { return float64(i) }
|
||||||
|
floatToInt := func(f float64) int { return int(f) }
|
||||||
|
|
||||||
|
// Second transformation: float64 -> string
|
||||||
|
floatToString := func(f float64) string { return F.Pipe1(f, func(x float64) string { return "value" }) }
|
||||||
|
stringToFloat := func(s string) float64 { return 42.0 }
|
||||||
|
|
||||||
|
// Compose IMap twice
|
||||||
|
lens1 := F.Pipe1(baseLens, IMap[S](intToFloat, floatToInt))
|
||||||
|
lens2 := F.Pipe1(lens1, IMap[S](floatToString, stringToFloat))
|
||||||
|
|
||||||
|
// Direct composition
|
||||||
|
lens3 := F.Pipe1(
|
||||||
|
baseLens,
|
||||||
|
IMap[S](
|
||||||
|
F.Flow2(intToFloat, floatToString),
|
||||||
|
F.Flow2(stringToFloat, floatToInt),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
s := S{value: 10}
|
||||||
|
|
||||||
|
// Both should produce the same results
|
||||||
|
assert.Equal(t, lens2.Get(s), lens3.Get(s))
|
||||||
|
assert.Equal(t, lens2.Set("test")(s), lens3.Set("test")(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestModifyLaws(t *testing.T) {
|
||||||
|
// Modify should satisfy: Modify(id) = id
|
||||||
|
// and: Modify(f ∘ g) = Modify(f) ∘ Modify(g)
|
||||||
|
|
||||||
|
type S struct {
|
||||||
|
value int
|
||||||
|
}
|
||||||
|
|
||||||
|
lens := MakeLens(
|
||||||
|
func(s S) int { return s.value },
|
||||||
|
func(s S, v int) S {
|
||||||
|
s.value = v
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
s := S{value: 10}
|
||||||
|
|
||||||
|
// Modify with identity should return the same value
|
||||||
|
t.Run("ModifyIdentity", func(t *testing.T) {
|
||||||
|
modifyIdentity := Modify[S](F.Identity[int])(lens)
|
||||||
|
result := modifyIdentity(s)
|
||||||
|
assert.Equal(t, s, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Modify composition: Modify(f ∘ g) = Modify(f) ∘ Modify(g)
|
||||||
|
t.Run("ModifyComposition", func(t *testing.T) {
|
||||||
|
f := func(x int) int { return x * 2 }
|
||||||
|
g := func(x int) int { return x + 3 }
|
||||||
|
|
||||||
|
// Modify(f ∘ g)
|
||||||
|
composed := F.Flow2(g, f)
|
||||||
|
modifyComposed := Modify[S](composed)(lens)
|
||||||
|
result1 := modifyComposed(s)
|
||||||
|
|
||||||
|
// Modify(f) ∘ Modify(g)
|
||||||
|
modifyG := Modify[S](g)(lens)
|
||||||
|
intermediate := modifyG(s)
|
||||||
|
modifyF := Modify[S](f)(lens)
|
||||||
|
result2 := modifyF(intermediate)
|
||||||
|
|
||||||
|
assert.Equal(t, result1, result2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComposeAssociativity(t *testing.T) {
|
||||||
|
// Test that lens composition is associative:
|
||||||
|
// (l1 ∘ l2) ∘ l3 = l1 ∘ (l2 ∘ l3)
|
||||||
|
|
||||||
|
type Level3 struct {
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Level2 struct {
|
||||||
|
level3 Level3
|
||||||
|
}
|
||||||
|
|
||||||
|
type Level1 struct {
|
||||||
|
level2 Level2
|
||||||
|
}
|
||||||
|
|
||||||
|
lens12 := MakeLens(
|
||||||
|
func(l1 Level1) Level2 { return l1.level2 },
|
||||||
|
func(l1 Level1, l2 Level2) Level1 {
|
||||||
|
l1.level2 = l2
|
||||||
|
return l1
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
lens23 := MakeLens(
|
||||||
|
func(l2 Level2) Level3 { return l2.level3 },
|
||||||
|
func(l2 Level2, l3 Level3) Level2 {
|
||||||
|
l2.level3 = l3
|
||||||
|
return l2
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
lens3Value := MakeLens(
|
||||||
|
func(l3 Level3) string { return l3.value },
|
||||||
|
func(l3 Level3, v string) Level3 {
|
||||||
|
l3.value = v
|
||||||
|
return l3
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// (lens12 ∘ lens23) ∘ lens3Value
|
||||||
|
composed1 := F.Pipe2(
|
||||||
|
lens12,
|
||||||
|
Compose[Level1](lens23),
|
||||||
|
Compose[Level1](lens3Value),
|
||||||
|
)
|
||||||
|
|
||||||
|
// lens12 ∘ (lens23 ∘ lens3Value)
|
||||||
|
composed2 := F.Pipe1(
|
||||||
|
lens12,
|
||||||
|
Compose[Level1](F.Pipe1(lens23, Compose[Level2](lens3Value))),
|
||||||
|
)
|
||||||
|
|
||||||
|
l1 := Level1{
|
||||||
|
level2: Level2{
|
||||||
|
level3: Level3{value: "test"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both compositions should behave identically
|
||||||
|
assert.Equal(t, composed1.Get(l1), composed2.Get(l1))
|
||||||
|
assert.Equal(t, composed1.Set("new")(l1), composed2.Set("new")(l1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Made with Bob
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
import (
|
||||||
EM "github.com/IBM/fp-go/v2/endomorphism"
|
|
||||||
F "github.com/IBM/fp-go/v2/function"
|
F "github.com/IBM/fp-go/v2/function"
|
||||||
"github.com/IBM/fp-go/v2/optics/lens"
|
"github.com/IBM/fp-go/v2/optics/lens"
|
||||||
O "github.com/IBM/fp-go/v2/option"
|
O "github.com/IBM/fp-go/v2/option"
|
||||||
@@ -9,30 +8,23 @@ import (
|
|||||||
|
|
||||||
// fromPredicate returns a `Lens` for a property accessibly as a getter and setter that can be optional
|
// fromPredicate returns a `Lens` for a property accessibly as a getter and setter that can be optional
|
||||||
// if the optional value is set then the nil value will be set instead
|
// if the optional value is set then the nil value will be set instead
|
||||||
func fromPredicate[GET ~func(S) Option[A], SET ~func(S, Option[A]) S, S, A any](creator func(get GET, set SET) LensO[S, A], pred func(A) bool, nilValue A) func(sa Lens[S, A]) LensO[S, A] {
|
func fromPredicate[GET ~func(S) Option[A], SET ~func(Option[A]) Endomorphism[S], S, A any](creator func(get GET, set SET) LensO[S, A], pred func(A) bool, nilValue A) func(sa Lens[S, A]) LensO[S, A] {
|
||||||
fromPred := O.FromPredicate(pred)
|
fromPred := O.FromPredicate(pred)
|
||||||
return func(sa Lens[S, A]) LensO[S, A] {
|
return func(sa Lens[S, A]) LensO[S, A] {
|
||||||
fold := O.Fold(F.Bind1of1(sa.Set)(nilValue), sa.Set)
|
return creator(F.Flow2(sa.Get, fromPred), O.Fold(F.Bind1of1(sa.Set)(nilValue), sa.Set))
|
||||||
return creator(F.Flow2(sa.Get, fromPred), func(s S, a Option[A]) S {
|
|
||||||
return F.Pipe2(
|
|
||||||
a,
|
|
||||||
fold,
|
|
||||||
EM.Ap(s),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromPredicate returns a `Lens` for a property accessibly as a getter and setter that can be optional
|
// FromPredicate returns a `Lens` for a property accessibly as a getter and setter that can be optional
|
||||||
// if the optional value is set then the nil value will be set instead
|
// if the optional value is set then the nil value will be set instead
|
||||||
func FromPredicate[S, A any](pred func(A) bool, nilValue A) func(sa Lens[S, A]) LensO[S, A] {
|
func FromPredicate[S, A any](pred func(A) bool, nilValue A) func(sa Lens[S, A]) LensO[S, A] {
|
||||||
return fromPredicate(lens.MakeLens[func(S) Option[A], func(S, Option[A]) S], pred, nilValue)
|
return fromPredicate(lens.MakeLensCurried[func(S) Option[A], func(Option[A]) Endomorphism[S]], pred, nilValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromPredicateRef returns a `Lens` for a property accessibly as a getter and setter that can be optional
|
// FromPredicateRef returns a `Lens` for a property accessibly as a getter and setter that can be optional
|
||||||
// if the optional value is set then the nil value will be set instead
|
// if the optional value is set then the nil value will be set instead
|
||||||
func FromPredicateRef[S, A any](pred func(A) bool, nilValue A) func(sa Lens[*S, A]) Lens[*S, Option[A]] {
|
func FromPredicateRef[S, A any](pred func(A) bool, nilValue A) func(sa Lens[*S, A]) Lens[*S, Option[A]] {
|
||||||
return fromPredicate(lens.MakeLensRef[func(*S) Option[A], func(*S, Option[A]) *S], pred, nilValue)
|
return fromPredicate(lens.MakeLensRefCurried[S, Option[A]], pred, nilValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromPredicate returns a `Lens` for a property accessibly as a getter and setter that can be optional
|
// FromPredicate returns a `Lens` for a property accessibly as a getter and setter that can be optional
|
||||||
@@ -48,45 +40,41 @@ func FromNillableRef[S, A any](sa Lens[*S, *A]) Lens[*S, Option[*A]] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fromNullableProp returns a `Lens` from a property that may be optional. The getter returns a default value for these items
|
// fromNullableProp returns a `Lens` from a property that may be optional. The getter returns a default value for these items
|
||||||
func fromNullableProp[GET ~func(S) A, SET ~func(S, A) S, S, A any](creator func(get GET, set SET) Lens[S, A], isNullable func(A) Option[A], defaultValue A) func(sa Lens[S, A]) Lens[S, A] {
|
func fromNullableProp[GET ~func(S) A, SET ~func(A) Endomorphism[S], S, A any](creator func(get GET, set SET) Lens[S, A], isNullable func(A) Option[A], defaultValue A) func(sa Lens[S, A]) Lens[S, A] {
|
||||||
|
orElse := O.GetOrElse(F.Constant(defaultValue))
|
||||||
return func(sa Lens[S, A]) Lens[S, A] {
|
return func(sa Lens[S, A]) Lens[S, A] {
|
||||||
return creator(F.Flow3(
|
return creator(F.Flow3(
|
||||||
sa.Get,
|
sa.Get,
|
||||||
isNullable,
|
isNullable,
|
||||||
O.GetOrElse(F.Constant(defaultValue)),
|
orElse,
|
||||||
), func(s S, a A) S {
|
), sa.Set)
|
||||||
return sa.Set(a)(s)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromNullableProp returns a `Lens` from a property that may be optional. The getter returns a default value for these items
|
// FromNullableProp returns a `Lens` from a property that may be optional. The getter returns a default value for these items
|
||||||
func FromNullableProp[S, A any](isNullable func(A) Option[A], defaultValue A) func(sa Lens[S, A]) Lens[S, A] {
|
func FromNullableProp[S, A any](isNullable func(A) Option[A], defaultValue A) func(sa Lens[S, A]) Lens[S, A] {
|
||||||
return fromNullableProp(lens.MakeLens[func(S) A, func(S, A) S], isNullable, defaultValue)
|
return fromNullableProp(lens.MakeLensCurried[func(S) A, func(A) Endomorphism[S]], isNullable, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromNullablePropRef returns a `Lens` from a property that may be optional. The getter returns a default value for these items
|
// FromNullablePropRef returns a `Lens` from a property that may be optional. The getter returns a default value for these items
|
||||||
func FromNullablePropRef[S, A any](isNullable func(A) Option[A], defaultValue A) func(sa Lens[*S, A]) Lens[*S, A] {
|
func FromNullablePropRef[S, A any](isNullable func(A) Option[A], defaultValue A) func(sa Lens[*S, A]) Lens[*S, A] {
|
||||||
return fromNullableProp(lens.MakeLensRef[func(*S) A, func(*S, A) *S], isNullable, defaultValue)
|
return fromNullableProp(lens.MakeLensRefCurried[S, A], isNullable, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fromOption returns a `Lens` from an option property. The getter returns a default value the setter will always set the some option
|
// fromOption returns a `Lens` from an option property. The getter returns a default value the setter will always set the some option
|
||||||
func fromOption[GET ~func(S) A, SET ~func(S, A) S, S, A any](creator func(get GET, set SET) Lens[S, A], defaultValue A) func(sa LensO[S, A]) Lens[S, A] {
|
func fromOption[GET ~func(S) A, SET ~func(A) Endomorphism[S], S, A any](creator func(get GET, set SET) Lens[S, A], defaultValue A) func(sa LensO[S, A]) Lens[S, A] {
|
||||||
|
orElse := O.GetOrElse(F.Constant(defaultValue))
|
||||||
return func(sa LensO[S, A]) Lens[S, A] {
|
return func(sa LensO[S, A]) Lens[S, A] {
|
||||||
return creator(F.Flow2(
|
return creator(F.Flow2(
|
||||||
sa.Get,
|
sa.Get,
|
||||||
O.GetOrElse(F.Constant(defaultValue)),
|
orElse,
|
||||||
), func(s S, a A) S {
|
), F.Flow2(O.Of[A], sa.Set))
|
||||||
return sa.Set(O.Some(a))(s)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromOption returns a `Lens` from an option property. The getter returns a default value the setter will always set the some option
|
// FromOption returns a `Lens` from an option property. The getter returns a default value the setter will always set the some option
|
||||||
func FromOption[S, A any](defaultValue A) func(sa LensO[S, A]) Lens[S, A] {
|
func FromOption[S, A any](defaultValue A) func(sa LensO[S, A]) Lens[S, A] {
|
||||||
return fromOption(lens.MakeLens[func(S) A, func(S, A) S], defaultValue)
|
return fromOption(lens.MakeLensCurried[func(S) A, func(A) Endomorphism[S]], defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromOptionRef creates a lens from an Option property with a default value for pointer structures.
|
// FromOptionRef creates a lens from an Option property with a default value for pointer structures.
|
||||||
@@ -105,5 +93,5 @@ func FromOption[S, A any](defaultValue A) func(sa LensO[S, A]) Lens[S, A] {
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - A function that takes a Lens[*S, Option[A]] and returns a Lens[*S, A]
|
// - A function that takes a Lens[*S, Option[A]] and returns a Lens[*S, A]
|
||||||
func FromOptionRef[S, A any](defaultValue A) func(sa Lens[*S, Option[A]]) Lens[*S, A] {
|
func FromOptionRef[S, A any](defaultValue A) func(sa Lens[*S, Option[A]]) Lens[*S, A] {
|
||||||
return fromOption(lens.MakeLensRef[func(*S) A, func(*S, A) *S], defaultValue)
|
return fromOption(lens.MakeLensRefCurried[S, A], defaultValue)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user