Skip to main content
Version: v2.2.82 (latest)
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

LayerProvides
Reader[C, ...]Dependency injection
IO[...]Lazy evaluation of side effects
Either[E, A]Custom error handling
02

Core API

Constructors

FunctionSignatureDescription
Rightfunc Right[C, E, A any](value A) ReaderIOEither[C, E, A]Create successful value
Leftfunc Left[C, E, A any](err E) ReaderIOEither[C, E, A]Create error value
Offunc Of[C, E, A any](value A) ReaderIOEither[C, E, A]Alias for Right
Askfunc Ask[C, E any]() ReaderIOEither[C, E, C]Access context
Asksfunc Asks[C, E, A any](f func(C) A) ReaderIOEither[C, E, A]Access and transform context

Transformations

FunctionSignatureDescription
Mapfunc Map[C, E, A, B any](f func(A) B) func(ReaderIOEither[C, E, A]) ReaderIOEither[C, E, B]Transform success value
MapLeftfunc MapLeft[C, A, E1, E2 any](f func(E1) E2) func(ReaderIOEither[C, E1, A]) ReaderIOEither[C, E2, A]Transform error
Chainfunc 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

FunctionSignatureDescription
Apfunc 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
SequenceArrayfunc 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 WhenConsider Simpler Alternative
Need all three: DI + effects + custom errorsUse Reader if no effects needed
Building full applicationsUse IOEither if no DI needed
Complex business logicUse ReaderIO if errors are simple
Maximum composability requiredSimpler types if features not needed