Documentation
¶
Overview ¶
Package effect provides a functional effect system for managing side effects in Go.
Overview ¶
The effect package is a high-level abstraction for composing effectful computations that may fail, require dependencies (context), and perform I/O operations. It is built on top of ReaderReaderIOResult, providing a clean API for dependency injection and error handling.
Naming Conventions ¶
The naming conventions in this package are modeled after effect-ts (https://effect.website/), a popular TypeScript library for functional effect systems. This alignment helps developers familiar with effect-ts to quickly understand and use this Go implementation.
Core Type ¶
The central type is Effect[C, A], which represents:
- C: The context/dependency type required by the effect
- A: The success value type produced by the effect
An Effect can:
- Succeed with a value of type A
- Fail with an error
- Require a context of type C
- Perform I/O operations
Basic Operations ¶
Creating Effects:
// Create a successful effect
effect.Succeed[MyContext, string]("hello")
// Create a failed effect
effect.Fail[MyContext, string](errors.New("failed"))
// Lift a pure value into an effect
effect.Of[MyContext, int](42)
Transforming Effects:
// Map over the success value
effect.Map[MyContext](func(x int) string {
return strconv.Itoa(x)
})
// Chain effects together (flatMap)
effect.Chain[MyContext](func(x int) Effect[MyContext, string] {
return effect.Succeed[MyContext, string](strconv.Itoa(x))
})
// Tap into an effect without changing its value
effect.Tap[MyContext](func(x int) Effect[MyContext, any] {
return effect.Succeed[MyContext, any](fmt.Println(x))
})
Dependency Injection ¶
Effects can access their required context:
// Transform the context before passing it to an effect
effect.Local[OuterCtx, InnerCtx](func(outer OuterCtx) InnerCtx {
return outer.Inner
})
// Provide a context to run an effect
effect.Provide[MyContext, string](myContext)
Do Notation ¶
The package provides "do notation" for composing effects in a sequential, imperative style:
type State struct {
X int
Y string
}
result := effect.Do[MyContext](State{}).
Bind(func(y string) func(State) State {
return func(s State) State {
s.Y = y
return s
}
}, fetchString).
Let(func(x int) func(State) State {
return func(s State) State {
s.X = x
return s
}
}, func(s State) int {
return len(s.Y)
})
Bind Operations ¶
The package provides various bind operations for integrating with other effect types:
- BindIOK: Bind an IO operation
- BindIOEitherK: Bind an IOEither operation
- BindIOResultK: Bind an IOResult operation
- BindReaderK: Bind a Reader operation
- BindReaderIOK: Bind a ReaderIO operation
- BindEitherK: Bind an Either operation
Each bind operation has a corresponding "L" variant for working with lenses:
- BindL, BindIOKL, BindReaderKL, etc.
Applicative Operations ¶
Apply effects in parallel:
// Apply a function effect to a value effect effect.Ap[string, MyContext](valueEffect)(functionEffect) // Apply effects to build up a structure effect.ApS[MyContext](setter, effect1)
Traversal ¶
Traverse collections with effects:
// Map an array with an effectful function
effect.TraverseArray[MyContext](func(x int) Effect[MyContext, string] {
return effect.Succeed[MyContext, string](strconv.Itoa(x))
})
Retry Logic ¶
Retry effects with configurable policies:
effect.Retrying[MyContext, string](
retryPolicy,
func(status retry.RetryStatus) Effect[MyContext, string] {
return fetchData()
},
func(result Result[string]) bool {
return result.IsLeft() // retry on error
},
)
Monoids ¶
Combine effects using monoid operations:
// Combine effects using applicative semantics effect.ApplicativeMonoid[MyContext](stringMonoid) // Combine effects using alternative semantics (first success) effect.AlternativeMonoid[MyContext](stringMonoid)
Running Effects ¶
To execute an effect:
// Provide the context ioResult := effect.Provide[MyContext, string](myContext)(myEffect) // Run synchronously readerResult := effect.RunSync(ioResult) // Execute with a context.Context value, err := readerResult(ctx)
Integration with Other Packages ¶
The effect package integrates seamlessly with other fp-go packages:
- either: For error handling
- io: For I/O operations
- reader: For dependency injection
- result: For result types
- retry: For retry logic
- monoid: For combining effects
Example ¶
type Config struct {
APIKey string
BaseURL string
}
func fetchUser(id int) Effect[Config, User] {
return effect.Chain[Config](func(cfg Config) Effect[Config, User] {
// Use cfg.APIKey and cfg.BaseURL
return effect.Succeed[Config, User](User{ID: id})
})(effect.Of[Config, Config](Config{}))
}
func main() {
cfg := Config{APIKey: "key", BaseURL: "https://api.example.com"}
userEffect := fetchUser(42)
// Run the effect
ioResult := effect.Provide(cfg)(userEffect)
readerResult := effect.RunSync(ioResult)
user, err := readerResult(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Printf("User: %+v\n", user)
}
Index ¶
- func LocalEffectK[A, C1, C2 any](f Kleisli[C2, C2, C1]) func(Effect[C1, A]) Effect[C2, A]
- func LocalIOK[A, C1, C2 any](f io.Kleisli[C2, C1]) func(Effect[C1, A]) Effect[C2, A]
- func LocalIOResultK[A, C1, C2 any](f ioresult.Kleisli[C2, C1]) func(Effect[C1, A]) Effect[C2, A]
- func LocalResultK[A, C1, C2 any](f result.Kleisli[C2, C1]) func(Effect[C1, A]) Effect[C2, A]
- func LocalThunkK[A, C1, C2 any](f thunk.Kleisli[C2, C1]) func(Effect[C1, A]) Effect[C2, A]
- func Provide[C, A any](c C) func(Effect[C, A]) ReaderIOResult[A]
- func Read[A, C any](c C) func(Effect[C, A]) Thunk[A]
- func RunSync[A any](fa ReaderIOResult[A]) readerresult.ReaderResult[A]
- type Effect
- func Do[C, S any](empty S) Effect[C, S]
- func Fail[C, A any](err error) Effect[C, A]
- func Of[C, A any](a A) Effect[C, A]
- func Retrying[C, A any](policy retry.RetryPolicy, action Kleisli[C, retry.RetryStatus, A], ...) Effect[C, A]
- func Succeed[C, A any](a A) Effect[C, A]
- func Suspend[C, A any](fa Lazy[Effect[C, A]]) Effect[C, A]
- type Either
- type IO
- type IOEither
- type IOResult
- type Kleisli
- func Contramap[C1, C2, A any](acc Reader[C1, C2]) Kleisli[C1, Effect[C2, A], A]
- func Local[C1, C2, A any](acc Reader[C1, C2]) Kleisli[C1, Effect[C2, A], A]
- func Ternary[C, A, B any](pred Predicate[A], onTrue, onFalse Kleisli[C, A, B]) Kleisli[C, A, B]
- func TraverseArray[C, A, B any](f Kleisli[C, A, B]) Kleisli[C, []A, []B]
- type Lazy
- type Lens
- type Monoid
- type Operator
- func Ap[B, C, A any](fa Effect[C, A]) Operator[C, func(A) B, B]
- func ApEitherS[C, S1, S2, T any](setter func(T) func(S1) S2, fa Either[error, T]) Operator[C, S1, S2]
- func ApEitherSL[C, S, T any](lens Lens[S, T], fa Either[error, T]) Operator[C, S, S]
- func ApIOEitherS[C, S1, S2, T any](setter func(T) func(S1) S2, fa IOEither[error, T]) Operator[C, S1, S2]
- func ApIOEitherSL[C, S, T any](lens Lens[S, T], fa IOEither[error, T]) Operator[C, S, S]
- func ApIOS[C, S1, S2, T any](setter func(T) func(S1) S2, fa IO[T]) Operator[C, S1, S2]
- func ApIOSL[C, S, T any](lens Lens[S, T], fa IO[T]) Operator[C, S, S]
- func ApReaderIOS[C, S1, S2, T any](setter func(T) func(S1) S2, fa ReaderIO[C, T]) Operator[C, S1, S2]
- func ApReaderIOSL[C, S, T any](lens Lens[S, T], fa ReaderIO[C, T]) Operator[C, S, S]
- func ApReaderS[C, S1, S2, T any](setter func(T) func(S1) S2, fa Reader[C, T]) Operator[C, S1, S2]
- func ApReaderSL[C, S, T any](lens Lens[S, T], fa Reader[C, T]) Operator[C, S, S]
- func ApS[C, S1, S2, T any](setter func(T) func(S1) S2, fa Effect[C, T]) Operator[C, S1, S2]
- func ApSL[C, S, T any](lens Lens[S, T], fa Effect[C, T]) Operator[C, S, S]
- func Bind[C, S1, S2, T any](setter func(T) func(S1) S2, f Kleisli[C, S1, T]) Operator[C, S1, S2]
- func BindEitherK[C, S1, S2, T any](setter func(T) func(S1) S2, f either.Kleisli[error, S1, T]) Operator[C, S1, S2]
- func BindIOEitherK[C, S1, S2, T any](setter func(T) func(S1) S2, f ioeither.Kleisli[error, S1, T]) Operator[C, S1, S2]
- func BindIOEitherKL[C, S, T any](lens Lens[S, T], f ioeither.Kleisli[error, T, T]) Operator[C, S, S]
- func BindIOK[C, S1, S2, T any](setter func(T) func(S1) S2, f io.Kleisli[S1, T]) Operator[C, S1, S2]
- func BindIOKL[C, S, T any](lens Lens[S, T], f io.Kleisli[T, T]) Operator[C, S, S]
- func BindIOResultK[C, S1, S2, T any](setter func(T) func(S1) S2, f ioresult.Kleisli[S1, T]) Operator[C, S1, S2]
- func BindL[C, S, T any](lens Lens[S, T], f func(T) Effect[C, T]) Operator[C, S, S]
- func BindReaderIOK[C, S1, S2, T any](setter func(T) func(S1) S2, f readerio.Kleisli[C, S1, T]) Operator[C, S1, S2]
- func BindReaderIOKL[C, S, T any](lens Lens[S, T], f readerio.Kleisli[C, T, T]) Operator[C, S, S]
- func BindReaderK[C, S1, S2, T any](setter func(T) func(S1) S2, f reader.Kleisli[C, S1, T]) Operator[C, S1, S2]
- func BindReaderKL[C, S, T any](lens Lens[S, T], f reader.Kleisli[C, T, T]) Operator[C, S, S]
- func BindTo[C, S1, T any](setter func(T) S1) Operator[C, T, S1]
- func Chain[C, A, B any](f Kleisli[C, A, B]) Operator[C, A, B]
- func ChainResultK[C, A, B any](f result.Kleisli[A, B]) Operator[C, A, B]
- func Let[C, S1, S2, T any](setter func(T) func(S1) S2, f func(S1) T) Operator[C, S1, S2]
- func LetL[C, S, T any](lens Lens[S, T], f func(T) T) Operator[C, S, S]
- func LetTo[C, S1, S2, T any](setter func(T) func(S1) S2, b T) Operator[C, S1, S2]
- func LetToL[C, S, T any](lens Lens[S, T], b T) Operator[C, S, S]
- func Map[C, A, B any](f func(A) B) Operator[C, A, B]
- func Tap[C, A, ANY any](f Kleisli[C, A, ANY]) Operator[C, A, A]
- type Predicate
- type Reader
- type ReaderIO
- type ReaderIOResult
- type Result
- type Thunk
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func LocalEffectK ¶
func LocalEffectK[A, C1, C2 any](f Kleisli[C2, C2, C1]) func(Effect[C1, A]) Effect[C2, A]
LocalEffectK transforms the context of an Effect using an Effect-returning function. This is the most powerful context transformation function, allowing the transformation itself to be effectful (can fail, perform I/O, and access the outer context).
LocalEffectK takes a Kleisli arrow that:
- Accepts the outer context C2
- Returns an Effect that produces the inner context C1
- Can fail with an error during context transformation
- Can perform I/O operations during transformation
This is useful when:
- Context transformation requires I/O (e.g., loading config from a file)
- Context transformation can fail (e.g., validating or parsing context)
- Context transformation needs to access the outer context
Type Parameters:
- A: The value type produced by the effect
- C1: The inner context type (required by the original effect)
- C2: The outer context type (provided to the transformed effect)
Parameters:
- f: A Kleisli arrow (C2 -> Effect[C2, C1]) that transforms C2 to C1 effectfully
Returns:
- A function that transforms Effect[C1, A] to Effect[C2, A]
Example:
type DatabaseConfig struct {
ConnectionString string
}
type AppConfig struct {
ConfigPath string
}
// Effect that needs DatabaseConfig
dbEffect := effect.Of[DatabaseConfig, string]("query result")
// Transform AppConfig to DatabaseConfig effectfully
// (e.g., load config from file, which can fail)
loadConfig := func(app AppConfig) Effect[AppConfig, DatabaseConfig] {
return effect.Chain[AppConfig](func(_ AppConfig) Effect[AppConfig, DatabaseConfig] {
// Simulate loading config from file (can fail)
return effect.Of[AppConfig, DatabaseConfig](DatabaseConfig{
ConnectionString: "loaded from " + app.ConfigPath,
})
})(effect.Of[AppConfig, AppConfig](app))
}
// Apply the transformation
transform := effect.LocalEffectK[string, DatabaseConfig, AppConfig](loadConfig)
appEffect := transform(dbEffect)
// Run with AppConfig
ioResult := effect.Provide(AppConfig{ConfigPath: "/etc/app.conf"})(appEffect)
readerResult := effect.RunSync(ioResult)
result, err := readerResult(context.Background())
Comparison with other Local functions:
- Local/Contramap: Pure context transformation (C2 -> C1)
- LocalIOK: IO-based transformation (C2 -> IO[C1])
- LocalIOResultK: IO with error handling (C2 -> IOResult[C1])
- LocalReaderIOResultK: Reader-based with IO and errors (C2 -> ReaderIOResult[C1])
- LocalEffectK: Full Effect transformation (C2 -> Effect[C2, C1])
func LocalIOResultK ¶
func LocalResultK ¶
func LocalThunkK ¶
func RunSync ¶
func RunSync[A any](fa ReaderIOResult[A]) readerresult.ReaderResult[A]
Types ¶
type Effect ¶
type Effect[C, A any] = readerreaderioresult.ReaderReaderIOResult[C, A]
func Retrying ¶
func Retrying[C, A any]( policy retry.RetryPolicy, action Kleisli[C, retry.RetryStatus, A], check Predicate[Result[A]], ) Effect[C, A]
type Kleisli ¶
type Kleisli[C, A, B any] = readerreaderioresult.Kleisli[C, A, B]
func Ternary ¶
func Ternary[C, A, B any](pred Predicate[A], onTrue, onFalse Kleisli[C, A, B]) Kleisli[C, A, B]
func TraverseArray ¶
func TraverseArray[C, A, B any](f Kleisli[C, A, B]) Kleisli[C, []A, []B]
type Operator ¶
type Operator[C, A, B any] = readerreaderioresult.Operator[C, A, B]
func ApEitherSL ¶
func ApIOEitherS ¶
func ApIOEitherSL ¶
func ApIOS ¶
func ApIOS[C, S1, S2, T any]( setter func(T) func(S1) S2, fa IO[T], ) Operator[C, S1, S2]
func ApReaderIOS ¶
func ApReaderIOS[C, S1, S2, T any]( setter func(T) func(S1) S2, fa ReaderIO[C, T], ) Operator[C, S1, S2]
func ApReaderIOSL ¶
func ApReaderIOSL[C, S, T any]( lens Lens[S, T], fa ReaderIO[C, T], ) Operator[C, S, S]
func ApReaderS ¶
func ApReaderS[C, S1, S2, T any]( setter func(T) func(S1) S2, fa Reader[C, T], ) Operator[C, S1, S2]
func ApReaderSL ¶
func ApReaderSL[C, S, T any]( lens Lens[S, T], fa Reader[C, T], ) Operator[C, S, S]
func ApS ¶
func ApS[C, S1, S2, T any]( setter func(T) func(S1) S2, fa Effect[C, T], ) Operator[C, S1, S2]
func Bind ¶
func Bind[C, S1, S2, T any]( setter func(T) func(S1) S2, f Kleisli[C, S1, T], ) Operator[C, S1, S2]
func BindEitherK ¶
func BindIOEitherK ¶
func BindIOEitherKL ¶
func BindIOResultK ¶
func BindReaderIOK ¶
func BindReaderIOKL ¶
func BindReaderK ¶
func BindReaderKL ¶
func ChainResultK ¶
type ReaderIOResult ¶
type ReaderIOResult[A any] = readerioresult.ReaderIOResult[A]
type Thunk ¶
type Thunk[A any] = ReaderIOResult[A]