1
0
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:
Dr. Carsten Leue
2025-11-12 15:03:55 +01:00
parent 567315a31c
commit af271e7d10
7 changed files with 1056 additions and 97 deletions

View File

@@ -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)
} }

View File

@@ -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)
} }

View File

@@ -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")
}

View File

@@ -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]]
) )

View File

@@ -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

View 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

View File

@@ -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)
} }