Skip to main content
Version: v2.2.82 (latest)
Reference · Core Type

StateReaderIOEither

The ultimate monad transformer. StateReaderIOEither[S, C, E, A] combines stateful computations (State), dependency injection (Reader), lazy evaluation (IO), and error handling (Either).

01

Overview

StateReaderIOEither combines all four powerful abstractions:

  • State[S]: Stateful computations
  • Reader[C]: Dependency injection
  • IO: Lazy evaluation and side effects
  • Either[E]: Error handling
type_definition.go
package statereaderioeither

// StateReaderIOEither combines all four abstractions
type StateReaderIOEither[S, C, E, A any] = 
  func(C) func(S) IO[Either[E, pair.Pair[A, S]]]

When to Use

Use CaseExample
Complex application stateGames, simulations
Stateful servicesSession management, transactions
Advanced scenariosWhen you need all four capabilities

Complexity Warning: Most applications don't need this level of complexity. Consider simpler alternatives first:

  • Effect (ReaderIOResult) for most applications
  • State for pure stateful computations
  • ReaderIOEither for effects with dependencies
02

Core API

Constructors

FunctionSignatureDescription
Offunc Of[S, C, E, A any](value A) StateReaderIOEither[S, C, E, A]Wrap pure value
Leftfunc Left[S, C, E, A any](err E) StateReaderIOEither[S, C, E, A]Create error
Rightfunc Right[S, C, E, A any](value A) StateReaderIOEither[S, C, E, A]Create success (alias for Of)

State Operations

FunctionSignatureDescription
Getfunc Get[S, C, E any]() StateReaderIOEither[S, C, E, S]Get current state
Putfunc Put[S, C, E any](s S) StateReaderIOEither[S, C, E, unit.Unit]Set new state
Modifyfunc Modify[S, C, E any](f func(S) S) StateReaderIOEither[S, C, E, unit.Unit]Modify state

Transformations

FunctionSignatureDescription
Mapfunc Map[S, C, E, A, B any](f func(A) B) func(StateReaderIOEither[S, C, E, A]) StateReaderIOEither[S, C, E, B]Transform value
Chainfunc Chain[S, C, E, A, B any](f func(A) StateReaderIOEither[S, C, E, B]) func(StateReaderIOEither[S, C, E, A]) StateReaderIOEither[S, C, E, B]Sequence operations

Dependencies

FunctionSignatureDescription
Askfunc Ask[S, C, E any]() StateReaderIOEither[S, C, E, C]Access dependencies
Asksfunc Asks[S, C, E, A any](f func(C) A) StateReaderIOEither[S, C, E, A]Access and transform
03

Usage Examples

Game State Example

game.go
package main

import (
  "fmt"
  SRIE "github.com/IBM/fp-go/v2/statereaderioeither"
  F "github.com/IBM/fp-go/v2/function"
)

type GameState struct {
  Score int
  Lives int
  Level int
}

type Dependencies struct {
  Logger *log.Logger
}

type GameError struct {
  Message string
}

func IncreaseScore(points int) SRIE.StateReaderIOEither[GameState, Dependencies, GameError, unit.Unit] {
  return func(deps Dependencies) func(state GameState) ioeither.IOEither[GameError, pair.Pair[unit.Unit, GameState]] {
      return func(s GameState) ioeither.IOEither[GameError, pair.Pair[unit.Unit, GameState]] {
          return ioeither.TryCatch(func() (pair.Pair[unit.Unit, GameState], GameError) {
              newState := s
              newState.Score += points
              deps.Logger.Printf("Score increased by %d", points)
              return pair.MakePair(unit.VOID, newState), GameError{}
          })
      }
  }
}

func LoseLife() SRIE.StateReaderIOEither[GameState, Dependencies, GameError, bool] {
  return func(deps Dependencies) func(state GameState) ioeither.IOEither[GameError, pair.Pair[bool, GameState]] {
      return func(s GameState) ioeither.IOEither[GameError, pair.Pair[bool, GameState]] {
          return ioeither.TryCatch(func() (pair.Pair[bool, GameState], GameError) {
              if s.Lives <= 0 {
                  return pair.MakePair(false, s), GameError{Message: "Game Over"}
              }
              newState := s
              newState.Lives--
              deps.Logger.Printf("Life lost. Lives remaining: %d", newState.Lives)
              return pair.MakePair(true, newState), GameError{}
          })
      }
  }
}

func main() {
  deps := Dependencies{Logger: log.New(os.Stdout, "", 0)}
  initialState := GameState{Score: 0, Lives: 3, Level: 1}
  
  game := F.Pipe2(
      IncreaseScore(100),
      SRIE.Chain(func(_ unit.Unit) SRIE.StateReaderIOEither[GameState, Dependencies, GameError, bool] {
          return LoseLife()
      }),
  )
  
  result := game(deps)(initialState)()
  fmt.Printf("%+v
", result)
}

Session Management

session.go
package main

import (
  SRIE "github.com/IBM/fp-go/v2/statereaderioeither"
)

type SessionState struct {
  UserID    string
  Token     string
  ExpiresAt time.Time
}

type AppDeps struct {
  DB    *sql.DB
  Cache *Cache
}

type SessionError struct {
  Code    int
  Message string
}

func ValidateSession() SRIE.StateReaderIOEither[SessionState, AppDeps, SessionError, bool] {
  return func(deps AppDeps) func(state SessionState) ioeither.IOEither[SessionError, pair.Pair[bool, SessionState]] {
      return func(s SessionState) ioeither.IOEither[SessionError, pair.Pair[bool, SessionState]] {
          return ioeither.TryCatch(func() (pair.Pair[bool, SessionState], SessionError) {
              if time.Now().After(s.ExpiresAt) {
                  return pair.MakePair(false, s), SessionError{
                      Code:    401,
                      Message: "Session expired",
                  }
              }
              return pair.MakePair(true, s), SessionError{}
          })
      }
  }
}

func RefreshSession() SRIE.StateReaderIOEither[SessionState, AppDeps, SessionError, unit.Unit] {
  return func(deps AppDeps) func(state SessionState) ioeither.IOEither[SessionError, pair.Pair[unit.Unit, SessionState]] {
      return func(s SessionState) ioeither.IOEither[SessionError, pair.Pair[unit.Unit, SessionState]] {
          return ioeither.TryCatch(func() (pair.Pair[unit.Unit, SessionState], SessionError) {
              newState := s
              newState.ExpiresAt = time.Now().Add(30 * time.Minute)
              
              // Update in cache
              deps.Cache.Set(s.UserID, newState)
              
              return pair.MakePair(unit.VOID, newState), SessionError{}
          })
      }
  }
}

Transaction Processing

transaction.go
package main

import (
  SRIE "github.com/IBM/fp-go/v2/statereaderioeither"
  F "github.com/IBM/fp-go/v2/function"
)

type TransactionState struct {
  Balance float64
  History []Transaction
}

type BankDeps struct {
  DB     *sql.DB
  Logger *log.Logger
}

type BankError struct {
  Code    string
  Message string
}

func Debit(amount float64) SRIE.StateReaderIOEither[TransactionState, BankDeps, BankError, unit.Unit] {
  return func(deps BankDeps) func(state TransactionState) ioeither.IOEither[BankError, pair.Pair[unit.Unit, TransactionState]] {
      return func(s TransactionState) ioeither.IOEither[BankError, pair.Pair[unit.Unit, TransactionState]] {
          return ioeither.TryCatch(func() (pair.Pair[unit.Unit, TransactionState], BankError) {
              if s.Balance < amount {
                  return pair.MakePair(unit.VOID, s), BankError{
                      Code:    "INSUFFICIENT_FUNDS",
                      Message: "Insufficient balance",
                  }
              }
              
              newState := s
              newState.Balance -= amount
              newState.History = append(newState.History, Transaction{
                  Type:   "DEBIT",
                  Amount: amount,
                  Time:   time.Now(),
              })
              
              deps.Logger.Printf("Debited: $%.2f", amount)
              return pair.MakePair(unit.VOID, newState), BankError{}
          })
      }
  }
}

func Credit(amount float64) SRIE.StateReaderIOEither[TransactionState, BankDeps, BankError, unit.Unit] {
  return func(deps BankDeps) func(state TransactionState) ioeither.IOEither[BankError, pair.Pair[unit.Unit, TransactionState]] {
      return func(s TransactionState) ioeither.IOEither[BankError, pair.Pair[unit.Unit, TransactionState]] {
          return ioeither.TryCatch(func() (pair.Pair[unit.Unit, TransactionState], BankError) {
              newState := s
              newState.Balance += amount
              newState.History = append(newState.History, Transaction{
                  Type:   "CREDIT",
                  Amount: amount,
                  Time:   time.Now(),
              })
              
              deps.Logger.Printf("Credited: $%.2f", amount)
              return pair.MakePair(unit.VOID, newState), BankError{}
          })
      }
  }
}

func Transfer(amount float64) SRIE.StateReaderIOEither[TransactionState, BankDeps, BankError, unit.Unit] {
  return F.Pipe2(
      Debit(amount),
      SRIE.Chain(func(_ unit.Unit) SRIE.StateReaderIOEither[TransactionState, BankDeps, BankError, unit.Unit] {
          return Credit(amount)
      }),
  )
}
04

Common Patterns

Pattern 1: Workflow Engine

workflow.go
package main

import (
  SRIE "github.com/IBM/fp-go/v2/statereaderioeither"
)

type WorkflowState struct {
  CurrentStep int
  Data        map[string]any
  Completed   bool
}

type WorkflowDeps struct {
  DB     *sql.DB
  Logger *log.Logger
}

type WorkflowError struct {
  Step    int
  Message string
}

func ExecuteStep(step int) SRIE.StateReaderIOEither[WorkflowState, WorkflowDeps, WorkflowError, unit.Unit] {
  return func(deps WorkflowDeps) func(state WorkflowState) ioeither.IOEither[WorkflowError, pair.Pair[unit.Unit, WorkflowState]] {
      return func(s WorkflowState) ioeither.IOEither[WorkflowError, pair.Pair[unit.Unit, WorkflowState]] {
          return ioeither.TryCatch(func() (pair.Pair[unit.Unit, WorkflowState], WorkflowError) {
              deps.Logger.Printf("Executing step %d", step)
              
              newState := s
              newState.CurrentStep = step
              
              // Execute step logic
              // ...
              
              return pair.MakePair(unit.VOID, newState), WorkflowError{}
          })
      }
  }
}

Pattern 2: State Machine

state_machine.go
package main

import (
  SRIE "github.com/IBM/fp-go/v2/statereaderioeither"
)

type MachineState struct {
  Current string
  History []string
}

type MachineDeps struct {
  Logger *log.Logger
}

type MachineError struct {
  From    string
  To      string
  Message string
}

func Transition(to string) SRIE.StateReaderIOEither[MachineState, MachineDeps, MachineError, unit.Unit] {
  return func(deps MachineDeps) func(state MachineState) ioeither.IOEither[MachineError, pair.Pair[unit.Unit, MachineState]] {
      return func(s MachineState) ioeither.IOEither[MachineError, pair.Pair[unit.Unit, MachineState]] {
          return ioeither.TryCatch(func() (pair.Pair[unit.Unit, MachineState], MachineError) {
              // Validate transition
              if !isValidTransition(s.Current, to) {
                  return pair.MakePair(unit.VOID, s), MachineError{
                      From:    s.Current,
                      To:      to,
                      Message: "Invalid transition",
                  }
              }
              
              newState := s
              newState.History = append(newState.History, s.Current)
              newState.Current = to
              
              deps.Logger.Printf("Transitioned: %s -> %s", s.Current, to)
              return pair.MakePair(unit.VOID, newState), MachineError{}
          })
      }
  }
}

When to Use: StateReaderIOEither is powerful but complex. Use it only when you truly need:

  1. Stateful computations that can't be avoided
  2. Dependency injection for testability
  3. Lazy evaluation for control flow
  4. Error handling with specific error types

For most cases, Effect (ReaderIOResult) is sufficient and simpler.