Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions v2/array/nonempty/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ package nonempty
import "github.com/IBM/fp-go/v2/option"

type (

// NonEmptyArray represents an array with at least one element
// NonEmptyArray represents an array that is guaranteed to have at least one element.
// This provides compile-time safety for operations that require non-empty collections.
NonEmptyArray[A any] []A

// Kleisli represents a Kleisli arrow for the NonEmptyArray monad.
// It's a function from A to NonEmptyArray[B], used for composing operations that produce non-empty arrays.
Kleisli[A, B any] = func(A) NonEmptyArray[B]

// Operator represents a function that transforms one NonEmptyArray into another.
// It takes a NonEmptyArray[A] and produces a NonEmptyArray[B].
Operator[A, B any] = Kleisli[NonEmptyArray[A], B]

// Option represents an optional value that may or may not be present.
Option[A any] = option.Option[A]
)
11 changes: 9 additions & 2 deletions v2/array/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ package array
import "github.com/IBM/fp-go/v2/option"

type (
Kleisli[A, B any] = func(A) []B
// Kleisli represents a Kleisli arrow for arrays.
// It's a function from A to []B, used for composing operations that produce arrays.
Kleisli[A, B any] = func(A) []B

// Operator represents a function that transforms one array into another.
// It takes a []A and produces a []B.
Operator[A, B any] = Kleisli[[]A, B]
Option[A any] = option.Option[A]

// Option represents an optional value that may or may not be present.
Option[A any] = option.Option[A]
)
25 changes: 19 additions & 6 deletions v2/assert/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,24 @@ import (
)

type (
Result[T any] = result.Result[T]
Reader = reader.Reader[*testing.T, bool]
Kleisli[T any] = reader.Reader[T, Reader]
Predicate[T any] = predicate.Predicate[T]
Lens[S, T any] = lens.Lens[S, T]
// Result represents a computation that may fail with an error.
Result[T any] = result.Result[T]

// Reader represents a test assertion that depends on a testing.T context and returns a boolean.
Reader = reader.Reader[*testing.T, bool]

// Kleisli represents a function that produces a test assertion Reader from a value of type T.
Kleisli[T any] = reader.Reader[T, Reader]

// Predicate represents a function that tests a value of type T and returns a boolean.
Predicate[T any] = predicate.Predicate[T]

// Lens is a functional reference to a subpart of a data structure.
Lens[S, T any] = lens.Lens[S, T]

// Optional is an optic that focuses on a value that may or may not be present.
Optional[S, T any] = optional.Optional[S, T]
Prism[S, T any] = prism.Prism[S, T]

// Prism is an optic that focuses on a case of a sum type.
Prism[S, T any] = prism.Prism[S, T]
)
2 changes: 2 additions & 0 deletions v2/boolean/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ package boolean
import "github.com/IBM/fp-go/v2/monoid"

type (
// Monoid represents a monoid structure for boolean values.
// A monoid provides an associative binary operation and an identity element.
Monoid = monoid.Monoid[bool]
)
5 changes: 5 additions & 0 deletions v2/builder/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ import (
)

type (
// Result represents a computation that may fail with an error.
// It's an alias for Either[error, T].
Result[T any] = result.Result[T]

// Prism is an optic that focuses on a case of a sum type.
// It provides a way to extract and construct values of a specific variant.
Prism[S, A any] = prism.Prism[S, A]

// Option represents an optional value that may or may not be present.
Option[T any] = option.Option[T]
)
2 changes: 2 additions & 0 deletions v2/consumer/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,7 @@ type (
// }
Consumer[A any] = func(A)

// Operator represents a function that transforms a Consumer[A] into a Consumer[B].
// This is useful for composing and adapting consumers to work with different types.
Operator[A, B any] = func(Consumer[A]) Consumer[B]
)
29 changes: 28 additions & 1 deletion v2/context/ioresult/ioeither.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,34 @@ import (
"github.com/IBM/fp-go/v2/result"
)

// withContext wraps an existing IOEither and performs a context check for cancellation before delegating
// WithContext wraps an IOResult and performs a context check for cancellation before executing.
// This ensures that if the context is already cancelled, the computation short-circuits immediately
// without executing the wrapped computation.
//
// This is useful for adding cancellation awareness to computations that might not check the context themselves.
//
// Type Parameters:
// - A: The type of the success value
//
// Parameters:
// - ctx: The context to check for cancellation
// - ma: The IOResult to wrap with context checking
//
// Returns:
// - An IOResult that checks for cancellation before executing
//
// Example:
//
// computation := func() Result[string] {
// // Long-running operation
// return result.Of("done")
// }
//
// ctx, cancel := context.WithCancel(context.Background())
// cancel() // Cancel immediately
//
// wrapped := WithContext(ctx, computation)
// result := wrapped() // Returns Left with context.Canceled error
func WithContext[A any](ctx context.Context, ma IOResult[A]) IOResult[A] {
return func() Result[A] {
if ctx.Err() != nil {
Expand Down
59 changes: 59 additions & 0 deletions v2/context/readerio/bracket.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,65 @@ import (
RIO "github.com/IBM/fp-go/v2/readerio"
)

// Bracket ensures that a resource is properly acquired, used, and released, even if an error occurs.
// This implements the bracket pattern for safe resource management with [ReaderIO].
//
// The bracket pattern guarantees that:
// - The acquire action is executed first to obtain the resource
// - The use function is called with the acquired resource
// - The release function is always called with the resource and result, regardless of success or failure
// - The final result from the use function is returned
//
// This is particularly useful for managing resources like file handles, database connections,
// or locks that must be cleaned up properly.
//
// Type Parameters:
// - A: The type of the acquired resource
// - B: The type of the result produced by the use function
// - ANY: The type returned by the release function (typically ignored)
//
// Parameters:
// - acquire: A ReaderIO that acquires the resource
// - use: A Kleisli arrow that uses the resource and produces a result
// - release: A function that releases the resource, receiving both the resource and the result
//
// Returns:
// - A ReaderIO[B] that safely manages the resource lifecycle
//
// Example:
//
// // Acquire a file handle
// acquireFile := func(ctx context.Context) IO[*os.File] {
// return func() *os.File {
// f, _ := os.Open("data.txt")
// return f
// }
// }
//
// // Use the file
// readFile := func(f *os.File) ReaderIO[string] {
// return func(ctx context.Context) IO[string] {
// return func() string {
// data, _ := io.ReadAll(f)
// return string(data)
// }
// }
// }
//
// // Release the file
// closeFile := func(f *os.File, result string) ReaderIO[any] {
// return func(ctx context.Context) IO[any] {
// return func() any {
// f.Close()
// return nil
// }
// }
// }
//
// // Safely read file with automatic cleanup
// safeRead := Bracket(acquireFile, readFile, closeFile)
// result := safeRead(context.Background())()
//
//go:inline
func Bracket[
A, B, ANY any](
Expand Down
52 changes: 52 additions & 0 deletions v2/context/readerio/consumer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,63 @@ package readerio

import "github.com/IBM/fp-go/v2/io"

// ChainConsumer chains a consumer function into a ReaderIO computation, discarding the original value.
// This is useful for performing side effects (like logging or metrics) that consume a value
// but don't produce a meaningful result.
//
// The consumer is executed for its side effects, and the computation returns an empty struct.
//
// Type Parameters:
// - A: The type of value to consume
//
// Parameters:
// - c: A consumer function that performs side effects on the value
//
// Returns:
// - An Operator that chains the consumer and returns struct{}
//
// Example:
//
// logUser := func(u User) {
// log.Printf("Processing user: %s", u.Name)
// }
//
// pipeline := F.Pipe2(
// fetchUser(123),
// ChainConsumer(logUser),
// )
//
//go:inline
func ChainConsumer[A any](c Consumer[A]) Operator[A, struct{}] {
return ChainIOK(io.FromConsumerK(c))
}

// ChainFirstConsumer chains a consumer function into a ReaderIO computation, preserving the original value.
// This is useful for performing side effects (like logging or metrics) while passing the value through unchanged.
//
// The consumer is executed for its side effects, but the original value is returned.
//
// Type Parameters:
// - A: The type of value to consume and return
//
// Parameters:
// - c: A consumer function that performs side effects on the value
//
// Returns:
// - An Operator that chains the consumer and returns the original value
//
// Example:
//
// logUser := func(u User) {
// log.Printf("User: %s", u.Name)
// }
//
// pipeline := F.Pipe3(
// fetchUser(123),
// ChainFirstConsumer(logUser), // Logs but passes user through
// Map(func(u User) string { return u.Email }),
// )
//
//go:inline
func ChainFirstConsumer[A any](c Consumer[A]) Operator[A, A] {
return ChainFirstIOK(io.FromConsumerK(c))
Expand Down
97 changes: 97 additions & 0 deletions v2/context/readerio/flip.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,108 @@ import (
RIO "github.com/IBM/fp-go/v2/readerio"
)

// SequenceReader transforms a ReaderIO containing a Reader into a Reader containing a ReaderIO.
// This "flips" the nested structure, allowing you to provide the Reader's environment first,
// then get a ReaderIO that can be executed with a context.
//
// Type transformation:
//
// From: ReaderIO[Reader[R, A]]
// = func(context.Context) func() func(R) A
//
// To: Reader[R, ReaderIO[A]]
// = func(R) func(context.Context) func() A
//
// This is useful for point-free style programming where you want to partially apply
// the Reader's environment before dealing with the context.
//
// Type Parameters:
// - R: The environment type that the Reader depends on
// - A: The value type
//
// Parameters:
// - ma: A ReaderIO containing a Reader
//
// Returns:
// - A Reader that produces a ReaderIO when given an environment
//
// Example:
//
// type Config struct {
// Timeout int
// }
//
// // A computation that produces a Reader
// getMultiplier := func(ctx context.Context) IO[func(Config) int] {
// return func() func(Config) int {
// return func(cfg Config) int {
// return cfg.Timeout * 2
// }
// }
// }
//
// // Sequence it to apply Config first
// sequenced := SequenceReader[Config, int](getMultiplier)
// cfg := Config{Timeout: 30}
// result := sequenced(cfg)(context.Background())() // Returns 60
//
//go:inline
func SequenceReader[R, A any](ma ReaderIO[Reader[R, A]]) Reader[R, ReaderIO[A]] {
return RIO.SequenceReader(ma)
}

// TraverseReader applies a Reader-based transformation to a ReaderIO, introducing a new environment dependency.
//
// This function takes a Reader-based Kleisli arrow and returns a function that can transform
// a ReaderIO. The result allows you to provide the Reader's environment (R) first, which then
// produces a ReaderIO that depends on the context.
//
// Type transformation:
//
// From: ReaderIO[A]
// = func(context.Context) func() A
//
// With: reader.Kleisli[R, A, B]
// = func(A) func(R) B
//
// To: func(ReaderIO[A]) func(R) ReaderIO[B]
// = func(ReaderIO[A]) func(R) func(context.Context) func() B
//
// This enables transforming values within a ReaderIO using environment-dependent logic.
//
// Type Parameters:
// - R: The environment type that the Reader depends on
// - A: The input value type
// - B: The output value type
//
// Parameters:
// - f: A Reader-based Kleisli arrow that transforms A to B using environment R
//
// Returns:
// - A function that takes a ReaderIO[A] and returns a function from R to ReaderIO[B]
//
// Example:
//
// type Config struct {
// Multiplier int
// }
//
// // A Reader-based transformation
// multiply := func(x int) func(Config) int {
// return func(cfg Config) int {
// return x * cfg.Multiplier
// }
// }
//
// // Apply TraverseReader
// traversed := TraverseReader[Config, int, int](multiply)
// computation := Of(10)
// result := traversed(computation)
//
// // Provide Config to get final result
// cfg := Config{Multiplier: 5}
// finalResult := result(cfg)(context.Background())() // Returns 50
//
//go:inline
func TraverseReader[R, A, B any](
f reader.Kleisli[R, A, B],
Expand Down
Loading