mirror of
https://github.com/IBM/fp-go.git
synced 2026-02-28 13:12:03 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2d111e8ec | ||
|
|
ae141c85c6 | ||
|
|
1230b4581b | ||
|
|
70c831c8f9 |
@@ -29,7 +29,7 @@ func TestFromReaderIOResult(t *testing.T) {
|
||||
ri := func(ctx context.Context) func() result.Result[Reader] {
|
||||
return func() result.Result[Reader] {
|
||||
// Return a Reader that always passes
|
||||
return result.Of[Reader](func(t *testing.T) bool {
|
||||
return result.Of(func(t *testing.T) bool {
|
||||
return true
|
||||
})
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func TestFromReaderIOResult(t *testing.T) {
|
||||
// Create a ReaderIOResult that returns a successful Equal assertion
|
||||
ri := func(ctx context.Context) func() result.Result[Reader] {
|
||||
return func() result.Result[Reader] {
|
||||
return result.Of[Reader](Equal(42)(42))
|
||||
return result.Of(Equal(42)(42))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func TestFromReaderIOResult(t *testing.T) {
|
||||
// Create a ReaderIOResult that returns a failing assertion
|
||||
ri := func(ctx context.Context) func() result.Result[Reader] {
|
||||
return func() result.Result[Reader] {
|
||||
return result.Of[Reader](Equal(42)(43))
|
||||
return result.Of(Equal(42)(43))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ func TestFromReaderIOResult(t *testing.T) {
|
||||
contextUsed = true
|
||||
}
|
||||
return func() result.Result[Reader] {
|
||||
return result.Of[Reader](func(t *testing.T) bool {
|
||||
return result.Of(func(t *testing.T) bool {
|
||||
return true
|
||||
})
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func TestFromReaderIOResult(t *testing.T) {
|
||||
// Create a ReaderIOResult that returns NoError assertion
|
||||
ri := func(ctx context.Context) func() result.Result[Reader] {
|
||||
return func() result.Result[Reader] {
|
||||
return result.Of[Reader](NoError(nil))
|
||||
return result.Of(NoError(nil))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ func TestFromReaderIOResult(t *testing.T) {
|
||||
ArrayLength[int](3)(arr),
|
||||
ArrayContains(2)(arr),
|
||||
})
|
||||
return result.Of[Reader](assertions)
|
||||
return result.Of(assertions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ func TestFromReaderIO(t *testing.T) {
|
||||
// Create a ReaderIO with Result assertions
|
||||
ri := func(ctx context.Context) func() Reader {
|
||||
return func() Reader {
|
||||
successResult := result.Of[int](42)
|
||||
successResult := result.Of(42)
|
||||
return Success(successResult)
|
||||
}
|
||||
}
|
||||
@@ -338,7 +338,7 @@ func TestFromReaderIOResultIntegration(t *testing.T) {
|
||||
}
|
||||
|
||||
// Return a successful assertion
|
||||
return result.Of[Reader](Equal("test")("test"))
|
||||
return result.Of(Equal("test")("test"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,37 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package constant provides the Const functor, a phantom type that ignores its second type parameter.
|
||||
//
|
||||
// The Const functor is a fundamental building block in functional programming that wraps a value
|
||||
// of type E while having a phantom type parameter A. This makes it useful for:
|
||||
// - Accumulating values during traversals (e.g., collecting metadata)
|
||||
// - Implementing optics (lenses, prisms) where you need to track information
|
||||
// - Building applicative functors that combine values using a semigroup
|
||||
//
|
||||
// # The Const Functor
|
||||
//
|
||||
// Const[E, A] wraps a value of type E and has a phantom type parameter A that doesn't affect
|
||||
// the runtime value. This allows it to participate in functor and applicative operations while
|
||||
// maintaining the wrapped value unchanged.
|
||||
//
|
||||
// # Key Properties
|
||||
//
|
||||
// - Map operations ignore the function and preserve the wrapped value
|
||||
// - Ap operations combine wrapped values using a semigroup
|
||||
// - The phantom type A allows type-safe composition with other functors
|
||||
//
|
||||
// # Example Usage
|
||||
//
|
||||
// // Accumulate string values
|
||||
// c1 := Make[string, int]("hello")
|
||||
// c2 := Make[string, int]("world")
|
||||
//
|
||||
// // Map doesn't change the wrapped value
|
||||
// mapped := Map[string, int, string](strconv.Itoa)(c1) // Still contains "hello"
|
||||
//
|
||||
// // Ap combines values using a semigroup
|
||||
// combined := Ap[string, int, int](S.Monoid)(c1)(c2) // Contains "helloworld"
|
||||
package constant
|
||||
|
||||
import (
|
||||
@@ -21,36 +52,209 @@ import (
|
||||
S "github.com/IBM/fp-go/v2/semigroup"
|
||||
)
|
||||
|
||||
// Const is a functor that wraps a value of type E with a phantom type parameter A.
|
||||
//
|
||||
// The Const functor is useful for accumulating values during traversals or implementing
|
||||
// optics. The type parameter A is phantom - it doesn't affect the runtime value but allows
|
||||
// the type to participate in functor and applicative operations.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - E: The type of the wrapped value (the actual data)
|
||||
// - A: The phantom type parameter (not stored, only used for type-level operations)
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // Create a Const that wraps a string
|
||||
// c := Make[string, int]("metadata")
|
||||
//
|
||||
// // The int type parameter is phantom - no int value is stored
|
||||
// value := Unwrap(c) // "metadata"
|
||||
type Const[E, A any] struct {
|
||||
value E
|
||||
}
|
||||
|
||||
// Make creates a Const value wrapping the given value.
|
||||
//
|
||||
// This is the primary constructor for Const values. The second type parameter A
|
||||
// is phantom and must be specified explicitly when needed for type inference.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - E: The type of the value to wrap
|
||||
// - A: The phantom type parameter
|
||||
//
|
||||
// Parameters:
|
||||
// - e: The value to wrap
|
||||
//
|
||||
// Returns:
|
||||
// - A Const[E, A] wrapping the value
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// c := Make[string, int]("hello")
|
||||
// value := Unwrap(c) // "hello"
|
||||
func Make[E, A any](e E) Const[E, A] {
|
||||
return Const[E, A]{value: e}
|
||||
}
|
||||
|
||||
// Unwrap extracts the wrapped value from a Const.
|
||||
//
|
||||
// This is the inverse of Make, retrieving the actual value stored in the Const.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - E: The type of the wrapped value
|
||||
// - A: The phantom type parameter
|
||||
//
|
||||
// Parameters:
|
||||
// - c: The Const to unwrap
|
||||
//
|
||||
// Returns:
|
||||
// - The wrapped value of type E
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// c := Make[string, int]("world")
|
||||
// value := Unwrap(c) // "world"
|
||||
func Unwrap[E, A any](c Const[E, A]) E {
|
||||
return c.value
|
||||
}
|
||||
|
||||
// Of creates a Const containing the monoid's empty value, ignoring the input.
|
||||
//
|
||||
// This implements the Applicative's "pure" operation for Const. It creates a Const
|
||||
// wrapping the monoid's identity element, regardless of the input value.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - E: The type of the wrapped value (must have a monoid)
|
||||
// - A: The input type (ignored)
|
||||
//
|
||||
// Parameters:
|
||||
// - m: The monoid providing the empty value
|
||||
//
|
||||
// Returns:
|
||||
// - A function that ignores its input and returns Const[E, A] with the empty value
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import S "github.com/IBM/fp-go/v2/string"
|
||||
//
|
||||
// of := Of[string, int](S.Monoid)
|
||||
// c := of(42) // Const[string, int] containing ""
|
||||
// value := Unwrap(c) // ""
|
||||
func Of[E, A any](m M.Monoid[E]) func(A) Const[E, A] {
|
||||
return F.Constant1[A](Make[E, A](m.Empty()))
|
||||
}
|
||||
|
||||
// MonadMap applies a function to the phantom type parameter without changing the wrapped value.
|
||||
//
|
||||
// This implements the Functor's map operation for Const. Since the type parameter A is phantom,
|
||||
// the function is never actually called - the wrapped value E remains unchanged.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - E: The type of the wrapped value
|
||||
// - A: The input phantom type
|
||||
// - B: The output phantom type
|
||||
//
|
||||
// Parameters:
|
||||
// - fa: The Const to map over
|
||||
// - _: The function to apply (ignored)
|
||||
//
|
||||
// Returns:
|
||||
// - A Const[E, B] with the same wrapped value
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// c := Make[string, int]("hello")
|
||||
// mapped := MonadMap(c, func(i int) string { return strconv.Itoa(i) })
|
||||
// // mapped still contains "hello", function was never called
|
||||
func MonadMap[E, A, B any](fa Const[E, A], _ func(A) B) Const[E, B] {
|
||||
return Make[E, B](fa.value)
|
||||
}
|
||||
|
||||
// MonadAp combines two Const values using a semigroup.
|
||||
//
|
||||
// This implements the Applicative's ap operation for Const. It combines the wrapped
|
||||
// values from both Const instances using the provided semigroup, ignoring the function
|
||||
// type in the first argument.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - E: The type of the wrapped values (must have a semigroup)
|
||||
// - A: The input phantom type
|
||||
// - B: The output phantom type
|
||||
//
|
||||
// Parameters:
|
||||
// - s: The semigroup for combining wrapped values
|
||||
//
|
||||
// Returns:
|
||||
// - A function that takes two Const values and combines their wrapped values
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import S "github.com/IBM/fp-go/v2/string"
|
||||
//
|
||||
// ap := MonadAp[string, int, int](S.Monoid)
|
||||
// c1 := Make[string, func(int) int]("hello")
|
||||
// c2 := Make[string, int]("world")
|
||||
// result := ap(c1, c2) // Const containing "helloworld"
|
||||
func MonadAp[E, A, B any](s S.Semigroup[E]) func(fab Const[E, func(A) B], fa Const[E, A]) Const[E, B] {
|
||||
return func(fab Const[E, func(A) B], fa Const[E, A]) Const[E, B] {
|
||||
return Make[E, B](s.Concat(fab.value, fa.value))
|
||||
}
|
||||
}
|
||||
|
||||
// Map applies a function to the phantom type parameter without changing the wrapped value.
|
||||
//
|
||||
// This is the curried version of MonadMap, providing a more functional programming style.
|
||||
// The function is never actually called since A is a phantom type.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - E: The type of the wrapped value
|
||||
// - A: The input phantom type
|
||||
// - B: The output phantom type
|
||||
//
|
||||
// Parameters:
|
||||
// - f: The function to apply (ignored)
|
||||
//
|
||||
// Returns:
|
||||
// - A function that transforms Const[E, A] to Const[E, B]
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import F "github.com/IBM/fp-go/v2/function"
|
||||
//
|
||||
// c := Make[string, int]("data")
|
||||
// mapped := F.Pipe1(c, Map[string, int, string](strconv.Itoa))
|
||||
// // mapped still contains "data"
|
||||
func Map[E, A, B any](f func(A) B) func(fa Const[E, A]) Const[E, B] {
|
||||
return F.Bind2nd(MonadMap[E, A, B], f)
|
||||
}
|
||||
|
||||
// Ap combines Const values using a semigroup in a curried style.
|
||||
//
|
||||
// This is the curried version of MonadAp, providing data-last style for better composition.
|
||||
// It combines the wrapped values from both Const instances using the provided semigroup.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - E: The type of the wrapped values (must have a semigroup)
|
||||
// - A: The input phantom type
|
||||
// - B: The output phantom type
|
||||
//
|
||||
// Parameters:
|
||||
// - s: The semigroup for combining wrapped values
|
||||
//
|
||||
// Returns:
|
||||
// - A curried function for combining Const values
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import (
|
||||
// F "github.com/IBM/fp-go/v2/function"
|
||||
// S "github.com/IBM/fp-go/v2/string"
|
||||
// )
|
||||
//
|
||||
// c1 := Make[string, int]("hello")
|
||||
// c2 := Make[string, func(int) int]("world")
|
||||
// result := F.Pipe1(c1, Ap[string, int, int](S.Monoid)(c2))
|
||||
// // result contains "helloworld"
|
||||
func Ap[E, A, B any](s S.Semigroup[E]) func(fa Const[E, A]) func(fab Const[E, func(A) B]) Const[E, B] {
|
||||
monadap := MonadAp[E, A, B](s)
|
||||
return func(fa Const[E, A]) func(fab Const[E, func(A) B]) Const[E, B] {
|
||||
|
||||
@@ -16,25 +16,340 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/internal/utils"
|
||||
N "github.com/IBM/fp-go/v2/number"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMap(t *testing.T) {
|
||||
fa := Make[string, int]("foo")
|
||||
assert.Equal(t, fa, F.Pipe1(fa, Map[string](utils.Double)))
|
||||
// TestMake tests the Make constructor
|
||||
func TestMake(t *testing.T) {
|
||||
t.Run("creates Const with string value", func(t *testing.T) {
|
||||
c := Make[string, int]("hello")
|
||||
assert.Equal(t, "hello", Unwrap(c))
|
||||
})
|
||||
|
||||
t.Run("creates Const with int value", func(t *testing.T) {
|
||||
c := Make[int, string](42)
|
||||
assert.Equal(t, 42, Unwrap(c))
|
||||
})
|
||||
|
||||
t.Run("creates Const with struct value", func(t *testing.T) {
|
||||
type Config struct {
|
||||
Name string
|
||||
Port int
|
||||
}
|
||||
cfg := Config{Name: "server", Port: 8080}
|
||||
c := Make[Config, bool](cfg)
|
||||
assert.Equal(t, cfg, Unwrap(c))
|
||||
})
|
||||
}
|
||||
|
||||
// TestUnwrap tests extracting values from Const
|
||||
func TestUnwrap(t *testing.T) {
|
||||
t.Run("unwraps string value", func(t *testing.T) {
|
||||
c := Make[string, int]("world")
|
||||
value := Unwrap(c)
|
||||
assert.Equal(t, "world", value)
|
||||
})
|
||||
|
||||
t.Run("unwraps empty string", func(t *testing.T) {
|
||||
c := Make[string, int]("")
|
||||
value := Unwrap(c)
|
||||
assert.Equal(t, "", value)
|
||||
})
|
||||
|
||||
t.Run("unwraps zero value", func(t *testing.T) {
|
||||
c := Make[int, string](0)
|
||||
value := Unwrap(c)
|
||||
assert.Equal(t, 0, value)
|
||||
})
|
||||
}
|
||||
|
||||
// TestOf tests the Of function
|
||||
func TestOf(t *testing.T) {
|
||||
assert.Equal(t, Make[string, int](""), Of[string, int](S.Monoid)(1))
|
||||
t.Run("creates Const with monoid empty value", func(t *testing.T) {
|
||||
of := Of[string, int](S.Monoid)
|
||||
c := of(42)
|
||||
assert.Equal(t, "", Unwrap(c))
|
||||
})
|
||||
|
||||
t.Run("ignores input value", func(t *testing.T) {
|
||||
of := Of[string, int](S.Monoid)
|
||||
c1 := of(1)
|
||||
c2 := of(100)
|
||||
assert.Equal(t, Unwrap(c1), Unwrap(c2))
|
||||
})
|
||||
|
||||
t.Run("works with int monoid", func(t *testing.T) {
|
||||
of := Of[int, string](N.MonoidSum[int]())
|
||||
c := of("ignored")
|
||||
assert.Equal(t, 0, Unwrap(c))
|
||||
})
|
||||
}
|
||||
|
||||
func TestAp(t *testing.T) {
|
||||
fab := Make[string, int]("bar")
|
||||
assert.Equal(t, Make[string, int]("foobar"), Ap[string, int, int](S.Monoid)(fab)(Make[string, func(int) int]("foo")))
|
||||
// TestMap tests the Map function
|
||||
func TestMap(t *testing.T) {
|
||||
t.Run("preserves wrapped value", func(t *testing.T) {
|
||||
fa := Make[string, int]("foo")
|
||||
result := F.Pipe1(fa, Map[string](utils.Double))
|
||||
assert.Equal(t, "foo", Unwrap(result))
|
||||
})
|
||||
|
||||
t.Run("changes phantom type", func(t *testing.T) {
|
||||
fa := Make[string, int]("data")
|
||||
fb := Map[string, int, string](strconv.Itoa)(fa)
|
||||
// Value unchanged, but type changed from Const[string, int] to Const[string, string]
|
||||
assert.Equal(t, "data", Unwrap(fb))
|
||||
})
|
||||
|
||||
t.Run("function is never called", func(t *testing.T) {
|
||||
called := false
|
||||
fa := Make[string, int]("test")
|
||||
fb := Map[string, int, string](func(i int) string {
|
||||
called = true
|
||||
return strconv.Itoa(i)
|
||||
})(fa)
|
||||
assert.False(t, called, "Map function should not be called")
|
||||
assert.Equal(t, "test", Unwrap(fb))
|
||||
})
|
||||
}
|
||||
|
||||
// TestMonadMap tests the MonadMap function
|
||||
func TestMonadMap(t *testing.T) {
|
||||
t.Run("preserves wrapped value", func(t *testing.T) {
|
||||
fa := Make[string, int]("original")
|
||||
fb := MonadMap(fa, func(i int) string { return strconv.Itoa(i) })
|
||||
assert.Equal(t, "original", Unwrap(fb))
|
||||
})
|
||||
|
||||
t.Run("works with different types", func(t *testing.T) {
|
||||
fa := Make[int, string](42)
|
||||
fb := MonadMap(fa, func(s string) bool { return len(s) > 0 })
|
||||
assert.Equal(t, 42, Unwrap(fb))
|
||||
})
|
||||
}
|
||||
|
||||
// TestAp tests the Ap function
|
||||
func TestAp(t *testing.T) {
|
||||
t.Run("combines string values", func(t *testing.T) {
|
||||
fab := Make[string, int]("bar")
|
||||
fa := Make[string, func(int) int]("foo")
|
||||
result := Ap[string, int, int](S.Monoid)(fab)(fa)
|
||||
assert.Equal(t, "foobar", Unwrap(result))
|
||||
})
|
||||
|
||||
t.Run("combines int values with sum", func(t *testing.T) {
|
||||
fab := Make[int, string](10)
|
||||
fa := Make[int, func(string) string](5)
|
||||
result := Ap[int, string, string](N.SemigroupSum[int]())(fab)(fa)
|
||||
assert.Equal(t, 15, Unwrap(result))
|
||||
})
|
||||
|
||||
t.Run("combines int values with product", func(t *testing.T) {
|
||||
fab := Make[int, bool](3)
|
||||
fa := Make[int, func(bool) bool](4)
|
||||
result := Ap[int, bool, bool](N.SemigroupProduct[int]())(fab)(fa)
|
||||
assert.Equal(t, 12, Unwrap(result))
|
||||
})
|
||||
}
|
||||
|
||||
// TestMonadAp tests the MonadAp function
|
||||
func TestMonadAp(t *testing.T) {
|
||||
t.Run("combines values using semigroup", func(t *testing.T) {
|
||||
ap := MonadAp[string, int, int](S.Monoid)
|
||||
fab := Make[string, func(int) int]("hello")
|
||||
fa := Make[string, int]("world")
|
||||
result := ap(fab, fa)
|
||||
assert.Equal(t, "helloworld", Unwrap(result))
|
||||
})
|
||||
|
||||
t.Run("works with empty strings", func(t *testing.T) {
|
||||
ap := MonadAp[string, int, int](S.Monoid)
|
||||
fab := Make[string, func(int) int]("")
|
||||
fa := Make[string, int]("test")
|
||||
result := ap(fab, fa)
|
||||
assert.Equal(t, "test", Unwrap(result))
|
||||
})
|
||||
}
|
||||
|
||||
// TestMonoid tests the Monoid function
|
||||
func TestMonoid(t *testing.T) {
|
||||
t.Run("always returns constant value", func(t *testing.T) {
|
||||
m := Monoid(42)
|
||||
assert.Equal(t, 42, m.Concat(1, 2))
|
||||
assert.Equal(t, 42, m.Concat(100, 200))
|
||||
assert.Equal(t, 42, m.Empty())
|
||||
})
|
||||
|
||||
t.Run("works with strings", func(t *testing.T) {
|
||||
m := Monoid("constant")
|
||||
assert.Equal(t, "constant", m.Concat("a", "b"))
|
||||
assert.Equal(t, "constant", m.Empty())
|
||||
})
|
||||
|
||||
t.Run("works with structs", func(t *testing.T) {
|
||||
type Point struct{ X, Y int }
|
||||
p := Point{X: 1, Y: 2}
|
||||
m := Monoid(p)
|
||||
assert.Equal(t, p, m.Concat(Point{X: 3, Y: 4}, Point{X: 5, Y: 6}))
|
||||
assert.Equal(t, p, m.Empty())
|
||||
})
|
||||
|
||||
t.Run("satisfies monoid laws", func(t *testing.T) {
|
||||
m := Monoid(10)
|
||||
|
||||
// Left identity: Concat(Empty(), x) = x (both return constant)
|
||||
assert.Equal(t, 10, m.Concat(m.Empty(), 5))
|
||||
|
||||
// Right identity: Concat(x, Empty()) = x (both return constant)
|
||||
assert.Equal(t, 10, m.Concat(5, m.Empty()))
|
||||
|
||||
// Associativity: Concat(Concat(x, y), z) = Concat(x, Concat(y, z))
|
||||
left := m.Concat(m.Concat(1, 2), 3)
|
||||
right := m.Concat(1, m.Concat(2, 3))
|
||||
assert.Equal(t, left, right)
|
||||
assert.Equal(t, 10, left)
|
||||
})
|
||||
}
|
||||
|
||||
// TestConstFunctorLaws tests functor laws for Const
|
||||
func TestConstFunctorLaws(t *testing.T) {
|
||||
t.Run("identity law", func(t *testing.T) {
|
||||
// map id = id
|
||||
fa := Make[string, int]("test")
|
||||
mapped := Map[string, int, int](F.Identity[int])(fa)
|
||||
assert.Equal(t, Unwrap(fa), Unwrap(mapped))
|
||||
})
|
||||
|
||||
t.Run("composition law", func(t *testing.T) {
|
||||
// map (g . f) = map g . map f
|
||||
fa := Make[string, int]("data")
|
||||
f := func(i int) string { return strconv.Itoa(i) }
|
||||
g := func(s string) bool { return len(s) > 0 }
|
||||
|
||||
// map (g . f)
|
||||
composed := Map[string, int, bool](func(i int) bool { return g(f(i)) })(fa)
|
||||
|
||||
// map g . map f
|
||||
intermediate := F.Pipe1(fa, Map[string, int, string](f))
|
||||
chained := Map[string, string, bool](g)(intermediate)
|
||||
|
||||
assert.Equal(t, Unwrap(composed), Unwrap(chained))
|
||||
})
|
||||
}
|
||||
|
||||
// TestConstApplicativeLaws tests applicative laws for Const
|
||||
func TestConstApplicativeLaws(t *testing.T) {
|
||||
t.Run("identity law", func(t *testing.T) {
|
||||
// For Const, ap combines the wrapped values using the semigroup
|
||||
// ap (of id) v combines empty (from of) with v's value
|
||||
v := Make[string, int]("value")
|
||||
ofId := Of[string, func(int) int](S.Monoid)(F.Identity[int])
|
||||
result := Ap[string, int, int](S.Monoid)(v)(ofId)
|
||||
// Result combines "" (from Of) with "value" using string monoid
|
||||
assert.Equal(t, "value", Unwrap(result))
|
||||
})
|
||||
|
||||
t.Run("homomorphism law", func(t *testing.T) {
|
||||
// ap (of f) (of x) = of (f x)
|
||||
f := func(i int) string { return strconv.Itoa(i) }
|
||||
x := 42
|
||||
|
||||
ofF := Of[string, func(int) string](S.Monoid)(f)
|
||||
ofX := Of[string, int](S.Monoid)(x)
|
||||
left := Ap[string, int, string](S.Monoid)(ofX)(ofF)
|
||||
|
||||
right := Of[string, string](S.Monoid)(f(x))
|
||||
|
||||
assert.Equal(t, Unwrap(left), Unwrap(right))
|
||||
})
|
||||
}
|
||||
|
||||
// TestConstEdgeCases tests edge cases
|
||||
func TestConstEdgeCases(t *testing.T) {
|
||||
t.Run("empty string values", func(t *testing.T) {
|
||||
c := Make[string, int]("")
|
||||
assert.Equal(t, "", Unwrap(c))
|
||||
|
||||
mapped := Map[string, int, string](strconv.Itoa)(c)
|
||||
assert.Equal(t, "", Unwrap(mapped))
|
||||
})
|
||||
|
||||
t.Run("zero values", func(t *testing.T) {
|
||||
c := Make[int, string](0)
|
||||
assert.Equal(t, 0, Unwrap(c))
|
||||
})
|
||||
|
||||
t.Run("nil pointer", func(t *testing.T) {
|
||||
var ptr *int
|
||||
c := Make[*int, string](ptr)
|
||||
assert.Nil(t, Unwrap(c))
|
||||
})
|
||||
|
||||
t.Run("multiple map operations", func(t *testing.T) {
|
||||
c := Make[string, int]("original")
|
||||
// Chain multiple map operations
|
||||
step1 := Map[string, int, string](strconv.Itoa)(c)
|
||||
step2 := Map[string, string, bool](func(s string) bool { return len(s) > 0 })(step1)
|
||||
result := Map[string, bool, int](func(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})(step2)
|
||||
assert.Equal(t, "original", Unwrap(result))
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkMake benchmarks the Make constructor
|
||||
func BenchmarkMake(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_ = Make[string, int]("test")
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkUnwrap benchmarks the Unwrap function
|
||||
func BenchmarkUnwrap(b *testing.B) {
|
||||
c := Make[string, int]("test")
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_ = Unwrap(c)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkMap benchmarks the Map function
|
||||
func BenchmarkMap(b *testing.B) {
|
||||
c := Make[string, int]("test")
|
||||
mapFn := Map[string, int, string](strconv.Itoa)
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_ = mapFn(c)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkAp benchmarks the Ap function
|
||||
func BenchmarkAp(b *testing.B) {
|
||||
fab := Make[string, int]("hello")
|
||||
fa := Make[string, func(int) int]("world")
|
||||
apFn := Ap[string, int, int](S.Monoid)
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_ = apFn(fab)(fa)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkMonoid benchmarks the Monoid function
|
||||
func BenchmarkMonoid(b *testing.B) {
|
||||
m := Monoid(42)
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_ = m.Concat(1, 2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
// 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 constant
|
||||
|
||||
import (
|
||||
@@ -5,7 +20,47 @@ import (
|
||||
M "github.com/IBM/fp-go/v2/monoid"
|
||||
)
|
||||
|
||||
// Monoid returns a [M.Monoid] that returns a constant value in all operations
|
||||
// Monoid creates a monoid that always returns a constant value.
|
||||
//
|
||||
// This creates a trivial monoid where both the Concat operation and Empty
|
||||
// always return the same constant value, regardless of inputs. This is useful
|
||||
// for testing, placeholder implementations, or when you need a monoid instance
|
||||
// but the actual combining behavior doesn't matter.
|
||||
//
|
||||
// # Monoid Laws
|
||||
//
|
||||
// The constant monoid satisfies all monoid laws trivially:
|
||||
// - Associativity: Concat(Concat(x, y), z) = Concat(x, Concat(y, z)) - always returns 'a'
|
||||
// - Left Identity: Concat(Empty(), x) = x - both return 'a'
|
||||
// - Right Identity: Concat(x, Empty()) = x - both return 'a'
|
||||
//
|
||||
// Type Parameters:
|
||||
// - A: The type of the constant value
|
||||
//
|
||||
// Parameters:
|
||||
// - a: The constant value to return in all operations
|
||||
//
|
||||
// Returns:
|
||||
// - A Monoid[A] that always returns the constant value
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // Create a monoid that always returns 42
|
||||
// m := Monoid(42)
|
||||
// result := m.Concat(1, 2) // 42
|
||||
// empty := m.Empty() // 42
|
||||
//
|
||||
// // Useful for testing or placeholder implementations
|
||||
// type Config struct {
|
||||
// Timeout int
|
||||
// }
|
||||
// defaultConfig := Monoid(Config{Timeout: 30})
|
||||
// config := defaultConfig.Concat(Config{Timeout: 10}, Config{Timeout: 20})
|
||||
// // config is Config{Timeout: 30}
|
||||
//
|
||||
// See also:
|
||||
// - function.Constant2: The underlying constant function
|
||||
// - M.MakeMonoid: The monoid constructor
|
||||
func Monoid[A any](a A) M.Monoid[A] {
|
||||
return M.MakeMonoid(function.Constant2[A, A](a), a)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ type TestContext struct {
|
||||
|
||||
// runEffect is a helper function to run an effect with a context and return the result
|
||||
func runEffect[C, A any](eff Effect[C, A], ctx C) (A, error) {
|
||||
ioResult := Provide[A, C](ctx)(eff)
|
||||
ioResult := Provide[A](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
return readerResult(context.Background())
|
||||
}
|
||||
|
||||
@@ -44,11 +44,11 @@ func TestLocal(t *testing.T) {
|
||||
}
|
||||
|
||||
// Apply Local to transform the context
|
||||
kleisli := Local[string, OuterContext, InnerContext](accessor)
|
||||
kleisli := Local[string](accessor)
|
||||
outerEffect := kleisli(innerEffect)
|
||||
|
||||
// Run with OuterContext
|
||||
ioResult := Provide[string, OuterContext](OuterContext{
|
||||
ioResult := Provide[string](OuterContext{
|
||||
Value: "test",
|
||||
Number: 42,
|
||||
})(outerEffect)
|
||||
@@ -70,11 +70,11 @@ func TestLocal(t *testing.T) {
|
||||
return InnerContext{Value: outer.Value + " transformed"}
|
||||
}
|
||||
|
||||
kleisli := Local[string, OuterContext, InnerContext](accessor)
|
||||
kleisli := Local[string](accessor)
|
||||
outerEffect := kleisli(innerEffect)
|
||||
|
||||
// Run with OuterContext
|
||||
ioResult := Provide[string, OuterContext](OuterContext{
|
||||
ioResult := Provide[string](OuterContext{
|
||||
Value: "original",
|
||||
Number: 100,
|
||||
})(outerEffect)
|
||||
@@ -93,10 +93,10 @@ func TestLocal(t *testing.T) {
|
||||
return InnerContext{Value: outer.Value}
|
||||
}
|
||||
|
||||
kleisli := Local[string, OuterContext, InnerContext](accessor)
|
||||
kleisli := Local[string](accessor)
|
||||
outerEffect := kleisli(innerEffect)
|
||||
|
||||
ioResult := Provide[string, OuterContext](OuterContext{
|
||||
ioResult := Provide[string](OuterContext{
|
||||
Value: "test",
|
||||
Number: 42,
|
||||
})(outerEffect)
|
||||
@@ -122,12 +122,12 @@ func TestLocal(t *testing.T) {
|
||||
level3Effect := Of[Level3]("deep result")
|
||||
|
||||
// Transform Level2 -> Level3
|
||||
local23 := Local[string, Level2, Level3](func(l2 Level2) Level3 {
|
||||
local23 := Local[string](func(l2 Level2) Level3 {
|
||||
return Level3{C: l2.B + "-c"}
|
||||
})
|
||||
|
||||
// Transform Level1 -> Level2
|
||||
local12 := Local[string, Level1, Level2](func(l1 Level1) Level2 {
|
||||
local12 := Local[string](func(l1 Level1) Level2 {
|
||||
return Level2{B: l1.A + "-b"}
|
||||
})
|
||||
|
||||
@@ -136,7 +136,7 @@ func TestLocal(t *testing.T) {
|
||||
level1Effect := local12(level2Effect)
|
||||
|
||||
// Run with Level1 context
|
||||
ioResult := Provide[string, Level1](Level1{A: "a"})(level1Effect)
|
||||
ioResult := Provide[string](Level1{A: "a"})(level1Effect)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -165,11 +165,11 @@ func TestLocal(t *testing.T) {
|
||||
return app.DB
|
||||
}
|
||||
|
||||
kleisli := Local[string, AppConfig, DatabaseConfig](accessor)
|
||||
kleisli := Local[string](accessor)
|
||||
appEffect := kleisli(dbEffect)
|
||||
|
||||
// Run with full AppConfig
|
||||
ioResult := Provide[string, AppConfig](AppConfig{
|
||||
ioResult := Provide[string](AppConfig{
|
||||
DB: DatabaseConfig{
|
||||
Host: "localhost",
|
||||
Port: 5432,
|
||||
@@ -195,21 +195,21 @@ func TestContramap(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test Local
|
||||
localKleisli := Local[int, OuterContext, InnerContext](accessor)
|
||||
localKleisli := Local[int](accessor)
|
||||
localEffect := localKleisli(innerEffect)
|
||||
|
||||
// Test Contramap
|
||||
contramapKleisli := Contramap[int, OuterContext, InnerContext](accessor)
|
||||
contramapKleisli := Contramap[int](accessor)
|
||||
contramapEffect := contramapKleisli(innerEffect)
|
||||
|
||||
outerCtx := OuterContext{Value: "test", Number: 100}
|
||||
|
||||
// Run both
|
||||
localIO := Provide[int, OuterContext](outerCtx)(localEffect)
|
||||
localIO := Provide[int](outerCtx)(localEffect)
|
||||
localReader := RunSync(localIO)
|
||||
localResult, localErr := localReader(context.Background())
|
||||
|
||||
contramapIO := Provide[int, OuterContext](outerCtx)(contramapEffect)
|
||||
contramapIO := Provide[int](outerCtx)(contramapEffect)
|
||||
contramapReader := RunSync(contramapIO)
|
||||
contramapResult, contramapErr := contramapReader(context.Background())
|
||||
|
||||
@@ -225,10 +225,10 @@ func TestContramap(t *testing.T) {
|
||||
return InnerContext{Value: outer.Value + " modified"}
|
||||
}
|
||||
|
||||
kleisli := Contramap[string, OuterContext, InnerContext](accessor)
|
||||
kleisli := Contramap[string](accessor)
|
||||
outerEffect := kleisli(innerEffect)
|
||||
|
||||
ioResult := Provide[string, OuterContext](OuterContext{
|
||||
ioResult := Provide[string](OuterContext{
|
||||
Value: "original",
|
||||
Number: 50,
|
||||
})(outerEffect)
|
||||
@@ -247,10 +247,10 @@ func TestContramap(t *testing.T) {
|
||||
return InnerContext{Value: outer.Value}
|
||||
}
|
||||
|
||||
kleisli := Contramap[int, OuterContext, InnerContext](accessor)
|
||||
kleisli := Contramap[int](accessor)
|
||||
outerEffect := kleisli(innerEffect)
|
||||
|
||||
ioResult := Provide[int, OuterContext](OuterContext{
|
||||
ioResult := Provide[int](OuterContext{
|
||||
Value: "test",
|
||||
Number: 42,
|
||||
})(outerEffect)
|
||||
@@ -278,12 +278,12 @@ func TestLocalAndContramapInteroperability(t *testing.T) {
|
||||
effect3 := Of[Config3]("result")
|
||||
|
||||
// Use Local for first transformation
|
||||
local23 := Local[string, Config2, Config3](func(c2 Config2) Config3 {
|
||||
local23 := Local[string](func(c2 Config2) Config3 {
|
||||
return Config3{Info: c2.Data}
|
||||
})
|
||||
|
||||
// Use Contramap for second transformation
|
||||
contramap12 := Contramap[string, Config1, Config2](func(c1 Config1) Config2 {
|
||||
contramap12 := Contramap[string](func(c1 Config1) Config2 {
|
||||
return Config2{Data: c1.Value}
|
||||
})
|
||||
|
||||
@@ -292,7 +292,7 @@ func TestLocalAndContramapInteroperability(t *testing.T) {
|
||||
effect1 := contramap12(effect2)
|
||||
|
||||
// Run
|
||||
ioResult := Provide[string, Config1](Config1{Value: "test"})(effect1)
|
||||
ioResult := Provide[string](Config1{Value: "test"})(effect1)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -326,7 +326,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
appEffect := transform(dbEffect)
|
||||
|
||||
// Run with AppConfig
|
||||
ioResult := Provide[string, AppConfig](AppConfig{
|
||||
ioResult := Provide[string](AppConfig{
|
||||
ConfigPath: "/etc/app.conf",
|
||||
})(appEffect)
|
||||
readerResult := RunSync(ioResult)
|
||||
@@ -356,7 +356,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
transform := LocalEffectK[string](failingTransform)
|
||||
outerEffect := transform(innerEffect)
|
||||
|
||||
ioResult := Provide[string, OuterCtx](OuterCtx{Path: "test"})(outerEffect)
|
||||
ioResult := Provide[string](OuterCtx{Path: "test"})(outerEffect)
|
||||
readerResult := RunSync(ioResult)
|
||||
_, err := readerResult(context.Background())
|
||||
|
||||
@@ -384,7 +384,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
transformK := LocalEffectK[string](transform)
|
||||
outerEffect := transformK(innerEffect)
|
||||
|
||||
ioResult := Provide[string, OuterCtx](OuterCtx{Path: "test"})(outerEffect)
|
||||
ioResult := Provide[string](OuterCtx{Path: "test"})(outerEffect)
|
||||
readerResult := RunSync(ioResult)
|
||||
_, err := readerResult(context.Background())
|
||||
|
||||
@@ -417,7 +417,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
transform := LocalEffectK[string](loadConfigEffect)
|
||||
appEffect := transform(configEffect)
|
||||
|
||||
ioResult := Provide[string, AppContext](AppContext{
|
||||
ioResult := Provide[string](AppContext{
|
||||
ConfigFile: "config.json",
|
||||
})(appEffect)
|
||||
readerResult := RunSync(ioResult)
|
||||
@@ -456,7 +456,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
level1Effect := transform12(level2Effect)
|
||||
|
||||
// Run with Level1 context
|
||||
ioResult := Provide[string, Level1](Level1{A: "a"})(level1Effect)
|
||||
ioResult := Provide[string](Level1{A: "a"})(level1Effect)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -497,7 +497,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
transform := LocalEffectK[string](transformWithContext)
|
||||
appEffect := transform(dbEffect)
|
||||
|
||||
ioResult := Provide[string, AppConfig](AppConfig{
|
||||
ioResult := Provide[string](AppConfig{
|
||||
Environment: "prod",
|
||||
DBHost: "localhost",
|
||||
DBPort: 5432,
|
||||
@@ -534,14 +534,14 @@ func TestLocalEffectK(t *testing.T) {
|
||||
outerEffect := transform(innerEffect)
|
||||
|
||||
// Test with invalid config
|
||||
ioResult := Provide[string, RawConfig](RawConfig{APIKey: ""})(outerEffect)
|
||||
ioResult := Provide[string](RawConfig{APIKey: ""})(outerEffect)
|
||||
readerResult := RunSync(ioResult)
|
||||
_, err := readerResult(context.Background())
|
||||
|
||||
assert.Error(t, err)
|
||||
|
||||
// Test with valid config
|
||||
ioResult2 := Provide[string, RawConfig](RawConfig{APIKey: "valid-key"})(outerEffect)
|
||||
ioResult2 := Provide[string](RawConfig{APIKey: "valid-key"})(outerEffect)
|
||||
readerResult2 := RunSync(ioResult2)
|
||||
result, err2 := readerResult2(context.Background())
|
||||
|
||||
@@ -569,7 +569,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
})
|
||||
|
||||
// Use Local for second transformation (pure)
|
||||
local12 := Local[string, Level1, Level2](func(l1 Level1) Level2 {
|
||||
local12 := Local[string](func(l1 Level1) Level2 {
|
||||
return Level2{Data: l1.Value}
|
||||
})
|
||||
|
||||
@@ -578,7 +578,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
effect1 := local12(effect2)
|
||||
|
||||
// Run
|
||||
ioResult := Provide[string, Level1](Level1{Value: "test"})(effect1)
|
||||
ioResult := Provide[string](Level1{Value: "test"})(effect1)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -610,7 +610,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
transform := LocalEffectK[int](complexTransform)
|
||||
outerEffect := transform(innerEffect)
|
||||
|
||||
ioResult := Provide[int, OuterCtx](OuterCtx{Multiplier: 3})(outerEffect)
|
||||
ioResult := Provide[int](OuterCtx{Multiplier: 3})(outerEffect)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ func TestChain_Success(t *testing.T) {
|
||||
t.Run("sequences two effects", func(t *testing.T) {
|
||||
eff := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
Chain[TestConfig](func(x int) Effect[TestConfig, string] {
|
||||
Chain(func(x int) Effect[TestConfig, string] {
|
||||
return Of[TestConfig](strconv.Itoa(x))
|
||||
}),
|
||||
)
|
||||
@@ -149,10 +149,10 @@ func TestChain_Success(t *testing.T) {
|
||||
t.Run("chains multiple effects", func(t *testing.T) {
|
||||
eff := F.Pipe2(
|
||||
Of[TestConfig](10),
|
||||
Chain[TestConfig](func(x int) Effect[TestConfig, int] {
|
||||
Chain(func(x int) Effect[TestConfig, int] {
|
||||
return Of[TestConfig](x + 5)
|
||||
}),
|
||||
Chain[TestConfig](func(x int) Effect[TestConfig, int] {
|
||||
Chain(func(x int) Effect[TestConfig, int] {
|
||||
return Of[TestConfig](x * 2)
|
||||
}),
|
||||
)
|
||||
@@ -166,7 +166,7 @@ func TestChain_Failure(t *testing.T) {
|
||||
testErr := errors.New("first error")
|
||||
eff := F.Pipe1(
|
||||
Fail[TestConfig, int](testErr),
|
||||
Chain[TestConfig](func(x int) Effect[TestConfig, string] {
|
||||
Chain(func(x int) Effect[TestConfig, string] {
|
||||
return Of[TestConfig]("should not execute")
|
||||
}),
|
||||
)
|
||||
@@ -178,7 +178,7 @@ func TestChain_Failure(t *testing.T) {
|
||||
testErr := errors.New("second error")
|
||||
eff := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
Chain[TestConfig](func(x int) Effect[TestConfig, string] {
|
||||
Chain(func(x int) Effect[TestConfig, string] {
|
||||
return Fail[TestConfig, string](testErr)
|
||||
}),
|
||||
)
|
||||
@@ -503,7 +503,7 @@ func TestTap_Success(t *testing.T) {
|
||||
log := []string{}
|
||||
eff := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
Tap[TestConfig](func(x int) Effect[TestConfig, any] {
|
||||
Tap(func(x int) Effect[TestConfig, any] {
|
||||
log = append(log, fmt.Sprintf("tapped: %d", x))
|
||||
return Of[TestConfig, any](nil)
|
||||
}),
|
||||
@@ -517,11 +517,11 @@ func TestTap_Success(t *testing.T) {
|
||||
log := []string{}
|
||||
eff := F.Pipe2(
|
||||
Of[TestConfig](10),
|
||||
Tap[TestConfig](func(x int) Effect[TestConfig, any] {
|
||||
Tap(func(x int) Effect[TestConfig, any] {
|
||||
log = append(log, "first")
|
||||
return Of[TestConfig, any](nil)
|
||||
}),
|
||||
Tap[TestConfig](func(x int) Effect[TestConfig, any] {
|
||||
Tap(func(x int) Effect[TestConfig, any] {
|
||||
log = append(log, "second")
|
||||
return Of[TestConfig, any](nil)
|
||||
}),
|
||||
@@ -538,7 +538,7 @@ func TestTap_Failure(t *testing.T) {
|
||||
executed := false
|
||||
eff := F.Pipe1(
|
||||
Fail[TestConfig, int](testErr),
|
||||
Tap[TestConfig](func(x int) Effect[TestConfig, any] {
|
||||
Tap(func(x int) Effect[TestConfig, any] {
|
||||
executed = true
|
||||
return Of[TestConfig, any](nil)
|
||||
}),
|
||||
@@ -620,7 +620,7 @@ func TestRead_Success(t *testing.T) {
|
||||
// Create an effect that uses the context's Multiplier
|
||||
eff := F.Pipe1(
|
||||
Of[TestConfig](10),
|
||||
ChainReaderK[TestConfig](func(x int) reader.Reader[TestConfig, int] {
|
||||
ChainReaderK(func(x int) reader.Reader[TestConfig, int] {
|
||||
return func(cfg TestConfig) int {
|
||||
return x * cfg.Multiplier
|
||||
}
|
||||
|
||||
@@ -641,8 +641,8 @@ func TestChainThunkK_Integration(t *testing.T) {
|
||||
|
||||
computation := F.Pipe3(
|
||||
Of[TestConfig](5),
|
||||
ChainReaderK[TestConfig](addMultiplier),
|
||||
ChainReaderIOK[TestConfig](logValue),
|
||||
ChainReaderK(addMultiplier),
|
||||
ChainReaderIOK(logValue),
|
||||
ChainThunkK[TestConfig](processThunk),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
@@ -28,7 +28,7 @@ func TestProvide(t *testing.T) {
|
||||
ctx := TestContext{Value: "test-value"}
|
||||
eff := Of[TestContext]("result")
|
||||
|
||||
ioResult := Provide[string, TestContext](ctx)(eff)
|
||||
ioResult := Provide[string](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestProvide(t *testing.T) {
|
||||
cfg := Config{Host: "localhost", Port: 8080}
|
||||
eff := Of[Config]("connected")
|
||||
|
||||
ioResult := Provide[string, Config](cfg)(eff)
|
||||
ioResult := Provide[string](cfg)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -58,7 +58,7 @@ func TestProvide(t *testing.T) {
|
||||
ctx := TestContext{Value: "test"}
|
||||
eff := Fail[TestContext, string](expectedErr)
|
||||
|
||||
ioResult := Provide[string, TestContext](ctx)(eff)
|
||||
ioResult := Provide[string](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
_, err := readerResult(context.Background())
|
||||
|
||||
@@ -74,7 +74,7 @@ func TestProvide(t *testing.T) {
|
||||
ctx := SimpleContext{ID: 42}
|
||||
eff := Of[SimpleContext](100)
|
||||
|
||||
ioResult := Provide[int, SimpleContext](ctx)(eff)
|
||||
ioResult := Provide[int](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -89,7 +89,7 @@ func TestProvide(t *testing.T) {
|
||||
return Of[TestContext]("result")
|
||||
})(Of[TestContext](42))
|
||||
|
||||
ioResult := Provide[string, TestContext](ctx)(eff)
|
||||
ioResult := Provide[string](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -104,7 +104,7 @@ func TestProvide(t *testing.T) {
|
||||
return "mapped"
|
||||
})(Of[TestContext](42))
|
||||
|
||||
ioResult := Provide[string, TestContext](ctx)(eff)
|
||||
ioResult := Provide[string](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -118,7 +118,7 @@ func TestRunSync(t *testing.T) {
|
||||
ctx := TestContext{Value: "test"}
|
||||
eff := Of[TestContext](42)
|
||||
|
||||
ioResult := Provide[int, TestContext](ctx)(eff)
|
||||
ioResult := Provide[int](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -130,7 +130,7 @@ func TestRunSync(t *testing.T) {
|
||||
ctx := TestContext{Value: "test"}
|
||||
eff := Of[TestContext]("hello")
|
||||
|
||||
ioResult := Provide[string, TestContext](ctx)(eff)
|
||||
ioResult := Provide[string](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
|
||||
bgCtx := context.Background()
|
||||
@@ -145,7 +145,7 @@ func TestRunSync(t *testing.T) {
|
||||
ctx := TestContext{Value: "test"}
|
||||
eff := Fail[TestContext, int](expectedErr)
|
||||
|
||||
ioResult := Provide[int, TestContext](ctx)(eff)
|
||||
ioResult := Provide[int](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
_, err := readerResult(context.Background())
|
||||
|
||||
@@ -162,7 +162,7 @@ func TestRunSync(t *testing.T) {
|
||||
return Of[TestContext](x + 10)
|
||||
})(Of[TestContext](5)))
|
||||
|
||||
ioResult := Provide[int, TestContext](ctx)(eff)
|
||||
ioResult := Provide[int](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -174,7 +174,7 @@ func TestRunSync(t *testing.T) {
|
||||
ctx := TestContext{Value: "test"}
|
||||
eff := Of[TestContext](42)
|
||||
|
||||
ioResult := Provide[int, TestContext](ctx)(eff)
|
||||
ioResult := Provide[int](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
|
||||
// Run multiple times
|
||||
@@ -200,7 +200,7 @@ func TestRunSync(t *testing.T) {
|
||||
user := User{Name: "Alice", Age: 30}
|
||||
eff := Of[TestContext](user)
|
||||
|
||||
ioResult := Provide[User, TestContext](ctx)(eff)
|
||||
ioResult := Provide[User](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -222,7 +222,7 @@ func TestProvideAndRunSyncIntegration(t *testing.T) {
|
||||
eff := Of[AppConfig]("API call successful")
|
||||
|
||||
// Provide config and run
|
||||
result, err := RunSync(Provide[string, AppConfig](cfg)(eff))(context.Background())
|
||||
result, err := RunSync(Provide[string](cfg)(eff))(context.Background())
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "API call successful", result)
|
||||
@@ -238,7 +238,7 @@ func TestProvideAndRunSyncIntegration(t *testing.T) {
|
||||
|
||||
eff := Fail[AppConfig, string](expectedErr)
|
||||
|
||||
_, err := RunSync(Provide[string, AppConfig](cfg)(eff))(context.Background())
|
||||
_, err := RunSync(Provide[string](cfg)(eff))(context.Background())
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, expectedErr, err)
|
||||
@@ -253,7 +253,7 @@ func TestProvideAndRunSyncIntegration(t *testing.T) {
|
||||
return Of[TestContext](x * 2)
|
||||
})(Of[TestContext](21)))
|
||||
|
||||
result, err := RunSync(Provide[string, TestContext](ctx)(eff))(context.Background())
|
||||
result, err := RunSync(Provide[string](ctx)(eff))(context.Background())
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "final", result)
|
||||
@@ -281,7 +281,7 @@ func TestProvideAndRunSyncIntegration(t *testing.T) {
|
||||
return State{X: x}
|
||||
})(Of[TestContext](10)))
|
||||
|
||||
result, err := RunSync(Provide[State, TestContext](ctx)(eff))(context.Background())
|
||||
result, err := RunSync(Provide[State](ctx)(eff))(context.Background())
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 10, result.X)
|
||||
@@ -300,11 +300,11 @@ func TestProvideAndRunSyncIntegration(t *testing.T) {
|
||||
innerEff := Of[InnerCtx]("inner result")
|
||||
|
||||
// Transform context
|
||||
transformedEff := Local[string, OuterCtx, InnerCtx](func(outer OuterCtx) InnerCtx {
|
||||
transformedEff := Local[string](func(outer OuterCtx) InnerCtx {
|
||||
return InnerCtx{Data: outer.Value + "-transformed"}
|
||||
})(innerEff)
|
||||
|
||||
result, err := RunSync(Provide[string, OuterCtx](outerCtx)(transformedEff))(context.Background())
|
||||
result, err := RunSync(Provide[string](outerCtx)(transformedEff))(context.Background())
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "inner result", result)
|
||||
@@ -318,7 +318,7 @@ func TestProvideAndRunSyncIntegration(t *testing.T) {
|
||||
return Of[TestContext](x * 2)
|
||||
})(input)
|
||||
|
||||
result, err := RunSync(Provide[[]int, TestContext](ctx)(eff))(context.Background())
|
||||
result, err := RunSync(Provide[[]int](ctx)(eff))(context.Background())
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []int{2, 4, 6, 8, 10}, result)
|
||||
|
||||
@@ -379,7 +379,7 @@ func TestMonadChainLeft(t *testing.T) {
|
||||
func TestChainLeft(t *testing.T) {
|
||||
t.Run("Curried function transforms Left value", func(t *testing.T) {
|
||||
// Create a reusable error handler
|
||||
handleNotFound := ChainLeft[error, string](func(err error) Either[string, int] {
|
||||
handleNotFound := ChainLeft(func(err error) Either[string, int] {
|
||||
if err.Error() == "not found" {
|
||||
return Right[string](0)
|
||||
}
|
||||
@@ -391,7 +391,7 @@ func TestChainLeft(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Curried function with Right value", func(t *testing.T) {
|
||||
handler := ChainLeft[error, string](func(err error) Either[string, int] {
|
||||
handler := ChainLeft(func(err error) Either[string, int] {
|
||||
return Left[int]("should not be called")
|
||||
})
|
||||
|
||||
@@ -401,7 +401,7 @@ func TestChainLeft(t *testing.T) {
|
||||
|
||||
t.Run("Use in pipeline with Pipe", func(t *testing.T) {
|
||||
// Create error transformer
|
||||
toStringError := ChainLeft[int, string](func(code int) Either[string, string] {
|
||||
toStringError := ChainLeft(func(code int) Either[string, string] {
|
||||
return Left[string](fmt.Sprintf("Error: %d", code))
|
||||
})
|
||||
|
||||
@@ -414,12 +414,12 @@ func TestChainLeft(t *testing.T) {
|
||||
|
||||
t.Run("Compose multiple ChainLeft operations", func(t *testing.T) {
|
||||
// First handler: convert error to string
|
||||
handler1 := ChainLeft[error, string](func(err error) Either[string, int] {
|
||||
handler1 := ChainLeft(func(err error) Either[string, int] {
|
||||
return Left[int](err.Error())
|
||||
})
|
||||
|
||||
// Second handler: add prefix to string error
|
||||
handler2 := ChainLeft[string, string](func(s string) Either[string, int] {
|
||||
handler2 := ChainLeft(func(s string) Either[string, int] {
|
||||
return Left[int]("Handled: " + s)
|
||||
})
|
||||
|
||||
|
||||
52
v2/monoid/types.go
Normal file
52
v2/monoid/types.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// 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 monoid
|
||||
|
||||
import "github.com/IBM/fp-go/v2/function"
|
||||
|
||||
// Void is an alias for function.Void, representing the unit type.
|
||||
//
|
||||
// The Void type (also known as Unit in functional programming) has exactly one value,
|
||||
// making it useful for representing the absence of meaningful information. It's similar
|
||||
// to void in other languages, but as a value rather than the absence of a value.
|
||||
//
|
||||
// This type alias is provided in the monoid package for convenience when working with
|
||||
// VoidMonoid and other monoid operations that may use the unit type.
|
||||
//
|
||||
// Common use cases:
|
||||
// - As a return type for functions that perform side effects but don't return meaningful data
|
||||
// - As a placeholder type parameter when a type is required but no data needs to be passed
|
||||
// - In monoid operations where you need to track that operations occurred without caring about results
|
||||
//
|
||||
// See also:
|
||||
// - function.Void: The underlying type definition
|
||||
// - function.VOID: The single inhabitant of the Void type
|
||||
// - VoidMonoid: A monoid instance for the Void type
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // Function that performs an action but returns no meaningful data
|
||||
// func logMessage(msg string) Void {
|
||||
// fmt.Println(msg)
|
||||
// return function.VOID
|
||||
// }
|
||||
//
|
||||
// // Using Void in monoid operations
|
||||
// m := VoidMonoid()
|
||||
// result := m.Concat(function.VOID, function.VOID) // function.VOID
|
||||
type (
|
||||
Void = function.Void
|
||||
)
|
||||
65
v2/monoid/void.go
Normal file
65
v2/monoid/void.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// 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 monoid
|
||||
|
||||
import (
|
||||
"github.com/IBM/fp-go/v2/function"
|
||||
S "github.com/IBM/fp-go/v2/semigroup"
|
||||
)
|
||||
|
||||
// VoidMonoid creates a Monoid for the Void (unit) type.
|
||||
//
|
||||
// The Void type has exactly one value (function.VOID), making it trivial to define
|
||||
// a monoid. This monoid uses the Last semigroup, which always returns the second
|
||||
// argument, though since all Void values are identical, the choice of semigroup
|
||||
// doesn't affect the result.
|
||||
//
|
||||
// This monoid is useful in contexts where:
|
||||
// - A monoid instance is required but no meaningful data needs to be combined
|
||||
// - You need to track that an operation occurred without caring about its result
|
||||
// - Building generic abstractions that work with any monoid, including the trivial case
|
||||
//
|
||||
// # Monoid Laws
|
||||
//
|
||||
// The VoidMonoid satisfies all monoid laws trivially:
|
||||
// - Associativity: Concat(Concat(x, y), z) = Concat(x, Concat(y, z)) - always VOID
|
||||
// - Left Identity: Concat(Empty(), x) = x - always VOID
|
||||
// - Right Identity: Concat(x, Empty()) = x - always VOID
|
||||
//
|
||||
// Returns:
|
||||
// - A Monoid[Void] instance
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// m := VoidMonoid()
|
||||
// result := m.Concat(function.VOID, function.VOID) // function.VOID
|
||||
// empty := m.Empty() // function.VOID
|
||||
//
|
||||
// // Useful for tracking operations without data
|
||||
// type Action = func() Void
|
||||
// actions := []Action{
|
||||
// func() Void { fmt.Println("Action 1"); return function.VOID },
|
||||
// func() Void { fmt.Println("Action 2"); return function.VOID },
|
||||
// }
|
||||
// // Execute all actions and combine results
|
||||
// results := A.Map(func(a Action) Void { return a() })(actions)
|
||||
// _ = ConcatAll(m)(results) // All actions executed, result is VOID
|
||||
func VoidMonoid() Monoid[Void] {
|
||||
return MakeMonoid(
|
||||
S.Last[Void]().Concat,
|
||||
function.VOID,
|
||||
)
|
||||
}
|
||||
292
v2/monoid/void_test.go
Normal file
292
v2/monoid/void_test.go
Normal file
@@ -0,0 +1,292 @@
|
||||
// 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 monoid
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/IBM/fp-go/v2/function"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestVoidMonoid_Basic tests basic VoidMonoid functionality
|
||||
func TestVoidMonoid_Basic(t *testing.T) {
|
||||
m := VoidMonoid()
|
||||
|
||||
// Test Empty returns VOID
|
||||
empty := m.Empty()
|
||||
assert.Equal(t, function.VOID, empty)
|
||||
|
||||
// Test Concat returns VOID (since all Void values are identical)
|
||||
result := m.Concat(function.VOID, function.VOID)
|
||||
assert.Equal(t, function.VOID, result)
|
||||
}
|
||||
|
||||
// TestVoidMonoid_Laws verifies VoidMonoid satisfies monoid laws
|
||||
func TestVoidMonoid_Laws(t *testing.T) {
|
||||
m := VoidMonoid()
|
||||
|
||||
// Since Void has only one value, we test with that value
|
||||
v := function.VOID
|
||||
|
||||
// Left Identity: Concat(Empty(), x) = x
|
||||
t.Run("left identity", func(t *testing.T) {
|
||||
result := m.Concat(m.Empty(), v)
|
||||
assert.Equal(t, v, result, "Left identity law failed")
|
||||
})
|
||||
|
||||
// Right Identity: Concat(x, Empty()) = x
|
||||
t.Run("right identity", func(t *testing.T) {
|
||||
result := m.Concat(v, m.Empty())
|
||||
assert.Equal(t, v, result, "Right identity law failed")
|
||||
})
|
||||
|
||||
// Associativity: Concat(Concat(x, y), z) = Concat(x, Concat(y, z))
|
||||
t.Run("associativity", func(t *testing.T) {
|
||||
left := m.Concat(m.Concat(v, v), v)
|
||||
right := m.Concat(v, m.Concat(v, v))
|
||||
assert.Equal(t, left, right, "Associativity law failed")
|
||||
})
|
||||
|
||||
// All results should be VOID
|
||||
t.Run("all operations return VOID", func(t *testing.T) {
|
||||
assert.Equal(t, function.VOID, m.Concat(v, v))
|
||||
assert.Equal(t, function.VOID, m.Empty())
|
||||
assert.Equal(t, function.VOID, m.Concat(m.Empty(), v))
|
||||
assert.Equal(t, function.VOID, m.Concat(v, m.Empty()))
|
||||
})
|
||||
}
|
||||
|
||||
// TestVoidMonoid_ConcatAll tests combining multiple Void values
|
||||
func TestVoidMonoid_ConcatAll(t *testing.T) {
|
||||
m := VoidMonoid()
|
||||
concatAll := ConcatAll(m)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input []Void
|
||||
expected Void
|
||||
}{
|
||||
{
|
||||
name: "empty slice",
|
||||
input: []Void{},
|
||||
expected: function.VOID,
|
||||
},
|
||||
{
|
||||
name: "single element",
|
||||
input: []Void{function.VOID},
|
||||
expected: function.VOID,
|
||||
},
|
||||
{
|
||||
name: "multiple elements",
|
||||
input: []Void{function.VOID, function.VOID, function.VOID},
|
||||
expected: function.VOID,
|
||||
},
|
||||
{
|
||||
name: "many elements",
|
||||
input: make([]Void, 100),
|
||||
expected: function.VOID,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Initialize slice with VOID values
|
||||
for i := range tt.input {
|
||||
tt.input[i] = function.VOID
|
||||
}
|
||||
result := concatAll(tt.input)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestVoidMonoid_Fold tests the Fold function with VoidMonoid
|
||||
func TestVoidMonoid_Fold(t *testing.T) {
|
||||
m := VoidMonoid()
|
||||
fold := Fold(m)
|
||||
|
||||
// Fold should behave identically to ConcatAll
|
||||
voids := []Void{function.VOID, function.VOID, function.VOID}
|
||||
result := fold(voids)
|
||||
assert.Equal(t, function.VOID, result)
|
||||
|
||||
// Empty fold
|
||||
emptyResult := fold([]Void{})
|
||||
assert.Equal(t, function.VOID, emptyResult)
|
||||
}
|
||||
|
||||
// TestVoidMonoid_Reverse tests that Reverse doesn't affect VoidMonoid
|
||||
func TestVoidMonoid_Reverse(t *testing.T) {
|
||||
m := VoidMonoid()
|
||||
reversed := Reverse(m)
|
||||
|
||||
// Since all Void values are identical, reverse should have no effect
|
||||
v := function.VOID
|
||||
|
||||
assert.Equal(t, m.Concat(v, v), reversed.Concat(v, v))
|
||||
assert.Equal(t, m.Empty(), reversed.Empty())
|
||||
|
||||
// Test identity laws still hold
|
||||
assert.Equal(t, v, reversed.Concat(reversed.Empty(), v))
|
||||
assert.Equal(t, v, reversed.Concat(v, reversed.Empty()))
|
||||
}
|
||||
|
||||
// TestVoidMonoid_ToSemigroup tests conversion to Semigroup
|
||||
func TestVoidMonoid_ToSemigroup(t *testing.T) {
|
||||
m := VoidMonoid()
|
||||
sg := ToSemigroup(m)
|
||||
|
||||
// Should work as a semigroup
|
||||
result := sg.Concat(function.VOID, function.VOID)
|
||||
assert.Equal(t, function.VOID, result)
|
||||
|
||||
// Verify it's the same underlying operation
|
||||
assert.Equal(t, m.Concat(function.VOID, function.VOID), sg.Concat(function.VOID, function.VOID))
|
||||
}
|
||||
|
||||
// TestVoidMonoid_FunctionMonoid tests VoidMonoid with FunctionMonoid
|
||||
func TestVoidMonoid_FunctionMonoid(t *testing.T) {
|
||||
m := VoidMonoid()
|
||||
funcMonoid := FunctionMonoid[string](m)
|
||||
|
||||
// Create functions that return Void
|
||||
f1 := func(s string) Void { return function.VOID }
|
||||
f2 := func(s string) Void { return function.VOID }
|
||||
|
||||
// Combine functions
|
||||
combined := funcMonoid.Concat(f1, f2)
|
||||
|
||||
// Test combined function
|
||||
result := combined("test")
|
||||
assert.Equal(t, function.VOID, result)
|
||||
|
||||
// Test empty function
|
||||
emptyFunc := funcMonoid.Empty()
|
||||
assert.Equal(t, function.VOID, emptyFunc("anything"))
|
||||
}
|
||||
|
||||
// TestVoidMonoid_PracticalUsage demonstrates practical usage patterns
|
||||
func TestVoidMonoid_PracticalUsage(t *testing.T) {
|
||||
m := VoidMonoid()
|
||||
|
||||
// Simulate tracking that operations occurred without caring about results
|
||||
type Action func() Void
|
||||
|
||||
actions := []Action{
|
||||
func() Void { return function.VOID }, // Action 1
|
||||
func() Void { return function.VOID }, // Action 2
|
||||
func() Void { return function.VOID }, // Action 3
|
||||
}
|
||||
|
||||
// Execute all actions and collect results
|
||||
results := make([]Void, len(actions))
|
||||
for i, action := range actions {
|
||||
results[i] = action()
|
||||
}
|
||||
|
||||
// Combine all results (all are VOID)
|
||||
finalResult := ConcatAll(m)(results)
|
||||
assert.Equal(t, function.VOID, finalResult)
|
||||
}
|
||||
|
||||
// TestVoidMonoid_EdgeCases tests edge cases
|
||||
func TestVoidMonoid_EdgeCases(t *testing.T) {
|
||||
m := VoidMonoid()
|
||||
|
||||
t.Run("multiple concatenations", func(t *testing.T) {
|
||||
// Chain multiple Concat operations
|
||||
result := m.Concat(
|
||||
m.Concat(
|
||||
m.Concat(function.VOID, function.VOID),
|
||||
function.VOID,
|
||||
),
|
||||
function.VOID,
|
||||
)
|
||||
assert.Equal(t, function.VOID, result)
|
||||
})
|
||||
|
||||
t.Run("concat with empty", func(t *testing.T) {
|
||||
// Various combinations with Empty()
|
||||
assert.Equal(t, function.VOID, m.Concat(m.Empty(), m.Empty()))
|
||||
assert.Equal(t, function.VOID, m.Concat(m.Concat(m.Empty(), function.VOID), m.Empty()))
|
||||
})
|
||||
|
||||
t.Run("large slice", func(t *testing.T) {
|
||||
// Test with a large number of elements
|
||||
largeSlice := make([]Void, 10000)
|
||||
for i := range largeSlice {
|
||||
largeSlice[i] = function.VOID
|
||||
}
|
||||
result := ConcatAll(m)(largeSlice)
|
||||
assert.Equal(t, function.VOID, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestVoidMonoid_TypeSafety verifies type safety
|
||||
func TestVoidMonoid_TypeSafety(t *testing.T) {
|
||||
m := VoidMonoid()
|
||||
|
||||
// Verify it implements Monoid interface
|
||||
var _ Monoid[Void] = m
|
||||
|
||||
// Verify Empty returns correct type
|
||||
empty := m.Empty()
|
||||
var _ Void = empty
|
||||
|
||||
// Verify Concat returns correct type
|
||||
result := m.Concat(function.VOID, function.VOID)
|
||||
var _ Void = result
|
||||
}
|
||||
|
||||
// BenchmarkVoidMonoid_Concat benchmarks the Concat operation
|
||||
func BenchmarkVoidMonoid_Concat(b *testing.B) {
|
||||
m := VoidMonoid()
|
||||
v := function.VOID
|
||||
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_ = m.Concat(v, v)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkVoidMonoid_ConcatAll benchmarks combining multiple Void values
|
||||
func BenchmarkVoidMonoid_ConcatAll(b *testing.B) {
|
||||
m := VoidMonoid()
|
||||
concatAll := ConcatAll(m)
|
||||
|
||||
voids := make([]Void, 1000)
|
||||
for i := range voids {
|
||||
voids[i] = function.VOID
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_ = concatAll(voids)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkVoidMonoid_Empty benchmarks the Empty operation
|
||||
func BenchmarkVoidMonoid_Empty(b *testing.B) {
|
||||
m := VoidMonoid()
|
||||
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
_ = m.Empty()
|
||||
}
|
||||
}
|
||||
|
||||
// Made with Bob
|
||||
@@ -62,7 +62,7 @@ func TestMonadAltBasicFunctionality(t *testing.T) {
|
||||
|
||||
assert.True(t, either.IsRight(result), "should successfully decode with first codec")
|
||||
|
||||
value := either.GetOrElse(reader.Of[validation.Errors, string](""))(result)
|
||||
value := either.GetOrElse(reader.Of[validation.Errors](""))(result)
|
||||
assert.Equal(t, "HELLO", value)
|
||||
})
|
||||
|
||||
@@ -105,7 +105,7 @@ func TestMonadAltBasicFunctionality(t *testing.T) {
|
||||
|
||||
assert.True(t, either.IsRight(result), "should successfully decode with second codec")
|
||||
|
||||
value := either.GetOrElse(reader.Of[validation.Errors, int](0))(result)
|
||||
value := either.GetOrElse(reader.Of[validation.Errors](0))(result)
|
||||
assert.Equal(t, -5, value)
|
||||
})
|
||||
|
||||
@@ -302,19 +302,19 @@ func TestAltOperator(t *testing.T) {
|
||||
// Test with "42" - should use base codec
|
||||
result1 := pipeline.Decode("42")
|
||||
assert.True(t, either.IsRight(result1))
|
||||
value1 := either.GetOrElse(reader.Of[validation.Errors, int](0))(result1)
|
||||
value1 := either.GetOrElse(reader.Of[validation.Errors](0))(result1)
|
||||
assert.Equal(t, 42, value1)
|
||||
|
||||
// Test with "100" - should use fallback1
|
||||
result2 := pipeline.Decode("100")
|
||||
assert.True(t, either.IsRight(result2))
|
||||
value2 := either.GetOrElse(reader.Of[validation.Errors, int](0))(result2)
|
||||
value2 := either.GetOrElse(reader.Of[validation.Errors](0))(result2)
|
||||
assert.Equal(t, 100, value2)
|
||||
|
||||
// Test with "999" - should use fallback2
|
||||
result3 := pipeline.Decode("999")
|
||||
assert.True(t, either.IsRight(result3))
|
||||
value3 := either.GetOrElse(reader.Of[validation.Errors, int](0))(result3)
|
||||
value3 := either.GetOrElse(reader.Of[validation.Errors](0))(result3)
|
||||
assert.Equal(t, 999, value3)
|
||||
})
|
||||
}
|
||||
@@ -449,7 +449,7 @@ func TestAltRoundTrip(t *testing.T) {
|
||||
decodeResult := altCodec.Decode(original)
|
||||
require.True(t, either.IsRight(decodeResult))
|
||||
|
||||
decoded := either.GetOrElse(reader.Of[validation.Errors, string](""))(decodeResult)
|
||||
decoded := either.GetOrElse(reader.Of[validation.Errors](""))(decodeResult)
|
||||
|
||||
// Encode
|
||||
encoded := altCodec.Encode(decoded)
|
||||
@@ -487,7 +487,7 @@ func TestAltRoundTrip(t *testing.T) {
|
||||
decodeResult := altCodec.Decode(original)
|
||||
require.True(t, either.IsRight(decodeResult))
|
||||
|
||||
decoded := either.GetOrElse(reader.Of[validation.Errors, string](""))(decodeResult)
|
||||
decoded := either.GetOrElse(reader.Of[validation.Errors](""))(decodeResult)
|
||||
|
||||
// Encode (uses first codec's encoder, which is identity)
|
||||
encoded := altCodec.Encode(decoded)
|
||||
@@ -619,7 +619,7 @@ func TestAltMonoid(t *testing.T) {
|
||||
result := combined.Decode("input")
|
||||
|
||||
assert.True(t, either.IsRight(result))
|
||||
value := either.GetOrElse(reader.Of[validation.Errors, int](0))(result)
|
||||
value := either.GetOrElse(reader.Of[validation.Errors](0))(result)
|
||||
assert.Equal(t, 10, value, "first success should win")
|
||||
})
|
||||
|
||||
@@ -628,7 +628,7 @@ func TestAltMonoid(t *testing.T) {
|
||||
result := combined.Decode("42")
|
||||
|
||||
assert.True(t, either.IsRight(result))
|
||||
value := either.GetOrElse(reader.Of[validation.Errors, int](0))(result)
|
||||
value := either.GetOrElse(reader.Of[validation.Errors](0))(result)
|
||||
assert.Equal(t, 42, value)
|
||||
})
|
||||
|
||||
@@ -637,7 +637,7 @@ func TestAltMonoid(t *testing.T) {
|
||||
result := combined.Decode("invalid")
|
||||
|
||||
assert.True(t, either.IsRight(result))
|
||||
value := either.GetOrElse(reader.Of[validation.Errors, int](-1))(result)
|
||||
value := either.GetOrElse(reader.Of[validation.Errors](-1))(result)
|
||||
assert.Equal(t, 0, value, "should use default zero value")
|
||||
})
|
||||
})
|
||||
@@ -768,21 +768,21 @@ func TestAltMonoid(t *testing.T) {
|
||||
t.Run("uses primary when it succeeds", func(t *testing.T) {
|
||||
result := combined.Decode("primary")
|
||||
assert.True(t, either.IsRight(result))
|
||||
value := either.GetOrElse(reader.Of[validation.Errors, string](""))(result)
|
||||
value := either.GetOrElse(reader.Of[validation.Errors](""))(result)
|
||||
assert.Equal(t, "from primary", value)
|
||||
})
|
||||
|
||||
t.Run("uses secondary when primary fails", func(t *testing.T) {
|
||||
result := combined.Decode("secondary")
|
||||
assert.True(t, either.IsRight(result))
|
||||
value := either.GetOrElse(reader.Of[validation.Errors, string](""))(result)
|
||||
value := either.GetOrElse(reader.Of[validation.Errors](""))(result)
|
||||
assert.Equal(t, "from secondary", value)
|
||||
})
|
||||
|
||||
t.Run("uses default when both fail", func(t *testing.T) {
|
||||
result := combined.Decode("other")
|
||||
assert.True(t, either.IsRight(result))
|
||||
value := either.GetOrElse(reader.Of[validation.Errors, string](""))(result)
|
||||
value := either.GetOrElse(reader.Of[validation.Errors](""))(result)
|
||||
assert.Equal(t, "default", value)
|
||||
})
|
||||
})
|
||||
@@ -841,7 +841,7 @@ func TestAltMonoid(t *testing.T) {
|
||||
result := combined.Decode("input")
|
||||
|
||||
assert.True(t, either.IsRight(result))
|
||||
value := either.GetOrElse(reader.Of[validation.Errors, int](-1))(result)
|
||||
value := either.GetOrElse(reader.Of[validation.Errors](-1))(result)
|
||||
// Empty (0) comes first, so it wins
|
||||
assert.Equal(t, 0, value)
|
||||
})
|
||||
@@ -852,7 +852,7 @@ func TestAltMonoid(t *testing.T) {
|
||||
result := combined.Decode("input")
|
||||
|
||||
assert.True(t, either.IsRight(result))
|
||||
value := either.GetOrElse(reader.Of[validation.Errors, int](-1))(result)
|
||||
value := either.GetOrElse(reader.Of[validation.Errors](-1))(result)
|
||||
assert.Equal(t, 10, value, "codec1 should win")
|
||||
})
|
||||
|
||||
@@ -867,8 +867,8 @@ func TestAltMonoid(t *testing.T) {
|
||||
assert.True(t, either.IsRight(resultLeft))
|
||||
assert.True(t, either.IsRight(resultRight))
|
||||
|
||||
valueLeft := either.GetOrElse(reader.Of[validation.Errors, int](-1))(resultLeft)
|
||||
valueRight := either.GetOrElse(reader.Of[validation.Errors, int](-1))(resultRight)
|
||||
valueLeft := either.GetOrElse(reader.Of[validation.Errors](-1))(resultLeft)
|
||||
valueRight := either.GetOrElse(reader.Of[validation.Errors](-1))(resultRight)
|
||||
|
||||
// Both should return 10 (first success)
|
||||
assert.Equal(t, valueLeft, valueRight)
|
||||
|
||||
@@ -100,7 +100,7 @@ func (t *typeImpl[A, O, I]) Is(i any) Result[A] {
|
||||
// stringToInt := codec.MakeType(...) // Type[int, string, string]
|
||||
// intToPositive := codec.MakeType(...) // Type[PositiveInt, int, int]
|
||||
// composed := codec.Pipe(intToPositive)(stringToInt) // Type[PositiveInt, string, string]
|
||||
func Pipe[A, B, O, I any](ab Type[B, A, A]) func(Type[A, O, I]) Type[B, O, I] {
|
||||
func Pipe[O, I, A, B any](ab Type[B, A, A]) func(Type[A, O, I]) Type[B, O, I] {
|
||||
return func(this Type[A, O, I]) Type[B, O, I] {
|
||||
return MakeType(
|
||||
fmt.Sprintf("Pipe(%s, %s)", this.Name(), ab.Name()),
|
||||
|
||||
@@ -1748,7 +1748,7 @@ func TestFromRefinementComposition(t *testing.T) {
|
||||
positiveCodec := FromRefinement(positiveIntPrism)
|
||||
|
||||
// Compose with Int codec using Pipe
|
||||
composed := Pipe[int, int, int, any](positiveCodec)(Int())
|
||||
composed := Pipe[int, any, int, int](positiveCodec)(Int())
|
||||
|
||||
t.Run("ComposedDecodeValid", func(t *testing.T) {
|
||||
result := composed.Decode(42)
|
||||
|
||||
@@ -159,7 +159,7 @@ func TestURL(t *testing.T) {
|
||||
|
||||
func TestDate(t *testing.T) {
|
||||
|
||||
getOrElseNull := either.GetOrElse(reader.Of[validation.Errors, time.Time](time.Time{}))
|
||||
getOrElseNull := either.GetOrElse(reader.Of[validation.Errors](time.Time{}))
|
||||
|
||||
t.Run("ISO 8601 date format", func(t *testing.T) {
|
||||
dateCodec := Date("2006-01-02")
|
||||
|
||||
@@ -70,7 +70,7 @@ func TestEitherEncode(t *testing.T) {
|
||||
|
||||
// TestEitherDecode tests decoding/validation of Either values
|
||||
func TestEitherDecode(t *testing.T) {
|
||||
getOrElseNull := either.GetOrElse(reader.Of[validation.Errors, either.Either[string, int]](either.Left[int]("")))
|
||||
getOrElseNull := either.GetOrElse(reader.Of[validation.Errors](either.Left[int]("")))
|
||||
|
||||
// Create codecs that both work with string input
|
||||
stringCodec := Id[string]()
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestFromReaderResult_Success(t *testing.T) {
|
||||
}
|
||||
|
||||
// Convert to Validate
|
||||
validator := FromReaderResult[int, string](successRR)
|
||||
validator := FromReaderResult(successRR)
|
||||
|
||||
// Execute the validator
|
||||
validationResult := validator(42)(nil)
|
||||
@@ -53,7 +53,7 @@ func TestFromReaderResult_Success(t *testing.T) {
|
||||
parseIntRR := result.Eitherize1(strconv.Atoi)
|
||||
|
||||
// Convert to Validate
|
||||
validator := FromReaderResult[string, int](parseIntRR)
|
||||
validator := FromReaderResult(parseIntRR)
|
||||
|
||||
// Execute with valid input
|
||||
validationResult := validator("123")(nil)
|
||||
@@ -74,7 +74,7 @@ func TestFromReaderResult_Success(t *testing.T) {
|
||||
}
|
||||
|
||||
// Convert to Validate
|
||||
validator := FromReaderResult[string, User](createUserRR)
|
||||
validator := FromReaderResult(createUserRR)
|
||||
|
||||
// Execute the validator
|
||||
validationResult := validator("Alice")(nil)
|
||||
@@ -88,7 +88,7 @@ func TestFromReaderResult_Success(t *testing.T) {
|
||||
return result.Of(input * 2)
|
||||
}
|
||||
|
||||
validator := FromReaderResult[int, int](successRR)
|
||||
validator := FromReaderResult(successRR)
|
||||
validationResult := validator(21)(Context{})
|
||||
|
||||
assert.Equal(t, validation.Success(42), validationResult)
|
||||
@@ -99,7 +99,7 @@ func TestFromReaderResult_Success(t *testing.T) {
|
||||
return result.Of(input + " processed")
|
||||
}
|
||||
|
||||
validator := FromReaderResult[string, string](successRR)
|
||||
validator := FromReaderResult(successRR)
|
||||
ctx := Context{
|
||||
{Key: "user", Type: "User"},
|
||||
{Key: "name", Type: "string"},
|
||||
@@ -122,7 +122,7 @@ func TestFromReaderResult_Failure(t *testing.T) {
|
||||
}
|
||||
|
||||
// Convert to Validate
|
||||
validator := FromReaderResult[string, int](failureRR)
|
||||
validator := FromReaderResult(failureRR)
|
||||
|
||||
// Execute the validator
|
||||
validationResult := validator("invalid")(nil)
|
||||
@@ -147,7 +147,7 @@ func TestFromReaderResult_Failure(t *testing.T) {
|
||||
return result.Left[string](originalErr)
|
||||
}
|
||||
|
||||
validator := FromReaderResult[int, string](failureRR)
|
||||
validator := FromReaderResult(failureRR)
|
||||
validationResult := validator(42)(nil)
|
||||
|
||||
assert.True(t, either.IsLeft(validationResult))
|
||||
@@ -166,7 +166,7 @@ func TestFromReaderResult_Failure(t *testing.T) {
|
||||
return result.Left[int](errors.New("conversion failed"))
|
||||
}
|
||||
|
||||
validator := FromReaderResult[string, int](failureRR)
|
||||
validator := FromReaderResult(failureRR)
|
||||
ctx := Context{
|
||||
{Key: "user", Type: "User"},
|
||||
{Key: "age", Type: "int"},
|
||||
@@ -213,7 +213,7 @@ func TestFromReaderResult_Failure(t *testing.T) {
|
||||
return result.Left[int](tc.err)
|
||||
}
|
||||
|
||||
validator := FromReaderResult[string, int](failureRR)
|
||||
validator := FromReaderResult(failureRR)
|
||||
validationResult := validator(tc.input)(nil)
|
||||
|
||||
assert.True(t, either.IsLeft(validationResult))
|
||||
@@ -251,7 +251,7 @@ func TestFromReaderResult_Integration(t *testing.T) {
|
||||
|
||||
// Combine validators
|
||||
validator := F.Pipe1(
|
||||
FromReaderResult[string, int](parseIntRR),
|
||||
FromReaderResult(parseIntRR),
|
||||
Chain(validatePositive),
|
||||
)
|
||||
|
||||
@@ -273,8 +273,8 @@ func TestFromReaderResult_Integration(t *testing.T) {
|
||||
|
||||
// Convert and map to double the value
|
||||
validator := F.Pipe1(
|
||||
FromReaderResult[string, int](parseIntRR),
|
||||
Map[string, int, int](func(n int) int { return n * 2 }),
|
||||
FromReaderResult(parseIntRR),
|
||||
Map[string](func(n int) int { return n * 2 }),
|
||||
)
|
||||
|
||||
validationResult := validator("21")(nil)
|
||||
@@ -294,7 +294,7 @@ func TestFromReaderResult_Integration(t *testing.T) {
|
||||
Bind(func(p int) func(State) State {
|
||||
return func(s State) State { s.parsed = p; return s }
|
||||
}, func(s State) Validate[string, int] {
|
||||
return FromReaderResult[string, int](parseIntRR)
|
||||
return FromReaderResult(parseIntRR)
|
||||
}),
|
||||
Let[string](func(v bool) func(State) State {
|
||||
return func(s State) State { s.valid = v; return s }
|
||||
@@ -315,7 +315,7 @@ func TestFromReaderResult_EdgeCases(t *testing.T) {
|
||||
return result.Of(input)
|
||||
}
|
||||
|
||||
validator := FromReaderResult[int, int](successRR)
|
||||
validator := FromReaderResult(successRR)
|
||||
validationResult := validator(42)(nil)
|
||||
|
||||
assert.True(t, either.IsRight(validationResult))
|
||||
@@ -326,7 +326,7 @@ func TestFromReaderResult_EdgeCases(t *testing.T) {
|
||||
return result.Of(input)
|
||||
}
|
||||
|
||||
validator := FromReaderResult[string, string](identityRR)
|
||||
validator := FromReaderResult(identityRR)
|
||||
validationResult := validator("")(nil)
|
||||
|
||||
assert.Equal(t, validation.Success(""), validationResult)
|
||||
@@ -337,7 +337,7 @@ func TestFromReaderResult_EdgeCases(t *testing.T) {
|
||||
return result.Of(input)
|
||||
}
|
||||
|
||||
validator := FromReaderResult[int, int](identityRR)
|
||||
validator := FromReaderResult(identityRR)
|
||||
validationResult := validator(0)(nil)
|
||||
|
||||
assert.Equal(t, validation.Success(0), validationResult)
|
||||
@@ -352,7 +352,7 @@ func TestFromReaderResult_EdgeCases(t *testing.T) {
|
||||
return result.Of(&Data{Value: input})
|
||||
}
|
||||
|
||||
validator := FromReaderResult[int, *Data](createDataRR)
|
||||
validator := FromReaderResult(createDataRR)
|
||||
validationResult := validator(42)(nil)
|
||||
|
||||
assert.True(t, either.IsRight(validationResult))
|
||||
@@ -372,7 +372,7 @@ func TestFromReaderResult_EdgeCases(t *testing.T) {
|
||||
return result.Of([]string{input, input})
|
||||
}
|
||||
|
||||
validator := FromReaderResult[string, []string](splitRR)
|
||||
validator := FromReaderResult(splitRR)
|
||||
validationResult := validator("test")(nil)
|
||||
|
||||
assert.Equal(t, validation.Success([]string{"test", "test"}), validationResult)
|
||||
@@ -383,7 +383,7 @@ func TestFromReaderResult_EdgeCases(t *testing.T) {
|
||||
return result.Of(map[string]int{input: len(input)})
|
||||
}
|
||||
|
||||
validator := FromReaderResult[string, map[string]int](createMapRR)
|
||||
validator := FromReaderResult(createMapRR)
|
||||
validationResult := validator("hello")(nil)
|
||||
|
||||
assert.Equal(t, validation.Success(map[string]int{"hello": 5}), validationResult)
|
||||
@@ -398,7 +398,7 @@ func TestFromReaderResult_TypeSafety(t *testing.T) {
|
||||
return result.Of(fmt.Sprintf("%d", input))
|
||||
}
|
||||
|
||||
validator := FromReaderResult[int, string](intToStringRR)
|
||||
validator := FromReaderResult(intToStringRR)
|
||||
|
||||
// This should compile and work correctly
|
||||
validationResult := validator(42)(nil)
|
||||
@@ -409,7 +409,7 @@ func TestFromReaderResult_TypeSafety(t *testing.T) {
|
||||
// This test verifies that the output type is preserved
|
||||
stringToIntRR := result.Eitherize1(strconv.Atoi)
|
||||
|
||||
validator := FromReaderResult[string, int](stringToIntRR)
|
||||
validator := FromReaderResult(stringToIntRR)
|
||||
validationResult := validator("42")(nil)
|
||||
|
||||
// The result should be Validation[int]
|
||||
@@ -428,7 +428,7 @@ func TestFromReaderResult_TypeSafety(t *testing.T) {
|
||||
return Output{Result: val}, nil
|
||||
})
|
||||
|
||||
validator := FromReaderResult[Input, Output](transformRR)
|
||||
validator := FromReaderResult(transformRR)
|
||||
validationResult := validator(Input{Value: "42"})(nil)
|
||||
|
||||
assert.Equal(t, validation.Success(Output{Result: 42}), validationResult)
|
||||
@@ -441,7 +441,7 @@ func BenchmarkFromReaderResult_Success(b *testing.B) {
|
||||
return result.Of(input * 2)
|
||||
}
|
||||
|
||||
validator := FromReaderResult[int, int](successRR)
|
||||
validator := FromReaderResult(successRR)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -455,7 +455,7 @@ func BenchmarkFromReaderResult_Failure(b *testing.B) {
|
||||
return result.Left[int](errors.New("error"))
|
||||
}
|
||||
|
||||
validator := FromReaderResult[int, int](failureRR)
|
||||
validator := FromReaderResult(failureRR)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -469,7 +469,7 @@ func BenchmarkFromReaderResult_WithContext(b *testing.B) {
|
||||
return result.Of(input * 2)
|
||||
}
|
||||
|
||||
validator := FromReaderResult[int, int](successRR)
|
||||
validator := FromReaderResult(successRR)
|
||||
ctx := Context{
|
||||
{Key: "user", Type: "User"},
|
||||
{Key: "age", Type: "int"},
|
||||
|
||||
@@ -26,7 +26,7 @@ func TestMonadChainLeft(t *testing.T) {
|
||||
handler := func(errs Errors) Validate[string, int] {
|
||||
for _, err := range errs {
|
||||
if err.Messsage == "validation failed" {
|
||||
return Of[string, int](0) // recover with default
|
||||
return Of[string](0) // recover with default
|
||||
}
|
||||
}
|
||||
return func(input string) Reader[Context, Validation[int]] {
|
||||
@@ -43,7 +43,7 @@ func TestMonadChainLeft(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("preserves success values unchanged", func(t *testing.T) {
|
||||
successValidator := Of[string, int](42)
|
||||
successValidator := Of[string](42)
|
||||
|
||||
handler := func(errs Errors) Validate[string, int] {
|
||||
return func(input string) Reader[Context, Validation[int]] {
|
||||
@@ -145,7 +145,7 @@ func TestMonadChainLeft(t *testing.T) {
|
||||
}
|
||||
|
||||
handler := func(errs Errors) Validate[Config, string] {
|
||||
return Of[Config, string]("default-value")
|
||||
return Of[Config]("default-value")
|
||||
}
|
||||
|
||||
validator := MonadChainLeft(failingValidator, handler)
|
||||
@@ -194,7 +194,7 @@ func TestMonadChainLeft(t *testing.T) {
|
||||
}
|
||||
|
||||
handler := func(errs Errors) Validate[string, int] {
|
||||
return Of[string, int](42)
|
||||
return Of[string](42)
|
||||
}
|
||||
|
||||
// MonadChainLeft - direct application
|
||||
@@ -229,7 +229,7 @@ func TestMonadChainLeft(t *testing.T) {
|
||||
// Check if we can recover
|
||||
for _, err := range errs {
|
||||
if err.Messsage == "error1" {
|
||||
return Of[string, int](100) // recover
|
||||
return Of[string](100) // recover
|
||||
}
|
||||
}
|
||||
return func(input string) Reader[Context, Validation[int]] {
|
||||
@@ -248,12 +248,12 @@ func TestMonadChainLeft(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("does not call handler on success", func(t *testing.T) {
|
||||
successValidator := Of[string, int](42)
|
||||
successValidator := Of[string](42)
|
||||
handlerCalled := false
|
||||
|
||||
handler := func(errs Errors) Validate[string, int] {
|
||||
handlerCalled = true
|
||||
return Of[string, int](0)
|
||||
return Of[string](0)
|
||||
}
|
||||
|
||||
validator := MonadChainLeft(successValidator, handler)
|
||||
@@ -267,9 +267,9 @@ func TestMonadChainLeft(t *testing.T) {
|
||||
// TestMonadAlt tests the MonadAlt function
|
||||
func TestMonadAlt(t *testing.T) {
|
||||
t.Run("returns first validator when it succeeds", func(t *testing.T) {
|
||||
validator1 := Of[string, int](42)
|
||||
validator1 := Of[string](42)
|
||||
validator2 := func() Validate[string, int] {
|
||||
return Of[string, int](100)
|
||||
return Of[string](100)
|
||||
}
|
||||
|
||||
result := MonadAlt(validator1, validator2)("input")(nil)
|
||||
@@ -285,7 +285,7 @@ func TestMonadAlt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
fallback := func() Validate[string, int] {
|
||||
return Of[string, int](42)
|
||||
return Of[string](42)
|
||||
}
|
||||
|
||||
result := MonadAlt(failing, fallback)("input")(nil)
|
||||
@@ -328,11 +328,11 @@ func TestMonadAlt(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("does not evaluate second validator when first succeeds", func(t *testing.T) {
|
||||
validator1 := Of[string, int](42)
|
||||
validator1 := Of[string](42)
|
||||
evaluated := false
|
||||
validator2 := func() Validate[string, int] {
|
||||
evaluated = true
|
||||
return Of[string, int](100)
|
||||
return Of[string](100)
|
||||
}
|
||||
|
||||
result := MonadAlt(validator1, validator2)("input")(nil)
|
||||
@@ -349,7 +349,7 @@ func TestMonadAlt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
fallback := func() Validate[string, string] {
|
||||
return Of[string, string]("fallback")
|
||||
return Of[string]("fallback")
|
||||
}
|
||||
|
||||
result := MonadAlt(failing, fallback)("input")(nil)
|
||||
@@ -374,7 +374,7 @@ func TestMonadAlt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
succeeding := func() Validate[string, int] {
|
||||
return Of[string, int](42)
|
||||
return Of[string](42)
|
||||
}
|
||||
|
||||
// Chain: try failing1, then failing2, then succeeding
|
||||
@@ -395,7 +395,7 @@ func TestMonadAlt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
fallback := func() Validate[Config, string] {
|
||||
return Of[Config, string]("default")
|
||||
return Of[Config]("default")
|
||||
}
|
||||
|
||||
result := MonadAlt(failing, fallback)(Config{Port: 9999})(nil)
|
||||
@@ -458,9 +458,9 @@ func TestMonadAlt(t *testing.T) {
|
||||
// TestAlt tests the Alt function
|
||||
func TestAlt(t *testing.T) {
|
||||
t.Run("returns first validator when it succeeds", func(t *testing.T) {
|
||||
validator1 := Of[string, int](42)
|
||||
validator1 := Of[string](42)
|
||||
validator2 := func() Validate[string, int] {
|
||||
return Of[string, int](100)
|
||||
return Of[string](100)
|
||||
}
|
||||
|
||||
withAlt := Alt(validator2)
|
||||
@@ -477,7 +477,7 @@ func TestAlt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
fallback := func() Validate[string, int] {
|
||||
return Of[string, int](42)
|
||||
return Of[string](42)
|
||||
}
|
||||
|
||||
withAlt := Alt(fallback)
|
||||
@@ -522,11 +522,11 @@ func TestAlt(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("does not evaluate second validator when first succeeds", func(t *testing.T) {
|
||||
validator1 := Of[string, int](42)
|
||||
validator1 := Of[string](42)
|
||||
evaluated := false
|
||||
validator2 := func() Validate[string, int] {
|
||||
evaluated = true
|
||||
return Of[string, int](100)
|
||||
return Of[string](100)
|
||||
}
|
||||
|
||||
withAlt := Alt(validator2)
|
||||
@@ -553,7 +553,7 @@ func TestAlt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
succeeding := func() Validate[string, int] {
|
||||
return Of[string, int](42)
|
||||
return Of[string](42)
|
||||
}
|
||||
|
||||
// Use F.Pipe to chain alternatives
|
||||
@@ -576,7 +576,7 @@ func TestAlt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
fallback := func() Validate[string, int] {
|
||||
return Of[string, int](42)
|
||||
return Of[string](42)
|
||||
}
|
||||
|
||||
// Alt - curried for pipelines
|
||||
@@ -592,9 +592,9 @@ func TestAlt(t *testing.T) {
|
||||
// TestMonadAltAndAltEquivalence tests that MonadAlt and Alt are equivalent
|
||||
func TestMonadAltAndAltEquivalence(t *testing.T) {
|
||||
t.Run("both produce same results for success", func(t *testing.T) {
|
||||
validator1 := Of[string, int](42)
|
||||
validator1 := Of[string](42)
|
||||
validator2 := func() Validate[string, int] {
|
||||
return Of[string, int](100)
|
||||
return Of[string](100)
|
||||
}
|
||||
|
||||
resultMonadAlt := MonadAlt(validator1, validator2)("input")(nil)
|
||||
@@ -612,7 +612,7 @@ func TestMonadAltAndAltEquivalence(t *testing.T) {
|
||||
}
|
||||
}
|
||||
fallback := func() Validate[string, int] {
|
||||
return Of[string, int](42)
|
||||
return Of[string](42)
|
||||
}
|
||||
|
||||
resultMonadAlt := MonadAlt(failing, fallback)("input")(nil)
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
// TestAlternativeMonoid tests the AlternativeMonoid function
|
||||
func TestAlternativeMonoid(t *testing.T) {
|
||||
t.Run("with string monoid", func(t *testing.T) {
|
||||
m := AlternativeMonoid[string, string](S.Monoid)
|
||||
m := AlternativeMonoid[string](S.Monoid)
|
||||
|
||||
t.Run("empty returns validator that succeeds with empty string", func(t *testing.T) {
|
||||
empty := m.Empty()
|
||||
@@ -25,8 +25,8 @@ func TestAlternativeMonoid(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("concat combines successful validators using monoid", func(t *testing.T) {
|
||||
validator1 := Of[string, string]("Hello")
|
||||
validator2 := Of[string, string](" World")
|
||||
validator1 := Of[string]("Hello")
|
||||
validator2 := Of[string](" World")
|
||||
|
||||
combined := m.Concat(validator1, validator2)
|
||||
result := combined("input")(nil)
|
||||
@@ -42,7 +42,7 @@ func TestAlternativeMonoid(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
succeeding := Of[string, string]("fallback")
|
||||
succeeding := Of[string]("fallback")
|
||||
|
||||
combined := m.Concat(failing, succeeding)
|
||||
result := combined("input")(nil)
|
||||
@@ -85,7 +85,7 @@ func TestAlternativeMonoid(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("concat with empty preserves validator", func(t *testing.T) {
|
||||
validator := Of[string, string]("test")
|
||||
validator := Of[string]("test")
|
||||
empty := m.Empty()
|
||||
|
||||
result1 := m.Concat(validator, empty)("input")(nil)
|
||||
@@ -110,7 +110,7 @@ func TestAlternativeMonoid(t *testing.T) {
|
||||
func(a, b int) int { return a + b },
|
||||
0,
|
||||
)
|
||||
m := AlternativeMonoid[string, int](intMonoid)
|
||||
m := AlternativeMonoid[string](intMonoid)
|
||||
|
||||
t.Run("empty returns validator with zero", func(t *testing.T) {
|
||||
empty := m.Empty()
|
||||
@@ -124,8 +124,8 @@ func TestAlternativeMonoid(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("concat combines decoded values when both succeed", func(t *testing.T) {
|
||||
validator1 := Of[string, int](10)
|
||||
validator2 := Of[string, int](32)
|
||||
validator1 := Of[string](10)
|
||||
validator2 := Of[string](32)
|
||||
|
||||
combined := m.Concat(validator1, validator2)
|
||||
result := combined("input")(nil)
|
||||
@@ -145,7 +145,7 @@ func TestAlternativeMonoid(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
succeeding := Of[string, int](42)
|
||||
succeeding := Of[string](42)
|
||||
|
||||
combined := m.Concat(failing, succeeding)
|
||||
result := combined("input")(nil)
|
||||
@@ -158,10 +158,10 @@ func TestAlternativeMonoid(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("multiple concat operations", func(t *testing.T) {
|
||||
validator1 := Of[string, int](1)
|
||||
validator2 := Of[string, int](2)
|
||||
validator3 := Of[string, int](3)
|
||||
validator4 := Of[string, int](4)
|
||||
validator1 := Of[string](1)
|
||||
validator2 := Of[string](2)
|
||||
validator3 := Of[string](3)
|
||||
validator4 := Of[string](4)
|
||||
|
||||
combined := m.Concat(m.Concat(m.Concat(validator1, validator2), validator3), validator4)
|
||||
result := combined("input")(nil)
|
||||
@@ -175,11 +175,11 @@ func TestAlternativeMonoid(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("satisfies monoid laws", func(t *testing.T) {
|
||||
m := AlternativeMonoid[string, string](S.Monoid)
|
||||
m := AlternativeMonoid[string](S.Monoid)
|
||||
|
||||
validator1 := Of[string, string]("a")
|
||||
validator2 := Of[string, string]("b")
|
||||
validator3 := Of[string, string]("c")
|
||||
validator1 := Of[string]("a")
|
||||
validator2 := Of[string]("b")
|
||||
validator3 := Of[string]("c")
|
||||
|
||||
t.Run("left identity", func(t *testing.T) {
|
||||
result := m.Concat(m.Empty(), validator1)("input")(nil)
|
||||
@@ -222,7 +222,7 @@ func TestAlternativeMonoid(t *testing.T) {
|
||||
func TestAltMonoid(t *testing.T) {
|
||||
t.Run("with default value as zero", func(t *testing.T) {
|
||||
m := AltMonoid(func() Validate[string, int] {
|
||||
return Of[string, int](0)
|
||||
return Of[string](0)
|
||||
})
|
||||
|
||||
t.Run("empty returns the provided zero validator", func(t *testing.T) {
|
||||
@@ -233,8 +233,8 @@ func TestAltMonoid(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("concat returns first validator when it succeeds", func(t *testing.T) {
|
||||
validator1 := Of[string, int](42)
|
||||
validator2 := Of[string, int](100)
|
||||
validator1 := Of[string](42)
|
||||
validator2 := Of[string](100)
|
||||
|
||||
combined := m.Concat(validator1, validator2)
|
||||
result := combined("input")(nil)
|
||||
@@ -250,7 +250,7 @@ func TestAltMonoid(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
succeeding := Of[string, int](42)
|
||||
succeeding := Of[string](42)
|
||||
|
||||
combined := m.Concat(failing, succeeding)
|
||||
result := combined("input")(nil)
|
||||
@@ -341,7 +341,7 @@ func TestAltMonoid(t *testing.T) {
|
||||
|
||||
t.Run("chaining multiple fallbacks", func(t *testing.T) {
|
||||
m := AltMonoid(func() Validate[string, string] {
|
||||
return Of[string, string]("default")
|
||||
return Of[string]("default")
|
||||
})
|
||||
|
||||
primary := func(input string) Reader[Context, Validation[string]] {
|
||||
@@ -358,7 +358,7 @@ func TestAltMonoid(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
tertiary := Of[string, string]("tertiary value")
|
||||
tertiary := Of[string]("tertiary value")
|
||||
|
||||
combined := m.Concat(m.Concat(primary, secondary), tertiary)
|
||||
result := combined("input")(nil)
|
||||
@@ -369,14 +369,14 @@ func TestAltMonoid(t *testing.T) {
|
||||
t.Run("difference from AlternativeMonoid", func(t *testing.T) {
|
||||
// AltMonoid - first success wins
|
||||
altM := AltMonoid(func() Validate[string, int] {
|
||||
return Of[string, int](0)
|
||||
return Of[string](0)
|
||||
})
|
||||
|
||||
// AlternativeMonoid - combines successes
|
||||
altMonoid := AlternativeMonoid[string, int](N.MonoidSum[int]())
|
||||
altMonoid := AlternativeMonoid[string](N.MonoidSum[int]())
|
||||
|
||||
validator1 := Of[string, int](10)
|
||||
validator2 := Of[string, int](32)
|
||||
validator1 := Of[string](10)
|
||||
validator2 := Of[string](32)
|
||||
|
||||
// AltMonoid: returns first success (10)
|
||||
result1 := altM.Concat(validator1, validator2)("input")(nil)
|
||||
|
||||
@@ -1405,7 +1405,7 @@ func TestFromResult(t *testing.T) {
|
||||
t.Run("extract from successful result", func(t *testing.T) {
|
||||
prism := FromResult[int]()
|
||||
|
||||
success := result.Of[int](42)
|
||||
success := result.Of(42)
|
||||
extracted := prism.GetOption(success)
|
||||
|
||||
assert.True(t, O.IsSome(extracted))
|
||||
@@ -1435,7 +1435,7 @@ func TestFromResult(t *testing.T) {
|
||||
t.Run("works with string type", func(t *testing.T) {
|
||||
prism := FromResult[string]()
|
||||
|
||||
success := result.Of[string]("hello")
|
||||
success := result.Of("hello")
|
||||
extracted := prism.GetOption(success)
|
||||
|
||||
assert.True(t, O.IsSome(extracted))
|
||||
@@ -1451,7 +1451,7 @@ func TestFromResult(t *testing.T) {
|
||||
prism := FromResult[Person]()
|
||||
|
||||
person := Person{Name: "Alice", Age: 30}
|
||||
success := result.Of[Person](person)
|
||||
success := result.Of(person)
|
||||
extracted := prism.GetOption(success)
|
||||
|
||||
assert.True(t, O.IsSome(extracted))
|
||||
@@ -1465,9 +1465,9 @@ func TestFromResult(t *testing.T) {
|
||||
func TestFromResultWithSet(t *testing.T) {
|
||||
t.Run("set on successful result", func(t *testing.T) {
|
||||
prism := FromResult[int]()
|
||||
setter := Set[result.Result[int], int](200)
|
||||
setter := Set[result.Result[int]](200)
|
||||
|
||||
success := result.Of[int](42)
|
||||
success := result.Of(42)
|
||||
updated := setter(prism)(success)
|
||||
|
||||
// Verify the value was updated
|
||||
@@ -1478,7 +1478,7 @@ func TestFromResultWithSet(t *testing.T) {
|
||||
|
||||
t.Run("set on error result leaves it unchanged", func(t *testing.T) {
|
||||
prism := FromResult[int]()
|
||||
setter := Set[result.Result[int], int](200)
|
||||
setter := Set[result.Result[int]](200)
|
||||
|
||||
failure := E.Left[int](errors.New("test error"))
|
||||
updated := setter(prism)(failure)
|
||||
@@ -1527,13 +1527,13 @@ func TestFromResultComposition(t *testing.T) {
|
||||
composed := Compose[result.Result[int]](positivePrism)(FromResult[int]())
|
||||
|
||||
// Test with positive number
|
||||
success := result.Of[int](42)
|
||||
success := result.Of(42)
|
||||
extracted := composed.GetOption(success)
|
||||
assert.True(t, O.IsSome(extracted))
|
||||
assert.Equal(t, 42, O.GetOrElse(F.Constant(-1))(extracted))
|
||||
|
||||
// Test with negative number
|
||||
negativeSuccess := result.Of[int](-5)
|
||||
negativeSuccess := result.Of(-5)
|
||||
extracted = composed.GetOption(negativeSuccess)
|
||||
assert.True(t, O.IsNone(extracted))
|
||||
|
||||
@@ -1705,7 +1705,7 @@ func TestParseJSONWithSet(t *testing.T) {
|
||||
originalJSON := []byte(`{"name":"Alice","age":30}`)
|
||||
newPerson := Person{Name: "Bob", Age: 25}
|
||||
|
||||
setter := Set[[]byte, Person](newPerson)
|
||||
setter := Set[[]byte](newPerson)
|
||||
updatedJSON := setter(prism)(originalJSON)
|
||||
|
||||
// Parse the updated JSON
|
||||
@@ -1722,7 +1722,7 @@ func TestParseJSONWithSet(t *testing.T) {
|
||||
invalidJSON := []byte(`{invalid}`)
|
||||
newPerson := Person{Name: "Charlie", Age: 35}
|
||||
|
||||
setter := Set[[]byte, Person](newPerson)
|
||||
setter := Set[[]byte](newPerson)
|
||||
result := setter(prism)(invalidJSON)
|
||||
|
||||
// Should return original unchanged since it couldn't be parsed
|
||||
|
||||
@@ -630,7 +630,7 @@ func TestLocalIOK(t *testing.T) {
|
||||
}
|
||||
|
||||
// Compose using LocalIOK
|
||||
adapted := LocalIOK[string, SimpleConfig, string](loadConfig)(useConfig)
|
||||
adapted := LocalIOK[string](loadConfig)(useConfig)
|
||||
result := adapted("config.json")()
|
||||
|
||||
assert.Equal(t, "localhost:8080", result)
|
||||
@@ -650,7 +650,7 @@ func TestLocalIOK(t *testing.T) {
|
||||
return io.Of(fmt.Sprintf("Processed: %d", n))
|
||||
}
|
||||
|
||||
adapted := LocalIOK[string, int, string](loadData)(processData)
|
||||
adapted := LocalIOK[string](loadData)(processData)
|
||||
result := adapted("test")()
|
||||
|
||||
assert.Equal(t, "Processed: 40", result)
|
||||
@@ -679,8 +679,8 @@ func TestLocalIOK(t *testing.T) {
|
||||
}
|
||||
|
||||
// Compose transformations
|
||||
step1 := LocalIOK[string, UserEnv, int](loadUser)(formatUser)
|
||||
step2 := LocalIOK[string, int, string](parseID)(step1)
|
||||
step1 := LocalIOK[string](loadUser)(formatUser)
|
||||
step2 := LocalIOK[string](parseID)(step1)
|
||||
|
||||
result := step2("42")()
|
||||
assert.Equal(t, "User ID: 42", result)
|
||||
@@ -704,7 +704,7 @@ func TestLocalIOK(t *testing.T) {
|
||||
return io.Of(fmt.Sprintf("Connected to %s:%d", cfg.Host, cfg.Port))
|
||||
}
|
||||
|
||||
adapted := LocalIOK[string, DatabaseConfig, AppConfig](extractDB)(connectDB)
|
||||
adapted := LocalIOK[string](extractDB)(connectDB)
|
||||
result := adapted(AppConfig{
|
||||
Database: DatabaseConfig{Host: "", Port: 5432},
|
||||
})()
|
||||
@@ -735,8 +735,8 @@ func TestLocalIOK(t *testing.T) {
|
||||
}
|
||||
|
||||
// Compose the pipeline
|
||||
step1 := LocalIOK[string, SimpleConfig, string](parseConfig)(useConfig)
|
||||
step2 := LocalIOK[string, string, ConfigFile](readFile)(step1)
|
||||
step1 := LocalIOK[string](parseConfig)(useConfig)
|
||||
step2 := LocalIOK[string](readFile)(step1)
|
||||
|
||||
result := step2(ConfigFile{Path: "app.json"})()
|
||||
assert.Equal(t, "Using example.com:9000", result)
|
||||
|
||||
@@ -149,7 +149,7 @@ func TestLocalIOK(t *testing.T) {
|
||||
}
|
||||
|
||||
// Compose using LocalIOK
|
||||
adapted := LocalIOK[string, string, SimpleConfig, string](loadConfig)(useConfig)
|
||||
adapted := LocalIOK[string, string](loadConfig)(useConfig)
|
||||
result := adapted("config.json")()
|
||||
|
||||
assert.Equal(t, E.Of[string]("Port: 8080"), result)
|
||||
@@ -169,7 +169,7 @@ func TestLocalIOK(t *testing.T) {
|
||||
return IOE.Of[string]("Processed: " + strconv.Itoa(n))
|
||||
}
|
||||
|
||||
adapted := LocalIOK[string, string, int, string](loadData)(processData)
|
||||
adapted := LocalIOK[string, string](loadData)(processData)
|
||||
result := adapted("test")()
|
||||
|
||||
assert.Equal(t, E.Of[string]("Processed: 40"), result)
|
||||
@@ -188,7 +188,7 @@ func TestLocalIOK(t *testing.T) {
|
||||
return IOE.Left[string]("operation failed")
|
||||
}
|
||||
|
||||
adapted := LocalIOK[string, string, SimpleConfig, string](loadConfig)(failingOperation)
|
||||
adapted := LocalIOK[string, string](loadConfig)(failingOperation)
|
||||
result := adapted("config.json")()
|
||||
|
||||
assert.Equal(t, E.Left[string]("operation failed"), result)
|
||||
@@ -216,8 +216,8 @@ func TestLocalIOK(t *testing.T) {
|
||||
}
|
||||
|
||||
// Compose transformations
|
||||
step1 := LocalIOK[string, string, SimpleConfig, int](loadConfig)(formatConfig)
|
||||
step2 := LocalIOK[string, string, int, string](parseID)(step1)
|
||||
step1 := LocalIOK[string, string](loadConfig)(formatConfig)
|
||||
step2 := LocalIOK[string, string](parseID)(step1)
|
||||
|
||||
result := step2("42")()
|
||||
assert.Equal(t, E.Of[string]("Port: 8042"), result)
|
||||
@@ -243,7 +243,7 @@ func TestLocalIOEitherK(t *testing.T) {
|
||||
}
|
||||
|
||||
// Compose using LocalIOEitherK
|
||||
adapted := LocalIOEitherK[string, SimpleConfig, string, string](loadConfig)(useConfig)
|
||||
adapted := LocalIOEitherK[string](loadConfig)(useConfig)
|
||||
|
||||
// Success case
|
||||
result := adapted("config.json")()
|
||||
@@ -265,7 +265,7 @@ func TestLocalIOEitherK(t *testing.T) {
|
||||
return IOE.Of[string]("Port: " + strconv.Itoa(cfg.Port))
|
||||
}
|
||||
|
||||
adapted := LocalIOEitherK[string, SimpleConfig, string, string](loadConfig)(useConfig)
|
||||
adapted := LocalIOEitherK[string](loadConfig)(useConfig)
|
||||
result := adapted("missing.json")()
|
||||
|
||||
// Error from loadConfig should propagate
|
||||
@@ -282,7 +282,7 @@ func TestLocalIOEitherK(t *testing.T) {
|
||||
return IOE.Left[string]("operation failed")
|
||||
}
|
||||
|
||||
adapted := LocalIOEitherK[string, SimpleConfig, string, string](loadConfig)(failingOperation)
|
||||
adapted := LocalIOEitherK[string](loadConfig)(failingOperation)
|
||||
result := adapted("config.json")()
|
||||
|
||||
// Error from ReaderIOEither should propagate
|
||||
@@ -317,8 +317,8 @@ func TestLocalIOEitherK(t *testing.T) {
|
||||
}
|
||||
|
||||
// Compose transformations
|
||||
step1 := LocalIOEitherK[string, SimpleConfig, int, string](loadConfig)(formatConfig)
|
||||
step2 := LocalIOEitherK[string, int, string, string](parseID)(step1)
|
||||
step1 := LocalIOEitherK[string](loadConfig)(formatConfig)
|
||||
step2 := LocalIOEitherK[string](parseID)(step1)
|
||||
|
||||
// Success case
|
||||
result := step2("42")()
|
||||
@@ -364,8 +364,8 @@ func TestLocalIOEitherK(t *testing.T) {
|
||||
}
|
||||
|
||||
// Compose the pipeline
|
||||
step1 := LocalIOEitherK[string, SimpleConfig, string, string](parseConfig)(useConfig)
|
||||
step2 := LocalIOEitherK[string, string, ConfigFile, string](readFile)(step1)
|
||||
step1 := LocalIOEitherK[string](parseConfig)(useConfig)
|
||||
step2 := LocalIOEitherK[string](readFile)(step1)
|
||||
|
||||
// Success case
|
||||
result := step2(ConfigFile{Path: "app.json"})()
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestTraverseArray_AllSuccess(t *testing.T) {
|
||||
}
|
||||
|
||||
input := []int{1, 2, 3, 4, 5}
|
||||
result := TraverseArray[context.Context](double)(input)
|
||||
result := TraverseArray(double)(input)
|
||||
|
||||
expected := O.Of([]int{2, 4, 6, 8, 10})
|
||||
assert.Equal(t, expected, result(context.Background())())
|
||||
@@ -48,7 +48,7 @@ func TestTraverseArray_OneFailure(t *testing.T) {
|
||||
}
|
||||
|
||||
input := []int{1, 2, 3, 4, 5}
|
||||
result := TraverseArray[context.Context](failOnThree)(input)
|
||||
result := TraverseArray(failOnThree)(input)
|
||||
|
||||
expected := O.None[[]int]()
|
||||
assert.Equal(t, expected, result(context.Background())())
|
||||
@@ -61,7 +61,7 @@ func TestTraverseArray_EmptyArray(t *testing.T) {
|
||||
}
|
||||
|
||||
input := []int{}
|
||||
result := TraverseArray[context.Context](double)(input)
|
||||
result := TraverseArray(double)(input)
|
||||
|
||||
expected := O.Of([]int{})
|
||||
assert.Equal(t, expected, result(context.Background())())
|
||||
@@ -82,7 +82,7 @@ func TestTraverseArray_WithEnvironment(t *testing.T) {
|
||||
}
|
||||
|
||||
input := []int{1, 2, 3}
|
||||
result := TraverseArray[Config](multiply)(input)
|
||||
result := TraverseArray(multiply)(input)
|
||||
|
||||
cfg := Config{Multiplier: 10}
|
||||
expected := O.Of([]int{10, 20, 30})
|
||||
@@ -105,7 +105,7 @@ func TestTraverseArray_ChainedOperation(t *testing.T) {
|
||||
|
||||
result := F.Pipe1(
|
||||
Of[Config]([]int{1, 2, 3, 4}),
|
||||
Chain(TraverseArray[Config](multiplyByFactor)),
|
||||
Chain(TraverseArray(multiplyByFactor)),
|
||||
)
|
||||
|
||||
cfg := Config{Factor: 5}
|
||||
@@ -120,7 +120,7 @@ func TestTraverseArrayWithIndex_AllSuccess(t *testing.T) {
|
||||
}
|
||||
|
||||
input := []string{"a", "b", "c"}
|
||||
result := TraverseArrayWithIndex[context.Context](addIndex)(input)
|
||||
result := TraverseArrayWithIndex(addIndex)(input)
|
||||
|
||||
expected := O.Of([]string{"0:a", "1:b", "2:c"})
|
||||
assert.Equal(t, expected, result(context.Background())())
|
||||
@@ -136,7 +136,7 @@ func TestTraverseArrayWithIndex_OneFailure(t *testing.T) {
|
||||
}
|
||||
|
||||
input := []string{"a", "b", "c"}
|
||||
result := TraverseArrayWithIndex[context.Context](failOnIndex)(input)
|
||||
result := TraverseArrayWithIndex(failOnIndex)(input)
|
||||
|
||||
expected := O.None[[]string]()
|
||||
assert.Equal(t, expected, result(context.Background())())
|
||||
@@ -149,7 +149,7 @@ func TestTraverseArrayWithIndex_EmptyArray(t *testing.T) {
|
||||
}
|
||||
|
||||
input := []string{}
|
||||
result := TraverseArrayWithIndex[context.Context](addIndex)(input)
|
||||
result := TraverseArrayWithIndex(addIndex)(input)
|
||||
|
||||
expected := O.Of([]string{})
|
||||
assert.Equal(t, expected, result(context.Background())())
|
||||
@@ -170,7 +170,7 @@ func TestTraverseArrayWithIndex_WithEnvironment(t *testing.T) {
|
||||
}
|
||||
|
||||
input := []string{"a", "b", "c"}
|
||||
result := TraverseArrayWithIndex[Config](formatWithIndex)(input)
|
||||
result := TraverseArrayWithIndex(formatWithIndex)(input)
|
||||
|
||||
cfg := Config{Prefix: "item-"}
|
||||
expected := O.Of([]string{"item-0:a", "item-1:b", "item-2:c"})
|
||||
@@ -184,7 +184,7 @@ func TestTraverseArrayWithIndex_IndexUsedInLogic(t *testing.T) {
|
||||
}
|
||||
|
||||
input := []int{10, 20, 30, 40}
|
||||
result := TraverseArrayWithIndex[context.Context](multiplyByIndex)(input)
|
||||
result := TraverseArrayWithIndex(multiplyByIndex)(input)
|
||||
|
||||
// 10*0=0, 20*1=20, 30*2=60, 40*3=120
|
||||
expected := O.Of([]int{0, 20, 60, 120})
|
||||
@@ -216,7 +216,7 @@ func TestTraverseArray_ComplexType(t *testing.T) {
|
||||
{ID: 3, Name: "Charlie"},
|
||||
}
|
||||
|
||||
result := TraverseArray[context.Context](loadProfile)(users)
|
||||
result := TraverseArray(loadProfile)(users)
|
||||
|
||||
expected := O.Of([]UserProfile{
|
||||
{UserID: 1, DisplayName: "Profile: Alice"},
|
||||
@@ -247,12 +247,12 @@ func TestTraverseArray_ConditionalFailure(t *testing.T) {
|
||||
|
||||
// With MaxValue=3, should fail on 4 and 5
|
||||
cfg1 := Config{MaxValue: 3}
|
||||
result1 := TraverseArray[Config](validateAndDouble)(input)
|
||||
result1 := TraverseArray(validateAndDouble)(input)
|
||||
assert.Equal(t, O.None[[]int](), result1(cfg1)())
|
||||
|
||||
// With MaxValue=10, all should succeed
|
||||
cfg2 := Config{MaxValue: 10}
|
||||
result2 := TraverseArray[Config](validateAndDouble)(input)
|
||||
result2 := TraverseArray(validateAndDouble)(input)
|
||||
expected := O.Of([]int{2, 4, 6, 8, 10})
|
||||
assert.Equal(t, expected, result2(cfg2)())
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func TestFromReader(t *testing.T) {
|
||||
return cfg.Value * 2
|
||||
}
|
||||
|
||||
ro := FromReader[Config](r)
|
||||
ro := FromReader(r)
|
||||
cfg := Config{Value: 21}
|
||||
result := ro(cfg)()
|
||||
|
||||
@@ -83,7 +83,7 @@ func TestSomeReader(t *testing.T) {
|
||||
return cfg.Value * 2
|
||||
}
|
||||
|
||||
ro := SomeReader[Config](r)
|
||||
ro := SomeReader(r)
|
||||
cfg := Config{Value: 21}
|
||||
result := ro(cfg)()
|
||||
|
||||
|
||||
@@ -349,7 +349,7 @@ func TestLocalIOK(t *testing.T) {
|
||||
}
|
||||
|
||||
// Compose using LocalIOK
|
||||
adapted := LocalIOK[string, SimpleConfig, string](loadConfig)(useConfig)
|
||||
adapted := LocalIOK[string](loadConfig)(useConfig)
|
||||
res := adapted("config.json")()
|
||||
|
||||
assert.Equal(t, result.Of("Port: 8080"), res)
|
||||
@@ -371,7 +371,7 @@ func TestLocalIOK(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
adapted := LocalIOK[string, int, string](loadData)(processData)
|
||||
adapted := LocalIOK[string](loadData)(processData)
|
||||
res := adapted("test")()
|
||||
|
||||
assert.Equal(t, result.Of("Processed: 40"), res)
|
||||
@@ -392,7 +392,7 @@ func TestLocalIOK(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
adapted := LocalIOK[string, SimpleConfig, string](loadConfig)(failingOperation)
|
||||
adapted := LocalIOK[string](loadConfig)(failingOperation)
|
||||
res := adapted("config.json")()
|
||||
|
||||
assert.True(t, result.IsLeft(res))
|
||||
@@ -424,7 +424,7 @@ func TestLocalIOEitherK(t *testing.T) {
|
||||
}
|
||||
|
||||
// Compose using LocalIOEitherK
|
||||
adapted := LocalIOEitherK[string, SimpleConfig, string](loadConfig)(useConfig)
|
||||
adapted := LocalIOEitherK[string](loadConfig)(useConfig)
|
||||
|
||||
// Success case
|
||||
res := adapted("config.json")()
|
||||
@@ -448,7 +448,7 @@ func TestLocalIOEitherK(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
adapted := LocalIOEitherK[string, SimpleConfig, string](loadConfig)(useConfig)
|
||||
adapted := LocalIOEitherK[string](loadConfig)(useConfig)
|
||||
res := adapted("missing.json")()
|
||||
|
||||
// Error from loadConfig should propagate
|
||||
@@ -469,7 +469,7 @@ func TestLocalIOEitherK(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
adapted := LocalIOEitherK[string, SimpleConfig, string](loadConfig)(failingOperation)
|
||||
adapted := LocalIOEitherK[string](loadConfig)(failingOperation)
|
||||
res := adapted("config.json")()
|
||||
|
||||
// Error from ReaderIOResult should propagate
|
||||
@@ -502,7 +502,7 @@ func TestLocalIOResultK(t *testing.T) {
|
||||
}
|
||||
|
||||
// Compose using LocalIOResultK
|
||||
adapted := LocalIOResultK[string, SimpleConfig, string](loadConfig)(useConfig)
|
||||
adapted := LocalIOResultK[string](loadConfig)(useConfig)
|
||||
|
||||
// Success case
|
||||
res := adapted("config.json")()
|
||||
@@ -526,7 +526,7 @@ func TestLocalIOResultK(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
adapted := LocalIOResultK[string, SimpleConfig, string](loadConfig)(useConfig)
|
||||
adapted := LocalIOResultK[string](loadConfig)(useConfig)
|
||||
res := adapted("missing.json")()
|
||||
|
||||
// Error from loadConfig should propagate
|
||||
@@ -562,8 +562,8 @@ func TestLocalIOResultK(t *testing.T) {
|
||||
}
|
||||
|
||||
// Compose transformations
|
||||
step1 := LocalIOResultK[string, SimpleConfig, int](loadConfig)(formatConfig)
|
||||
step2 := LocalIOResultK[string, int, string](parseID)(step1)
|
||||
step1 := LocalIOResultK[string](loadConfig)(formatConfig)
|
||||
step2 := LocalIOResultK[string](parseID)(step1)
|
||||
|
||||
// Success case
|
||||
res := step2("test")()
|
||||
@@ -607,8 +607,8 @@ func TestLocalIOResultK(t *testing.T) {
|
||||
}
|
||||
|
||||
// Compose the pipeline
|
||||
step1 := LocalIOResultK[string, SimpleConfig, string](parseConfig)(useConfig)
|
||||
step2 := LocalIOResultK[string, string, ConfigFile](readFile)(step1)
|
||||
step1 := LocalIOResultK[string](parseConfig)(useConfig)
|
||||
step2 := LocalIOResultK[string](readFile)(step1)
|
||||
|
||||
// Success case
|
||||
res := step2(ConfigFile{Path: "app.json"})()
|
||||
@@ -764,7 +764,7 @@ func TestTap(t *testing.T) {
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config](42),
|
||||
Tap[Config](tapFunc),
|
||||
Tap(tapFunc),
|
||||
)
|
||||
|
||||
res := pipeline(Config{})()
|
||||
@@ -783,12 +783,12 @@ func TestTap(t *testing.T) {
|
||||
|
||||
pipelineWithTap := F.Pipe1(
|
||||
Of[Config](42),
|
||||
Tap[Config](sideEffect),
|
||||
Tap(sideEffect),
|
||||
)
|
||||
|
||||
pipelineWithChainFirst := F.Pipe1(
|
||||
Of[Config](42),
|
||||
ChainFirst[Config](sideEffect),
|
||||
ChainFirst(sideEffect),
|
||||
)
|
||||
|
||||
resultTap := pipelineWithTap(Config{})()
|
||||
@@ -896,7 +896,7 @@ func TestTapReaderK(t *testing.T) {
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config](42),
|
||||
TapReaderK[Config](tapFunc),
|
||||
TapReaderK(tapFunc),
|
||||
)
|
||||
|
||||
res := pipeline(Config{multiplier: 2})()
|
||||
@@ -965,7 +965,7 @@ func TestChainLeft(t *testing.T) {
|
||||
t.Run("Right passes through unchanged", func(t *testing.T) {
|
||||
pipeline := F.Pipe1(
|
||||
Right[Config](42),
|
||||
ChainLeft[Config](func(err error) ReaderIOResult[Config, int] {
|
||||
ChainLeft(func(err error) ReaderIOResult[Config, int] {
|
||||
return Left[Config, int](errors.New("should not run"))
|
||||
}),
|
||||
)
|
||||
@@ -980,7 +980,7 @@ func TestChainLeft(t *testing.T) {
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Left[Config, int](originalError),
|
||||
ChainLeft[Config](func(err error) ReaderIOResult[Config, int] {
|
||||
ChainLeft(func(err error) ReaderIOResult[Config, int] {
|
||||
return Left[Config, int](newError)
|
||||
}),
|
||||
)
|
||||
@@ -992,7 +992,7 @@ func TestChainLeft(t *testing.T) {
|
||||
t.Run("Left recovers to Right", func(t *testing.T) {
|
||||
pipeline := F.Pipe1(
|
||||
Left[Config, int](errors.New("error")),
|
||||
ChainLeft[Config](func(err error) ReaderIOResult[Config, int] {
|
||||
ChainLeft(func(err error) ReaderIOResult[Config, int] {
|
||||
return Right[Config](99)
|
||||
}),
|
||||
)
|
||||
@@ -1010,7 +1010,7 @@ func TestTapLeft(t *testing.T) {
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Right[Config](42),
|
||||
TapLeft[int, Config](func(err error) ReaderIOResult[Config, string] {
|
||||
TapLeft[int](func(err error) ReaderIOResult[Config, string] {
|
||||
return func(cfg Config) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
tapped = true
|
||||
@@ -1031,7 +1031,7 @@ func TestTapLeft(t *testing.T) {
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Left[Config, int](originalError),
|
||||
TapLeft[int, Config](func(err error) ReaderIOResult[Config, string] {
|
||||
TapLeft[int](func(err error) ReaderIOResult[Config, string] {
|
||||
return func(cfg Config) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
tapped = true
|
||||
|
||||
@@ -91,7 +91,7 @@ func TestMonadMapTo(t *testing.T) {
|
||||
func TestChain(t *testing.T) {
|
||||
g := F.Pipe1(
|
||||
Of[OuterConfig, InnerConfig, error](1),
|
||||
Chain[OuterConfig, InnerConfig, error](func(v int) ReaderReaderIOEither[OuterConfig, InnerConfig, error, string] {
|
||||
Chain(func(v int) ReaderReaderIOEither[OuterConfig, InnerConfig, error, string] {
|
||||
return Of[OuterConfig, InnerConfig, error](fmt.Sprintf("%d", v))
|
||||
}),
|
||||
)
|
||||
@@ -193,7 +193,7 @@ func TestFromEither(t *testing.T) {
|
||||
|
||||
t.Run("Left", func(t *testing.T) {
|
||||
err := errors.New("test error")
|
||||
result := FromEither[OuterConfig, InnerConfig, error, int](E.Left[int](err))
|
||||
result := FromEither[OuterConfig, InnerConfig](E.Left[int](err))
|
||||
assert.Equal(t, E.Left[int](err), result(OuterConfig{})(InnerConfig{})())
|
||||
})
|
||||
}
|
||||
@@ -239,7 +239,7 @@ func TestLeftIO(t *testing.T) {
|
||||
func TestFromIOEither(t *testing.T) {
|
||||
t.Run("Right", func(t *testing.T) {
|
||||
ioe := IOE.Right[error](42)
|
||||
result := FromIOEither[OuterConfig, InnerConfig, error](ioe)
|
||||
result := FromIOEither[OuterConfig, InnerConfig](ioe)
|
||||
assert.Equal(t, E.Right[error](42), result(OuterConfig{})(InnerConfig{})())
|
||||
})
|
||||
|
||||
@@ -344,7 +344,7 @@ func TestFromPredicate(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Predicate false", func(t *testing.T) {
|
||||
result := FromPredicate[OuterConfig, InnerConfig, error](isPositive, onFalse)(-5)
|
||||
result := FromPredicate[OuterConfig, InnerConfig](isPositive, onFalse)(-5)
|
||||
expected := E.Left[int](fmt.Errorf("not positive: -5"))
|
||||
assert.Equal(t, expected, result(OuterConfig{})(InnerConfig{})())
|
||||
})
|
||||
@@ -391,7 +391,7 @@ func TestRead(t *testing.T) {
|
||||
func TestChainEitherK(t *testing.T) {
|
||||
g := F.Pipe1(
|
||||
Of[OuterConfig, InnerConfig, error](1),
|
||||
ChainEitherK[OuterConfig, InnerConfig, error](func(v int) E.Either[error, string] {
|
||||
ChainEitherK[OuterConfig, InnerConfig](func(v int) E.Either[error, string] {
|
||||
return E.Right[error](fmt.Sprintf("%d", v))
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -6,9 +6,62 @@ This folder is meant to contain examples that illustrate how to use the library.
|
||||
|
||||
[](https://www.youtube.com/watch?v=Jif3jL6DRdw "introduction to fp-go")
|
||||
|
||||
### References
|
||||
## External Documentation References
|
||||
|
||||
- [Ryan's Blog](https://rlee.dev/practical-guide-to-fp-ts-part-1) - practical introduction into FP concepts
|
||||
- [Investigate Functional Programming Concepts in Go](https://betterprogramming.pub/investigate-functional-programming-concepts-in-go-1dada09bc913) - discussion around FP concepts in golang
|
||||
- [Investigating the I/O Monad in Go](https://medium.com/better-programming/investigating-the-i-o-monad-in-go-3c0fabbb4b3d) - a closer look at I/O monads in golang
|
||||
-
|
||||
### Official Documentation
|
||||
|
||||
- [API Documentation](https://pkg.go.dev/github.com/IBM/fp-go/v2) - Complete API reference
|
||||
- [Go 1.24 Release Notes](https://tip.golang.org/doc/go1.24) - Information about generic type aliases
|
||||
- [Go Blog: Generating code](https://go.dev/blog/generate) - Using `go generate`
|
||||
- [Go Context Package](https://pkg.go.dev/context) - Standard library context documentation
|
||||
|
||||
### Functional Programming Concepts
|
||||
|
||||
#### Introductory Resources
|
||||
- [Ryan's Blog](https://rlee.dev/practical-guide-to-fp-ts-part-1) - Practical introduction into FP concepts
|
||||
- [Investigate Functional Programming Concepts in Go](https://betterprogramming.pub/investigate-functional-programming-concepts-in-go-1dada09bc913) - Discussion around FP concepts in golang
|
||||
- [Investigating the I/O Monad in Go](https://medium.com/better-programming/investigating-the-i-o-monad-in-go-3c0fabbb4b3d) - A closer look at I/O monads in golang
|
||||
- [Professor Frisby's Mostly Adequate Guide](https://github.com/MostlyAdequate/mostly-adequate-guide) - Comprehensive FP guide
|
||||
- [mostly-adequate-fp-ts](https://github.com/ChuckJonas/mostly-adequate-fp-ts/) - TypeScript companion to Frisby's guide
|
||||
|
||||
#### Currying and Function Composition
|
||||
- [Mostly Adequate Guide - Ch. 4: Currying](https://mostly-adequate.gitbook.io/mostly-adequate-guide/ch04) - Excellent introduction with clear examples
|
||||
- [Curry and Function Composition](https://medium.com/javascript-scene/curry-and-function-composition-2c208d774983) by Eric Elliott
|
||||
- [Why Curry Helps](https://hughfdjackson.com/javascript/why-curry-helps/) - Practical benefits of currying
|
||||
|
||||
### Haskell and Type Theory
|
||||
|
||||
- [Haskell Wiki - Currying](https://wiki.haskell.org/Currying) - Comprehensive explanation of currying in Haskell
|
||||
- [Learn You a Haskell - Higher Order Functions](http://learnyouahaskell.com/higher-order-functions) - Introduction to currying and partial application
|
||||
- [Haskell's Prelude](https://hackage.haskell.org/package/base/docs/Prelude.html) - Standard library showing data-last convention
|
||||
- [Haskell Pair Type](https://hackage.haskell.org/package/TypeCompose-0.9.14/docs/Data-Pair.html) - Haskell definition of Pair
|
||||
- [Haskell Lens Library](https://hackage.haskell.org/package/lens) - Pioneering optics library
|
||||
|
||||
### Optics
|
||||
|
||||
- [Introduction to optics: lenses and prisms](https://medium.com/@gcanti/introduction-to-optics-lenses-and-prisms-3230e73bfcfe) by Giulio Canti - Excellent introduction to optics concepts
|
||||
- [Lenses in Functional Programming](https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/a-little-lens-starter-tutorial) - Tutorial on lens fundamentals
|
||||
- [Profunctor Optics: The Categorical View](https://bartoszmilewski.com/2017/07/07/profunctor-optics-the-categorical-view/) by Bartosz Milewski - Deep dive into the theory
|
||||
- [Why Optics?](https://www.tweag.io/blog/2022-01-06-optics-vs-lenses/) - Discussion of benefits and use cases
|
||||
|
||||
### Related Libraries
|
||||
|
||||
- [fp-ts](https://github.com/gcanti/fp-ts) - TypeScript library that inspired fp-go
|
||||
- [fp-ts Documentation](https://gcanti.github.io/fp-ts/) - TypeScript library documentation
|
||||
- [fp-ts Issue #1238](https://github.com/gcanti/fp-ts/issues/1238) - Real-world examples of data-last refactoring
|
||||
- [urfave/cli/v3](https://github.com/urfave/cli) - Underlying CLI framework
|
||||
|
||||
### Project Resources
|
||||
|
||||
- [GitHub Repository](https://github.com/IBM/fp-go) - Source code and issues
|
||||
- [Coverage Status](https://coveralls.io/github/IBM/fp-go?branch=main) - Test coverage reports
|
||||
- [Go Report Card](https://goreportcard.com/report/github.com/IBM/fp-go/v2) - Code quality metrics
|
||||
- [Apache License 2.0](https://github.com/IBM/fp-go/blob/main/LICENSE) - Project license
|
||||
|
||||
### Internal Documentation
|
||||
|
||||
- [DESIGN.md](../DESIGN.md) - Design philosophy and patterns
|
||||
- [IDIOMATIC_COMPARISON.md](../IDIOMATIC_COMPARISON.md) - Performance comparison between standard and idiomatic packages
|
||||
- [Optics Overview](../optics/README.md) - Complete guide to lenses, prisms, and other optics
|
||||
- [CLI Package](../cli/README.md) - Command-line interface utilities
|
||||
- [ReaderResult Package](../idiomatic/context/readerresult/README.md) - Context-aware result handling
|
||||
Reference in New Issue
Block a user