mirror of
https://github.com/IBM/fp-go.git
synced 2026-02-24 12:57:26 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61b948425b | ||
|
|
a276f3acff |
99
v2/llms.txt
Normal file
99
v2/llms.txt
Normal file
@@ -0,0 +1,99 @@
|
||||
# fp-go
|
||||
|
||||
> A comprehensive functional programming library for Go, bringing type-safe monads, functors, applicatives, optics, and composable abstractions inspired by fp-ts and Haskell to the Go ecosystem. Created by IBM, licensed under Apache-2.0.
|
||||
|
||||
fp-go v2 requires Go 1.24+ and leverages generic type aliases for a cleaner API.
|
||||
|
||||
Key concepts: `Option` for nullable values, `Either`/`Result` for error handling, `IO` for lazy side effects, `Reader` for dependency injection, `IOResult` for effectful error handling, `ReaderIOResult` for the full monad stack, and `Optics` (lens, prism, traversal, iso) for immutable data manipulation.
|
||||
|
||||
## Core Documentation
|
||||
|
||||
- [API Reference (pkg.go.dev)](https://pkg.go.dev/github.com/IBM/fp-go/v2): Complete API documentation for all packages
|
||||
- [README](https://github.com/IBM/fp-go/blob/main/v2/README.md): Overview, quick start, installation, and migration guide from v1 to v2
|
||||
- [Design Decisions](https://github.com/IBM/fp-go/blob/main/v2/DESIGN.md): Key design principles and patterns
|
||||
- [Functional I/O Guide](https://github.com/IBM/fp-go/blob/main/v2/FUNCTIONAL_IO.md): Understanding Context, errors, and the Reader pattern for I/O operations
|
||||
- [Idiomatic vs Standard Comparison](https://github.com/IBM/fp-go/blob/main/v2/IDIOMATIC_COMPARISON.md): Performance comparison and when to use each approach
|
||||
- [Optics README](https://github.com/IBM/fp-go/blob/main/v2/optics/README.md): Guide to lens, prism, optional, and traversal optics
|
||||
|
||||
## Standard Packages (struct-based)
|
||||
|
||||
- [option](https://pkg.go.dev/github.com/IBM/fp-go/v2/option): Option monad — represent optional values without nil
|
||||
- [either](https://pkg.go.dev/github.com/IBM/fp-go/v2/either): Either monad — type-safe error handling with Left/Right values
|
||||
- [result](https://pkg.go.dev/github.com/IBM/fp-go/v2/result): Result monad — simplified Either with `error` as Left type (recommended for error handling)
|
||||
- [io](https://pkg.go.dev/github.com/IBM/fp-go/v2/io): IO monad — lazy evaluation and side effect management
|
||||
- [iooption](https://pkg.go.dev/github.com/IBM/fp-go/v2/iooption): IOOption — IO combined with Option
|
||||
- [ioeither](https://pkg.go.dev/github.com/IBM/fp-go/v2/ioeither): IOEither — IO combined with Either for effectful error handling
|
||||
- [ioresult](https://pkg.go.dev/github.com/IBM/fp-go/v2/ioresult): IOResult — IO combined with Result (recommended over IOEither)
|
||||
- [reader](https://pkg.go.dev/github.com/IBM/fp-go/v2/reader): Reader monad — dependency injection pattern
|
||||
- [readeroption](https://pkg.go.dev/github.com/IBM/fp-go/v2/readeroption): ReaderOption — Reader combined with Option
|
||||
- [readeriooption](https://pkg.go.dev/github.com/IBM/fp-go/v2/readeriooption): ReaderIOOption — Reader + IO + Option
|
||||
- [readerioresult](https://pkg.go.dev/github.com/IBM/fp-go/v2/readerioresult): ReaderIOResult — Reader + IO + Result for complex workflows
|
||||
- [readerioeither](https://pkg.go.dev/github.com/IBM/fp-go/v2/readerioeither): ReaderIOEither — Reader + IO + Either
|
||||
- [statereaderioeither](https://pkg.go.dev/github.com/IBM/fp-go/v2/statereaderioeither): StateReaderIOEither — State + Reader + IO + Either
|
||||
|
||||
## Idiomatic Packages (tuple-based, high performance)
|
||||
|
||||
- [idiomatic/option](https://pkg.go.dev/github.com/IBM/fp-go/v2/idiomatic/option): Option using native Go `(value, bool)` tuples
|
||||
- [idiomatic/result](https://pkg.go.dev/github.com/IBM/fp-go/v2/idiomatic/result): Result using native Go `(value, error)` tuples
|
||||
- [idiomatic/ioresult](https://pkg.go.dev/github.com/IBM/fp-go/v2/idiomatic/ioresult): IOResult using `func() (value, error)`
|
||||
- [idiomatic/readerresult](https://pkg.go.dev/github.com/IBM/fp-go/v2/idiomatic/readerresult): ReaderResult with tuple-based results
|
||||
- [idiomatic/readerioresult](https://pkg.go.dev/github.com/IBM/fp-go/v2/idiomatic/readerioresult): ReaderIOResult with tuple-based results
|
||||
|
||||
## Context Packages (context.Context specializations)
|
||||
|
||||
- [context/readerioresult](https://pkg.go.dev/github.com/IBM/fp-go/v2/context/readerioresult): ReaderIOResult specialized for context.Context
|
||||
- [context/readerioresult/http](https://pkg.go.dev/github.com/IBM/fp-go/v2/context/readerioresult/http): Functional HTTP client utilities
|
||||
- [context/readerioresult/http/builder](https://pkg.go.dev/github.com/IBM/fp-go/v2/context/readerioresult/http/builder): Functional HTTP request builder
|
||||
- [context/statereaderioresult](https://pkg.go.dev/github.com/IBM/fp-go/v2/context/statereaderioresult): State + Reader + IO + Result for context.Context
|
||||
|
||||
## Optics
|
||||
|
||||
- [optics](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics): Core optics package
|
||||
- [optics/lens](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/lens): Lenses for focusing on fields in product types
|
||||
- [optics/prism](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/prism): Prisms for focusing on variants in sum types
|
||||
- [optics/iso](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/iso): Isomorphisms for bidirectional transformations
|
||||
- [optics/optional](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/optional): Optionals for values that may not exist
|
||||
- [optics/traversal](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/traversal): Traversals for focusing on multiple values
|
||||
- [optics/codec](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/codec): Codecs for encoding/decoding with validation
|
||||
|
||||
## Utility Packages
|
||||
|
||||
- [array](https://pkg.go.dev/github.com/IBM/fp-go/v2/array): Functional array/slice operations (map, filter, fold, etc.)
|
||||
- [record](https://pkg.go.dev/github.com/IBM/fp-go/v2/record): Functional operations for maps
|
||||
- [function](https://pkg.go.dev/github.com/IBM/fp-go/v2/function): Function composition, pipe, flow, curry, identity
|
||||
- [pair](https://pkg.go.dev/github.com/IBM/fp-go/v2/pair): Strongly-typed pair/tuple data structure
|
||||
- [tuple](https://pkg.go.dev/github.com/IBM/fp-go/v2/tuple): Type-safe heterogeneous tuples
|
||||
- [predicate](https://pkg.go.dev/github.com/IBM/fp-go/v2/predicate): Predicate combinators (and, or, not, etc.)
|
||||
- [endomorphism](https://pkg.go.dev/github.com/IBM/fp-go/v2/endomorphism): Endomorphism operations (compose, chain)
|
||||
- [eq](https://pkg.go.dev/github.com/IBM/fp-go/v2/eq): Type-safe equality comparisons
|
||||
- [ord](https://pkg.go.dev/github.com/IBM/fp-go/v2/ord): Total ordering type class
|
||||
- [semigroup](https://pkg.go.dev/github.com/IBM/fp-go/v2/semigroup): Semigroup algebraic structure
|
||||
- [monoid](https://pkg.go.dev/github.com/IBM/fp-go/v2/monoid): Monoid algebraic structure
|
||||
- [number](https://pkg.go.dev/github.com/IBM/fp-go/v2/number): Algebraic structures for numeric types
|
||||
- [string](https://pkg.go.dev/github.com/IBM/fp-go/v2/string): Functional string utilities
|
||||
- [boolean](https://pkg.go.dev/github.com/IBM/fp-go/v2/boolean): Functional boolean utilities
|
||||
- [bytes](https://pkg.go.dev/github.com/IBM/fp-go/v2/bytes): Functional byte slice utilities
|
||||
- [json](https://pkg.go.dev/github.com/IBM/fp-go/v2/json): Functional JSON encoding/decoding
|
||||
- [lazy](https://pkg.go.dev/github.com/IBM/fp-go/v2/lazy): Lazy evaluation without side effects
|
||||
- [identity](https://pkg.go.dev/github.com/IBM/fp-go/v2/identity): Identity monad
|
||||
- [retry](https://pkg.go.dev/github.com/IBM/fp-go/v2/retry): Retry policies with configurable backoff
|
||||
- [tailrec](https://pkg.go.dev/github.com/IBM/fp-go/v2/tailrec): Trampoline for tail-call optimization
|
||||
- [di](https://pkg.go.dev/github.com/IBM/fp-go/v2/di): Dependency injection utilities
|
||||
- [effect](https://pkg.go.dev/github.com/IBM/fp-go/v2/effect): Functional effect system
|
||||
- [circuitbreaker](https://pkg.go.dev/github.com/IBM/fp-go/v2/circuitbreaker): Circuit breaker error types
|
||||
- [builder](https://pkg.go.dev/github.com/IBM/fp-go/v2/builder): Generic builder pattern with validation
|
||||
|
||||
## Code Samples
|
||||
|
||||
- [samples/builder](https://github.com/IBM/fp-go/tree/main/v2/samples/builder): Functional builder pattern example
|
||||
- [samples/http](https://github.com/IBM/fp-go/tree/main/v2/samples/http): HTTP client examples
|
||||
- [samples/lens](https://github.com/IBM/fp-go/tree/main/v2/samples/lens): Optics/lens examples
|
||||
- [samples/mostly-adequate](https://github.com/IBM/fp-go/tree/main/v2/samples/mostly-adequate): Examples adapted from "Mostly Adequate Guide to Functional Programming"
|
||||
- [samples/tuples](https://github.com/IBM/fp-go/tree/main/v2/samples/tuples): Tuple usage examples
|
||||
|
||||
## Optional
|
||||
|
||||
- [Source Code](https://github.com/IBM/fp-go): GitHub repository
|
||||
- [Issues](https://github.com/IBM/fp-go/issues): Bug reports and feature requests
|
||||
- [Go Report Card](https://goreportcard.com/report/github.com/IBM/fp-go/v2): Code quality report
|
||||
- [Coverage](https://coveralls.io/github/IBM/fp-go?branch=main): Test coverage report
|
||||
@@ -3,7 +3,7 @@ package readerio
|
||||
import "github.com/IBM/fp-go/v2/io"
|
||||
|
||||
//go:inline
|
||||
func ChainConsumer[R, A any](c Consumer[A]) Operator[R, A, struct{}] {
|
||||
func ChainConsumer[R, A any](c Consumer[A]) Operator[R, A, Void] {
|
||||
return ChainIOK[R](io.FromConsumer(c))
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package readerio
|
||||
import (
|
||||
"github.com/IBM/fp-go/v2/consumer"
|
||||
"github.com/IBM/fp-go/v2/either"
|
||||
"github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/io"
|
||||
"github.com/IBM/fp-go/v2/predicate"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
@@ -66,4 +67,6 @@ type (
|
||||
// Predicate represents a function that tests a value of type A and returns a boolean.
|
||||
// It's commonly used for filtering and conditional operations.
|
||||
Predicate[A any] = predicate.Predicate[A]
|
||||
|
||||
Void = function.Void
|
||||
)
|
||||
|
||||
@@ -1,14 +1,107 @@
|
||||
// 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 readerioresult
|
||||
|
||||
import (
|
||||
"github.com/IBM/fp-go/v2/readerioeither"
|
||||
)
|
||||
|
||||
// ChainConsumer chains a consumer (side-effect function) into a ReaderIOResult computation,
|
||||
// replacing the success value with Void (empty struct).
|
||||
//
|
||||
// This is useful for performing side effects (like logging, printing, or writing to a file)
|
||||
// where you don't need to preserve the original value. The consumer is only executed if the
|
||||
// computation succeeds; if it fails with an error, the consumer is skipped.
|
||||
//
|
||||
// Type parameters:
|
||||
// - R: The context/environment type
|
||||
// - A: The value type to consume
|
||||
//
|
||||
// Parameters:
|
||||
// - c: A consumer function that performs a side effect on the value
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// An Operator that executes the consumer and returns Void on success
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import (
|
||||
// "context"
|
||||
// "fmt"
|
||||
// RIO "github.com/IBM/fp-go/v2/readerioresult"
|
||||
// )
|
||||
//
|
||||
// // Log a value and discard it
|
||||
// logValue := RIO.ChainConsumer[context.Context](func(x int) {
|
||||
// fmt.Printf("Value: %d\n", x)
|
||||
// })
|
||||
//
|
||||
// computation := F.Pipe1(
|
||||
// RIO.Of[context.Context](42),
|
||||
// logValue,
|
||||
// )
|
||||
// // Prints "Value: 42" and returns result.Of(struct{}{})
|
||||
//
|
||||
//go:inline
|
||||
func ChainConsumer[R, A any](c Consumer[A]) Operator[R, A, struct{}] {
|
||||
func ChainConsumer[R, A any](c Consumer[A]) Operator[R, A, Void] {
|
||||
return readerioeither.ChainConsumer[R, error](c)
|
||||
}
|
||||
|
||||
// ChainFirstConsumer chains a consumer into a ReaderIOResult computation while preserving
|
||||
// the original value.
|
||||
//
|
||||
// This is useful for performing side effects (like logging, printing, or metrics collection)
|
||||
// where you want to keep the original value for further processing. The consumer is only
|
||||
// executed if the computation succeeds; if it fails with an error, the consumer is skipped
|
||||
// and the error is propagated.
|
||||
//
|
||||
// Type parameters:
|
||||
// - R: The context/environment type
|
||||
// - A: The value type to consume and preserve
|
||||
//
|
||||
// Parameters:
|
||||
// - c: A consumer function that performs a side effect on the value
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// An Operator that executes the consumer and returns the original value on success
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import (
|
||||
// "context"
|
||||
// "fmt"
|
||||
// F "github.com/IBM/fp-go/v2/function"
|
||||
// N "github.com/IBM/fp-go/v2/number"
|
||||
// RIO "github.com/IBM/fp-go/v2/readerioresult"
|
||||
// )
|
||||
//
|
||||
// // Log a value but keep it for further processing
|
||||
// logValue := RIO.ChainFirstConsumer[context.Context](func(x int) {
|
||||
// fmt.Printf("Processing: %d\n", x)
|
||||
// })
|
||||
//
|
||||
// computation := F.Pipe2(
|
||||
// RIO.Of[context.Context](10),
|
||||
// logValue,
|
||||
// RIO.Map[context.Context](N.Mul(2)),
|
||||
// )
|
||||
// // Prints "Processing: 10" and returns result.Of(20)
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstConsumer[R, A any](c Consumer[A]) Operator[R, A, A] {
|
||||
return readerioeither.ChainFirstConsumer[R, error](c)
|
||||
|
||||
362
v2/readerioresult/consumer_test.go
Normal file
362
v2/readerioresult/consumer_test.go
Normal file
@@ -0,0 +1,362 @@
|
||||
// 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 readerioresult
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
N "github.com/IBM/fp-go/v2/number"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestChainConsumer_Success tests that ChainConsumer executes the consumer
|
||||
// and returns Void when the computation succeeds
|
||||
func TestChainConsumer_Success(t *testing.T) {
|
||||
// Track if consumer was called
|
||||
var consumed int
|
||||
consumer := func(x int) {
|
||||
consumed = x
|
||||
}
|
||||
|
||||
// Create a successful computation and chain the consumer
|
||||
computation := F.Pipe1(
|
||||
Of[context.Context](42),
|
||||
ChainConsumer[context.Context](consumer),
|
||||
)
|
||||
|
||||
// Execute the computation
|
||||
res := computation(context.Background())()
|
||||
|
||||
// Verify consumer was called with correct value
|
||||
assert.Equal(t, 42, consumed)
|
||||
|
||||
// Verify result is successful with Void
|
||||
assert.True(t, result.IsRight(res))
|
||||
if result.IsRight(res) {
|
||||
val := result.GetOrElse(func(error) Void { return Void{} })(res)
|
||||
assert.Equal(t, Void{}, val)
|
||||
}
|
||||
}
|
||||
|
||||
// TestChainConsumer_Failure tests that ChainConsumer does not execute
|
||||
// the consumer when the computation fails
|
||||
func TestChainConsumer_Failure(t *testing.T) {
|
||||
// Track if consumer was called
|
||||
consumerCalled := false
|
||||
consumer := func(x int) {
|
||||
consumerCalled = true
|
||||
}
|
||||
|
||||
// Create a failing computation
|
||||
expectedErr := errors.New("test error")
|
||||
computation := F.Pipe1(
|
||||
Left[context.Context, int](expectedErr),
|
||||
ChainConsumer[context.Context](consumer),
|
||||
)
|
||||
|
||||
// Execute the computation
|
||||
res := computation(context.Background())()
|
||||
|
||||
// Verify consumer was NOT called
|
||||
assert.False(t, consumerCalled)
|
||||
|
||||
// Verify result is an error
|
||||
assert.True(t, result.IsLeft(res))
|
||||
}
|
||||
|
||||
// TestChainConsumer_MultipleOperations tests chaining multiple operations
|
||||
// with ChainConsumer in a pipeline
|
||||
func TestChainConsumer_MultipleOperations(t *testing.T) {
|
||||
// Track consumer calls
|
||||
var values []int
|
||||
consumer := func(x int) {
|
||||
values = append(values, x)
|
||||
}
|
||||
|
||||
// Create a pipeline with multiple operations
|
||||
computation := F.Pipe2(
|
||||
Of[context.Context](10),
|
||||
Map[context.Context](N.Mul(2)),
|
||||
ChainConsumer[context.Context](consumer),
|
||||
)
|
||||
|
||||
// Execute the computation
|
||||
res := computation(context.Background())()
|
||||
|
||||
// Verify consumer was called with transformed value
|
||||
assert.Equal(t, []int{20}, values)
|
||||
|
||||
// Verify result is successful
|
||||
assert.True(t, result.IsRight(res))
|
||||
}
|
||||
|
||||
// TestChainFirstConsumer_Success tests that ChainFirstConsumer executes
|
||||
// the consumer and preserves the original value
|
||||
func TestChainFirstConsumer_Success(t *testing.T) {
|
||||
// Track if consumer was called
|
||||
var consumed int
|
||||
consumer := func(x int) {
|
||||
consumed = x
|
||||
}
|
||||
|
||||
// Create a successful computation and chain the consumer
|
||||
computation := F.Pipe1(
|
||||
Of[context.Context](42),
|
||||
ChainFirstConsumer[context.Context](consumer),
|
||||
)
|
||||
|
||||
// Execute the computation
|
||||
res := computation(context.Background())()
|
||||
|
||||
// Verify consumer was called with correct value
|
||||
assert.Equal(t, 42, consumed)
|
||||
|
||||
// Verify result is successful and preserves original value
|
||||
assert.True(t, result.IsRight(res))
|
||||
if result.IsRight(res) {
|
||||
val := result.GetOrElse(func(error) int { return 0 })(res)
|
||||
assert.Equal(t, 42, val)
|
||||
}
|
||||
}
|
||||
|
||||
// TestChainFirstConsumer_Failure tests that ChainFirstConsumer does not
|
||||
// execute the consumer when the computation fails
|
||||
func TestChainFirstConsumer_Failure(t *testing.T) {
|
||||
// Track if consumer was called
|
||||
consumerCalled := false
|
||||
consumer := func(x int) {
|
||||
consumerCalled = true
|
||||
}
|
||||
|
||||
// Create a failing computation
|
||||
expectedErr := errors.New("test error")
|
||||
computation := F.Pipe1(
|
||||
Left[context.Context, int](expectedErr),
|
||||
ChainFirstConsumer[context.Context](consumer),
|
||||
)
|
||||
|
||||
// Execute the computation
|
||||
res := computation(context.Background())()
|
||||
|
||||
// Verify consumer was NOT called
|
||||
assert.False(t, consumerCalled)
|
||||
|
||||
// Verify result is an error
|
||||
assert.True(t, result.IsLeft(res))
|
||||
}
|
||||
|
||||
// TestChainFirstConsumer_PreservesValue tests that ChainFirstConsumer
|
||||
// preserves the value for further processing
|
||||
func TestChainFirstConsumer_PreservesValue(t *testing.T) {
|
||||
// Track consumer calls
|
||||
var logged []int
|
||||
logger := func(x int) {
|
||||
logged = append(logged, x)
|
||||
}
|
||||
|
||||
// Create a pipeline that logs intermediate values
|
||||
computation := F.Pipe3(
|
||||
Of[context.Context](10),
|
||||
ChainFirstConsumer[context.Context](logger),
|
||||
Map[context.Context](N.Mul(2)),
|
||||
ChainFirstConsumer[context.Context](logger),
|
||||
)
|
||||
|
||||
// Execute the computation
|
||||
res := computation(context.Background())()
|
||||
|
||||
// Verify consumer was called at each step
|
||||
assert.Equal(t, []int{10, 20}, logged)
|
||||
|
||||
// Verify final result
|
||||
assert.True(t, result.IsRight(res))
|
||||
if result.IsRight(res) {
|
||||
val := result.GetOrElse(func(error) int { return 0 })(res)
|
||||
assert.Equal(t, 20, val)
|
||||
}
|
||||
}
|
||||
|
||||
// TestChainFirstConsumer_WithMap tests combining ChainFirstConsumer with Map
|
||||
func TestChainFirstConsumer_WithMap(t *testing.T) {
|
||||
// Track intermediate values
|
||||
var intermediate int
|
||||
consumer := func(x int) {
|
||||
intermediate = x
|
||||
}
|
||||
|
||||
// Create a pipeline with logging and transformation
|
||||
computation := F.Pipe2(
|
||||
Of[context.Context](5),
|
||||
ChainFirstConsumer[context.Context](consumer),
|
||||
Map[context.Context](N.Mul(3)),
|
||||
)
|
||||
|
||||
// Execute the computation
|
||||
res := computation(context.Background())()
|
||||
|
||||
// Verify consumer saw original value
|
||||
assert.Equal(t, 5, intermediate)
|
||||
|
||||
// Verify final result is transformed
|
||||
assert.True(t, result.IsRight(res))
|
||||
if result.IsRight(res) {
|
||||
val := result.GetOrElse(func(error) int { return 0 })(res)
|
||||
assert.Equal(t, 15, val)
|
||||
}
|
||||
}
|
||||
|
||||
// TestChainConsumer_WithContext tests that consumers work with context
|
||||
func TestChainConsumer_WithContext(t *testing.T) {
|
||||
type Config struct {
|
||||
Multiplier int
|
||||
}
|
||||
|
||||
// Track consumer calls
|
||||
var consumed int
|
||||
consumer := func(x int) {
|
||||
consumed = x
|
||||
}
|
||||
|
||||
// Create a computation that uses context
|
||||
computation := F.Pipe2(
|
||||
Of[Config](10),
|
||||
Map[Config](N.Mul(2)),
|
||||
ChainConsumer[Config](consumer),
|
||||
)
|
||||
|
||||
// Execute with context
|
||||
cfg := Config{Multiplier: 3}
|
||||
res := computation(cfg)()
|
||||
|
||||
// Verify consumer was called
|
||||
assert.Equal(t, 20, consumed)
|
||||
|
||||
// Verify result is successful
|
||||
assert.True(t, result.IsRight(res))
|
||||
}
|
||||
|
||||
// TestChainFirstConsumer_SideEffects tests that ChainFirstConsumer
|
||||
// can be used for side effects like logging
|
||||
func TestChainFirstConsumer_SideEffects(t *testing.T) {
|
||||
// Simulate a logging side effect
|
||||
var logs []string
|
||||
logValue := func(x string) {
|
||||
logs = append(logs, "Processing: "+x)
|
||||
}
|
||||
|
||||
// Create a pipeline with logging
|
||||
computation := F.Pipe3(
|
||||
Of[context.Context]("hello"),
|
||||
ChainFirstConsumer[context.Context](logValue),
|
||||
Map[context.Context](S.Append(" world")),
|
||||
ChainFirstConsumer[context.Context](logValue),
|
||||
)
|
||||
|
||||
// Execute the computation
|
||||
res := computation(context.Background())()
|
||||
|
||||
// Verify logs were created
|
||||
assert.Equal(t, []string{
|
||||
"Processing: hello",
|
||||
"Processing: hello world",
|
||||
}, logs)
|
||||
|
||||
// Verify final result
|
||||
assert.True(t, result.IsRight(res))
|
||||
if result.IsRight(res) {
|
||||
val := result.GetOrElse(func(error) string { return "" })(res)
|
||||
assert.Equal(t, "hello world", val)
|
||||
}
|
||||
}
|
||||
|
||||
// TestChainConsumer_ComplexType tests consumers with complex types
|
||||
func TestChainConsumer_ComplexType(t *testing.T) {
|
||||
type User struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
// Track consumed user
|
||||
var consumedUser *User
|
||||
consumer := func(u User) {
|
||||
consumedUser = &u
|
||||
}
|
||||
|
||||
// Create a computation with a complex type
|
||||
user := User{Name: "Alice", Age: 30}
|
||||
computation := F.Pipe1(
|
||||
Of[context.Context](user),
|
||||
ChainConsumer[context.Context](consumer),
|
||||
)
|
||||
|
||||
// Execute the computation
|
||||
res := computation(context.Background())()
|
||||
|
||||
// Verify consumer received the user
|
||||
assert.NotNil(t, consumedUser)
|
||||
assert.Equal(t, "Alice", consumedUser.Name)
|
||||
assert.Equal(t, 30, consumedUser.Age)
|
||||
|
||||
// Verify result is successful
|
||||
assert.True(t, result.IsRight(res))
|
||||
}
|
||||
|
||||
// TestChainFirstConsumer_ComplexType tests ChainFirstConsumer with complex types
|
||||
func TestChainFirstConsumer_ComplexType(t *testing.T) {
|
||||
type Product struct {
|
||||
ID int
|
||||
Name string
|
||||
Price float64
|
||||
}
|
||||
|
||||
// Track consumed products
|
||||
var consumedProducts []Product
|
||||
consumer := func(p Product) {
|
||||
consumedProducts = append(consumedProducts, p)
|
||||
}
|
||||
|
||||
// Create a pipeline with complex type
|
||||
product := Product{ID: 1, Name: "Widget", Price: 9.99}
|
||||
computation := F.Pipe2(
|
||||
Of[context.Context](product),
|
||||
ChainFirstConsumer[context.Context](consumer),
|
||||
Map[context.Context](func(p Product) Product {
|
||||
p.Price = p.Price * 1.1 // Apply 10% markup
|
||||
return p
|
||||
}),
|
||||
)
|
||||
|
||||
// Execute the computation
|
||||
res := computation(context.Background())()
|
||||
|
||||
// Verify consumer saw original product
|
||||
assert.Len(t, consumedProducts, 1)
|
||||
assert.Equal(t, 9.99, consumedProducts[0].Price)
|
||||
|
||||
// Verify final result has updated price
|
||||
assert.True(t, result.IsRight(res))
|
||||
if result.IsRight(res) {
|
||||
finalProduct := result.GetOrElse(func(error) Product { return Product{} })(res)
|
||||
assert.InDelta(t, 10.989, finalProduct.Price, 0.001)
|
||||
}
|
||||
}
|
||||
|
||||
// Made with Bob
|
||||
@@ -25,10 +25,11 @@ import (
|
||||
"github.com/IBM/fp-go/v2/readerio"
|
||||
RIOE "github.com/IBM/fp-go/v2/readerioeither"
|
||||
"github.com/IBM/fp-go/v2/readeroption"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
)
|
||||
|
||||
//go:inline
|
||||
func FromReaderOption[R, A any](onNone func() error) Kleisli[R, ReaderOption[R, A], A] {
|
||||
func FromReaderOption[R, A any](onNone Lazy[error]) Kleisli[R, ReaderOption[R, A], A] {
|
||||
return RIOE.FromReaderOption[R, A](onNone)
|
||||
}
|
||||
|
||||
@@ -113,7 +114,7 @@ func MonadTap[R, A, B any](fa ReaderIOResult[R, A], f Kleisli[R, A, B]) ReaderIO
|
||||
// The Either is automatically lifted into the ReaderIOResult context.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainEitherK[R, A, B any](ma ReaderIOResult[R, A], f func(A) Result[B]) ReaderIOResult[R, B] {
|
||||
func MonadChainEitherK[R, A, B any](ma ReaderIOResult[R, A], f result.Kleisli[A, B]) ReaderIOResult[R, B] {
|
||||
return RIOE.MonadChainEitherK(ma, f)
|
||||
}
|
||||
|
||||
@@ -121,7 +122,7 @@ func MonadChainEitherK[R, A, B any](ma ReaderIOResult[R, A], f func(A) Result[B]
|
||||
// The Either is automatically lifted into the ReaderIOResult context.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainResultK[R, A, B any](ma ReaderIOResult[R, A], f func(A) Result[B]) ReaderIOResult[R, B] {
|
||||
func MonadChainResultK[R, A, B any](ma ReaderIOResult[R, A], f result.Kleisli[A, B]) ReaderIOResult[R, B] {
|
||||
return RIOE.MonadChainEitherK(ma, f)
|
||||
}
|
||||
|
||||
@@ -129,7 +130,7 @@ func MonadChainResultK[R, A, B any](ma ReaderIOResult[R, A], f func(A) Result[B]
|
||||
// This is the curried version of MonadChainEitherK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainEitherK[R, A, B any](f func(A) Result[B]) Operator[R, A, B] {
|
||||
func ChainEitherK[R, A, B any](f result.Kleisli[A, B]) Operator[R, A, B] {
|
||||
return RIOE.ChainEitherK[R](f)
|
||||
}
|
||||
|
||||
@@ -137,7 +138,7 @@ func ChainEitherK[R, A, B any](f func(A) Result[B]) Operator[R, A, B] {
|
||||
// This is the curried version of MonadChainEitherK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainResultK[R, A, B any](f func(A) Result[B]) Operator[R, A, B] {
|
||||
func ChainResultK[R, A, B any](f result.Kleisli[A, B]) Operator[R, A, B] {
|
||||
return RIOE.ChainEitherK[R](f)
|
||||
}
|
||||
|
||||
@@ -145,12 +146,12 @@ func ChainResultK[R, A, B any](f func(A) Result[B]) Operator[R, A, B] {
|
||||
// Useful for validation or side effects that return Either.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirstEitherK[R, A, B any](ma ReaderIOResult[R, A], f func(A) Result[B]) ReaderIOResult[R, A] {
|
||||
func MonadChainFirstEitherK[R, A, B any](ma ReaderIOResult[R, A], f result.Kleisli[A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadChainFirstEitherK(ma, f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MonadTapEitherK[R, A, B any](ma ReaderIOResult[R, A], f func(A) Result[B]) ReaderIOResult[R, A] {
|
||||
func MonadTapEitherK[R, A, B any](ma ReaderIOResult[R, A], f result.Kleisli[A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadTapEitherK(ma, f)
|
||||
}
|
||||
|
||||
@@ -158,12 +159,12 @@ func MonadTapEitherK[R, A, B any](ma ReaderIOResult[R, A], f func(A) Result[B])
|
||||
// This is the curried version of MonadChainFirstEitherK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstEitherK[R, A, B any](f func(A) Result[B]) Operator[R, A, A] {
|
||||
func ChainFirstEitherK[R, A, B any](f result.Kleisli[A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstEitherK[R](f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func TapEitherK[R, A, B any](f func(A) Result[B]) Operator[R, A, A] {
|
||||
func TapEitherK[R, A, B any](f result.Kleisli[A, B]) Operator[R, A, A] {
|
||||
return RIOE.TapEitherK[R](f)
|
||||
}
|
||||
|
||||
@@ -171,12 +172,12 @@ func TapEitherK[R, A, B any](f func(A) Result[B]) Operator[R, A, A] {
|
||||
// Useful for validation or side effects that return Either.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirstResultK[R, A, B any](ma ReaderIOResult[R, A], f func(A) Result[B]) ReaderIOResult[R, A] {
|
||||
func MonadChainFirstResultK[R, A, B any](ma ReaderIOResult[R, A], f result.Kleisli[A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadChainFirstEitherK(ma, f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MonadTapResultK[R, A, B any](ma ReaderIOResult[R, A], f func(A) Result[B]) ReaderIOResult[R, A] {
|
||||
func MonadTapResultK[R, A, B any](ma ReaderIOResult[R, A], f result.Kleisli[A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadTapEitherK(ma, f)
|
||||
}
|
||||
|
||||
@@ -184,12 +185,12 @@ func MonadTapResultK[R, A, B any](ma ReaderIOResult[R, A], f func(A) Result[B])
|
||||
// This is the curried version of MonadChainFirstEitherK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstResultK[R, A, B any](f func(A) Result[B]) Operator[R, A, A] {
|
||||
func ChainFirstResultK[R, A, B any](f result.Kleisli[A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstEitherK[R](f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func TapResultK[R, A, B any](f func(A) Result[B]) Operator[R, A, A] {
|
||||
func TapResultK[R, A, B any](f result.Kleisli[A, B]) Operator[R, A, A] {
|
||||
return RIOE.TapEitherK[R](f)
|
||||
}
|
||||
|
||||
@@ -230,17 +231,17 @@ func TapReaderK[R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, A, A] {
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ChainReaderOptionK[R, A, B any](onNone func() error) func(readeroption.Kleisli[R, A, B]) Operator[R, A, B] {
|
||||
func ChainReaderOptionK[R, A, B any](onNone Lazy[error]) func(readeroption.Kleisli[R, A, B]) Operator[R, A, B] {
|
||||
return RIOE.ChainReaderOptionK[R, A, B](onNone)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ChainFirstReaderOptionK[R, A, B any](onNone func() error) func(readeroption.Kleisli[R, A, B]) Operator[R, A, A] {
|
||||
func ChainFirstReaderOptionK[R, A, B any](onNone Lazy[error]) func(readeroption.Kleisli[R, A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstReaderOptionK[R, A, B](onNone)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func TapReaderOptionK[R, A, B any](onNone func() error) func(readeroption.Kleisli[R, A, B]) Operator[R, A, A] {
|
||||
func TapReaderOptionK[R, A, B any](onNone Lazy[error]) func(readeroption.Kleisli[R, A, B]) Operator[R, A, A] {
|
||||
return RIOE.TapReaderOptionK[R, A, B](onNone)
|
||||
}
|
||||
|
||||
@@ -421,7 +422,7 @@ func TapIOK[R, A, B any](f func(A) IO[B]) Operator[R, A, A] {
|
||||
// If the Option is None, the provided error function is called to produce the error value.
|
||||
//
|
||||
//go:inline
|
||||
func ChainOptionK[R, A, B any](onNone func() error) func(func(A) Option[B]) Operator[R, A, B] {
|
||||
func ChainOptionK[R, A, B any](onNone Lazy[error]) func(func(A) Option[B]) Operator[R, A, B] {
|
||||
return RIOE.ChainOptionK[R, A, B](onNone)
|
||||
}
|
||||
|
||||
@@ -619,7 +620,7 @@ func Asks[R, A any](r Reader[R, A]) ReaderIOResult[R, A] {
|
||||
// If the Option is None, the provided function is called to produce the error.
|
||||
//
|
||||
//go:inline
|
||||
func FromOption[R, A any](onNone func() error) Kleisli[R, Option[A], A] {
|
||||
func FromOption[R, A any](onNone Lazy[error]) Kleisli[R, Option[A], A] {
|
||||
return RIOE.FromOption[R, A](onNone)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/IBM/fp-go/v2/consumer"
|
||||
"github.com/IBM/fp-go/v2/either"
|
||||
"github.com/IBM/fp-go/v2/endomorphism"
|
||||
"github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/io"
|
||||
"github.com/IBM/fp-go/v2/ioeither"
|
||||
"github.com/IBM/fp-go/v2/ioresult"
|
||||
@@ -122,4 +123,6 @@ type (
|
||||
|
||||
// Predicate represents a function that tests a value of type A and returns a boolean.
|
||||
Predicate[A any] = predicate.Predicate[A]
|
||||
|
||||
Void = function.Void
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user