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

Effect

Complete solution for real-world applications. Effect[C, A] combines dependency injection (Reader), lazy evaluation (IO), and error handling (Result) into one powerful abstraction.

01

Overview

Effect is the most powerful type in fp-go, combining three essential abstractions:

  • Reader[C]: Dependency injection and context passing
  • IO: Lazy evaluation and side effect management
  • Result[A]: Type-safe error handling
type_definition.go
package effect

// Effect is an alias for ReaderIOResult
type Effect[C, A any] = ReaderIOResult[C, A]
// Which expands to: func(C) IO[Result[A]]
// Or fully: func(C) func() Either[error, A]

Why Effect?

CapabilityBenefit
Dependency InjectionType-safe, testable dependencies
Lazy EvaluationControl when effects execute
Error HandlingType-safe, composable errors
TestabilityEasy to mock dependencies
ComposabilityBuild complex operations from simple ones
02

Core API

Constructors

FunctionSignatureDescription
Succeedfunc Succeed[C, A any](value A) Effect[C, A]Create success value
Failfunc Fail[C, A any](err error) Effect[C, A]Create failure
Offunc Of[C, A any](value A) Effect[C, A]Alias for Succeed
FromIOResultfunc FromIOResult[C, A any](ior IOResult[A]) Effect[C, A]Lift IOResult
FromIOfunc FromIO[C, A any](io IO[A]) Effect[C, A]Lift IO (always succeeds)
Askfunc Ask[C, A any](f func(C) A) Effect[C, A]Access dependencies

Transformations

FunctionSignatureDescription
Mapfunc Map[C, A, B any](f func(A) B) func(Effect[C, A]) Effect[C, B]Transform success value
Chainfunc Chain[C, A, B any](f func(A) Effect[C, B]) func(Effect[C, A]) Effect[C, B]Sequence operations
Tapfunc Tap[C, A any](f func(A) Effect[C, any]) func(Effect[C, A]) Effect[C, A]Side effect, keep value
BiMapfunc BiMap[C, A, B any](fe func(error) error, fa func(A) B) func(Effect[C, A]) Effect[C, B]Transform both sides

Dependencies

FunctionSignatureDescription
Asksfunc Asks[C, A any](f func(C) A) Effect[C, A]Access and transform context
Providefunc Provide[C, A any](ctx C) func(Effect[C, A]) IOResult[A]Supply dependencies
Localfunc Local[C1, C2, A any](f func(C1) C2) func(Effect[C2, A]) Effect[C1, A]Transform context

Error Handling

FunctionSignatureDescription
OrElsefunc OrElse[C, A any](f func(error) Effect[C, A]) func(Effect[C, A]) Effect[C, A]Fallback on error
MapErrorfunc MapError[C, A any](f func(error) error) func(Effect[C, A]) Effect[C, A]Transform error
Foldfunc Fold[C, A, B any](onError func(error) B, onOk func(A) B) func(Effect[C, A]) Reader[C, IO[B]]Pattern match

Combining

FunctionSignatureDescription
Apfunc Ap[C, A, B any](fa Effect[C, A]) func(Effect[C, func(A) B]) Effect[C, B]Apply wrapped function
SequenceArrayfunc SequenceArray[C, A any]([]Effect[C, A]) Effect[C, []A]All-or-nothing for arrays
TraverseArrayfunc TraverseArray[C, A, B any](f func(A) Effect[C, B]) func([]A) Effect[C, []B]Map and sequence
03

Usage Examples

Basic Operations

basic.go
package main

import (
  "errors"
  E "github.com/IBM/fp-go/v2/effect"
)

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

func main() {
  // Create effects
  success := E.Succeed[Dependencies, string]("Hello")
  failure := E.Fail[Dependencies, string](errors.New("error"))
  
  // Access dependencies
  getDB := E.Ask[Dependencies, *sql.DB](func(deps Dependencies) *sql.DB {
      return deps.DB
  })
  
  // Execute with dependencies
  deps := Dependencies{DB: connectDB(), Logger: log.Default()}
  result := success(deps)()  // Result[string] = Ok("Hello")
}

Service Layer

service.go
package main

import (
  E "github.com/IBM/fp-go/v2/effect"
  F "github.com/IBM/fp-go/v2/function"
)

type Dependencies struct {
  DB     *sql.DB
  Cache  *Cache
  Logger *log.Logger
}

type UserService struct{}

func (s *UserService) GetUser(id string) E.Effect[Dependencies, User] {
  return F.Pipe3(
      // Try cache first
      s.getUserFromCache(id),
      E.OrElse(func(err error) E.Effect[Dependencies, User] {
          // Fallback to database
          return s.getUserFromDB(id)
      }),
      E.Tap(s.logUser),
      E.Chain(s.validateUser),
  )
}

func (s *UserService) getUserFromCache(id string) E.Effect[Dependencies, User] {
  return E.Ask[Dependencies, User](func(deps Dependencies) User {
      if user, ok := deps.Cache.Get(id); ok {
          return user
      }
      return User{} // Will trigger OrElse
  })
}

func (s *UserService) getUserFromDB(id string) E.Effect[Dependencies, User] {
  return E.Ask[Dependencies, User](func(deps Dependencies) User {
      var user User
      err := deps.DB.QueryRow(
          "SELECT id, name, email FROM users WHERE id = ?", id,
      ).Scan(&user.ID, &user.Name, &user.Email)
      
      if err != nil {
          return User{}
      }
      return user
  })
}

func (s *UserService) logUser(user User) E.Effect[Dependencies, any] {
  return E.Ask[Dependencies, any](func(deps Dependencies) any {
      deps.Logger.Printf("Processing user: %s", user.ID)
      return nil
  })
}

func (s *UserService) validateUser(user User) E.Effect[Dependencies, User] {
  if user.Name == "" {
      return E.Fail[Dependencies, User](errors.New("invalid user"))
  }
  return E.Succeed[Dependencies, User](user)
}

HTTP Handler

http.go
package main

import (
  "encoding/json"
  "net/http"
  E "github.com/IBM/fp-go/v2/effect"
  F "github.com/IBM/fp-go/v2/function"
)

type HTTPDeps struct {
  UserService *UserService
  Logger      *log.Logger
}

func GetUserHandler(deps HTTPDeps) http.HandlerFunc {
  return func(w http.ResponseWriter, r *http.Request) {
      id := r.URL.Query().Get("id")
      
      // Create effect
      eff := F.Pipe2(
          deps.UserService.GetUser(id),
          E.Map[HTTPDeps](func(u User) []byte {
              data, _ := json.Marshal(u)
              return data
          }),
      )
      
      // Execute effect
      result := eff(deps)()
      
      // Handle result
      if result.IsError(result) {
          http.Error(w, "Error", http.StatusInternalServerError)
          return
      }
      
      data, _ := result.Unwrap(result)
      w.Header().Set("Content-Type", "application/json")
      w.Write(data)
  }
}

Transaction Management

transaction.go
package main

import (
  E "github.com/IBM/fp-go/v2/effect"
  F "github.com/IBM/fp-go/v2/function"
)

type TxDeps struct {
  DB *sql.DB
}

func WithTransaction[A any](
  eff E.Effect[TxDeps, A],
) E.Effect[TxDeps, A] {
  return E.Ask[TxDeps, A](func(deps TxDeps) A {
      tx, err := deps.DB.Begin()
      if err != nil {
          return *new(A)
      }
      
      // Execute with transaction
      txDeps := TxDeps{DB: tx}
      result := eff(txDeps)()
      
      // Commit or rollback
      if result.IsError(result) {
          tx.Rollback()
          return *new(A)
      }
      
      if err := tx.Commit(); err != nil {
          return *new(A)
      }
      
      val, _ := result.Unwrap(result)
      return val
  })
}

func TransferFunds(from, to string, amount float64) E.Effect[TxDeps, unit.Unit] {
  return WithTransaction(
      F.Pipe2(
          debit(from, amount),
          E.Chain[TxDeps](func(_ unit.Unit) E.Effect[TxDeps, unit.Unit] {
              return credit(to, amount)
          }),
      ),
  )
}

Do Notation

do_notation.go
package main

import (
  E "github.com/IBM/fp-go/v2/effect"
  F "github.com/IBM/fp-go/v2/function"
)

type State struct {
  User   User
  Orders []Order
  Total  float64
}

func GenerateReport(userID string) E.Effect[Dependencies, Report] {
  return F.Pipe4(
      E.Do[Dependencies](State{}),
      E.Bind("User", func(s State) E.Effect[Dependencies, User] {
          return fetchUser(userID)
      }),
      E.Bind("Orders", func(s State) E.Effect[Dependencies, []Order] {
          return fetchOrders(s.User.ID)
      }),
      E.Let("Total", func(s State) float64 {
          var total float64
          for _, order := range s.Orders {
              total += order.Amount
          }
          return total
      }),
      E.Map[Dependencies](func(s State) Report {
          return Report{
              User:   s.User,
              Orders: s.Orders,
              Total:  s.Total,
          }
      }),
  )
}
04

Common Patterns

Pattern 1: Middleware

middleware.go
package main

import (
  E "github.com/IBM/fp-go/v2/effect"
)

func LoggingMiddleware[A any](
  next E.Effect[HTTPDeps, A],
) E.Effect[HTTPDeps, A] {
  return E.Tap[HTTPDeps](func(resp A) E.Effect[HTTPDeps, any] {
      return E.Ask[HTTPDeps, any](func(deps HTTPDeps) any {
          deps.Logger.Printf("Response: %+v", resp)
          return nil
      })
  })(next)
}

func AuthMiddleware[A any](
  next E.Effect[HTTPDeps, A],
) E.Effect[HTTPDeps, A] {
  return E.Chain[HTTPDeps](func(_ any) E.Effect[HTTPDeps, A] {
      return next
  })(validateAuth())
}

Pattern 2: Fallback Chain

fallback.go
package main

import (
  E "github.com/IBM/fp-go/v2/effect"
  F "github.com/IBM/fp-go/v2/function"
)

func GetConfig(key string) E.Effect[Dependencies, string] {
  return F.Pipe3(
      GetFromEnv(key),
      E.OrElse(func(err error) E.Effect[Dependencies, string] {
          return GetFromFile(key)
      }),
      E.OrElse(func(err error) E.Effect[Dependencies, string] {
          return GetFromDefaults(key)
      }),
  )
}

Pattern 3: Testing

testing.go
package main

import (
  "testing"
  E "github.com/IBM/fp-go/v2/effect"
)

func TestProcessOrder(t *testing.T) {
  // Mock dependencies
  mockDeps := Dependencies{
      DB: &MockDB{
          Orders: map[string]Order{
              "123": {ID: "123", Total: 100.0},
          },
      },
      Logger: log.New(io.Discard, "", 0),
      Config: Config{},
  }
  
  // Execute effect with mocks
  result := ProcessOrder("123")(mockDeps)()
  
  // Assert
  assert.True(t, result.IsOk(result))
  order, _ := result.Unwrap(result)
  assert.Equal(t, "123", order.ID)
}
05

Type Comparison

FeatureEffect[C, A]IOResult[A]ReaderIOResult[C, A]
Dependencies✅ Yes❌ No✅ Yes
Lazy✅ Yes✅ Yes✅ Yes
Can fail✅ Yes✅ Yes✅ Yes
Type params212
Use caseFull applicationsSimple IOSame as Effect

Note: Effect is an alias for ReaderIOResult - they are the same type with different naming conventions inspired by effect-ts.

06

Best Practices

✅ Do

do.go
// Use Effect for application logic
func ProcessUser(id string) effect.Effect[Dependencies, User]

// Compose effects
result := F.Pipe3(
  fetchUser(id),
  effect.Chain[Dependencies](validateUser),
  effect.Chain[Dependencies](saveUser),
)

// Provide dependencies at the edge
func main() {
  deps := setupDependencies()
  result := ProcessUser("123")(deps)()
}

// Test with mock dependencies
func TestProcessUser(t *testing.T) {
  mockDeps := createMockDeps()
  result := ProcessUser("123")(mockDeps)()
}

❌ Don't

dont.go
// Don't execute effects in constructors
func NewService() *Service {
  data := fetchData()(deps)()  // ❌ Executes immediately
  return &Service{data: data}
}

// Don't pass dependencies as parameters
func ProcessUser(deps Dependencies, id string) effect.Effect[Dependencies, User]  // ❌
func ProcessUser(id string) effect.Effect[Dependencies, User]  // ✅

// Don't ignore errors
effect.Map[Dependencies](func(u User) User {
  saveUser(u)(deps)()  // ❌ Error is lost
  return u
})