Reference · Core Type
ReaderIOEither
The most powerful combination: dependency injection (Reader) + lazy effects (IO) + custom error handling (Either). ReaderIOEither[C, E, A] is the ultimate monad stack.
01
Overview
type_definition.go
package readerioeither // ReaderIOEither combines Reader, IO, and Either type ReaderIOEither[C, E, A any] = Reader[C, IOEither[E, A]] // Which expands to: func(C) func() Either[E, A]
The Ultimate Stack
| Layer | Provides |
|---|---|
| Reader[C, ...] | Dependency injection |
| IO[...] | Lazy evaluation of side effects |
| Either[E, A] | Custom error handling |
02
Core API
Constructors
| Function | Signature | Description |
|---|---|---|
Right | func Right[C, E, A any](value A) ReaderIOEither[C, E, A] | Create successful value |
Left | func Left[C, E, A any](err E) ReaderIOEither[C, E, A] | Create error value |
Of | func Of[C, E, A any](value A) ReaderIOEither[C, E, A] | Alias for Right |
Ask | func Ask[C, E any]() ReaderIOEither[C, E, C] | Access context |
Asks | func Asks[C, E, A any](f func(C) A) ReaderIOEither[C, E, A] | Access and transform context |
Transformations
| Function | Signature | Description |
|---|---|---|
Map | func Map[C, E, A, B any](f func(A) B) func(ReaderIOEither[C, E, A]) ReaderIOEither[C, E, B] | Transform success value |
MapLeft | func MapLeft[C, A, E1, E2 any](f func(E1) E2) func(ReaderIOEither[C, E1, A]) ReaderIOEither[C, E2, A] | Transform error |
Chain | func Chain[C, E, A, B any](f func(A) ReaderIOEither[C, E, B]) func(ReaderIOEither[C, E, A]) ReaderIOEither[C, E, B] | Sequence operations |
Combining
| Function | Signature | Description |
|---|---|---|
Ap | func Ap[C, E, A, B any](fa ReaderIOEither[C, E, A]) func(ReaderIOEither[C, E, func(A) B]) ReaderIOEither[C, E, B] | Apply wrapped function |
SequenceArray | func SequenceArray[C, E, A any]([]ReaderIOEither[C, E, A]) ReaderIOEither[C, E, []A] | All-or-nothing |
03
Usage Examples
Basic Application
basic.go
package main
import (
RIOE "github.com/IBM/fp-go/v2/readerioeither"
)
type Dependencies struct {
DB *sql.DB
Logger *log.Logger
}
type AppError struct {
Code int
Message string
}
func fetchUser(id string) RIOE.ReaderIOEither[Dependencies, AppError, User] {
return RIOE.Ask[Dependencies, AppError, *sql.DB](func(deps Dependencies) *sql.DB {
return deps.DB
}).Chain(func(db *sql.DB) RIOE.ReaderIOEither[Dependencies, AppError, User] {
return RIOE.TryCatch(func() (User, AppError) {
user, err := queryUser(db, id)
if err != nil {
return User{}, AppError{Code: 500, Message: err.Error()}
}
return user, AppError{}
})
})
}
func main() {
deps := Dependencies{
DB: connectDB(),
Logger: log.New(os.Stdout, "", 0),
}
result := fetchUser("user-123")(deps)()
// Either[AppError, User]
}
Complete Application
complete_app.go
package main
import (
RIOE "github.com/IBM/fp-go/v2/readerioeither"
F "github.com/IBM/fp-go/v2/function"
)
type Config struct {
DBUrl string
Port int
}
type Dependencies struct {
DB *sql.DB
Logger *log.Logger
Config Config
}
type AppError struct {
Code int
Message string
}
func getDB() RIOE.ReaderIOEither[Dependencies, AppError, *sql.DB] {
return RIOE.Asks(func(deps Dependencies) *sql.DB {
return deps.DB
})
}
func logInfo(msg string) RIOE.ReaderIOEither[Dependencies, AppError, unit.Unit] {
return RIOE.Ask[Dependencies, AppError, *log.Logger](func(deps Dependencies) *log.Logger {
return deps.Logger
}).Chain(func(logger *log.Logger) RIOE.ReaderIOEither[Dependencies, AppError, unit.Unit] {
return RIOE.FromIO(IO.FromImpure(func() {
logger.Println(msg)
}))
})
}
func fetchUser(id string) RIOE.ReaderIOEither[Dependencies, AppError, User] {
return F.Pipe3(
logInfo("Fetching user: " + id),
RIOE.Chain(func(_ unit.Unit) RIOE.ReaderIOEither[Dependencies, AppError, *sql.DB] {
return getDB()
}),
RIOE.Chain(func(db *sql.DB) RIOE.ReaderIOEither[Dependencies, AppError, User] {
return RIOE.TryCatch(func() (User, AppError) {
user, err := db.QueryUser(id)
if err != nil {
return User{}, AppError{Code: 500, Message: err.Error()}
}
return user, AppError{}
})
}),
RIOE.ChainFirst(func(u User) RIOE.ReaderIOEither[Dependencies, AppError, unit.Unit] {
return logInfo("Found user: " + u.Name)
}),
)
}
func main() {
deps := Dependencies{
DB: connectDB(),
Logger: log.New(os.Stdout, "", 0),
Config: loadConfig(),
}
result := fetchUser("user-123")(deps)()
// Either[AppError, User]
}
04
Common Patterns
Pattern: Full-Stack Application
fullstack.go
package main
import (
RIOE "github.com/IBM/fp-go/v2/readerioeither"
F "github.com/IBM/fp-go/v2/function"
)
func processOrder(orderID string) RIOE.ReaderIOEither[Dependencies, AppError, Receipt] {
return F.Pipe4(
fetchOrder(orderID),
RIOE.Chain(validateOrder),
RIOE.Chain(chargePayment),
RIOE.Chain(sendConfirmation),
RIOE.MapLeft(func(err AppError) AppError {
return AppError{
Code: err.Code,
Message: fmt.Sprintf("Order processing failed: %s", err.Message),
}
}),
)
}
When to Use
| Use ReaderIOEither When | Consider Simpler Alternative |
|---|---|
| Need all three: DI + effects + custom errors | Use Reader if no effects needed |
| Building full applications | Use IOEither if no DI needed |
| Complex business logic | Use ReaderIO if errors are simple |
| Maximum composability required | Simpler types if features not needed |