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

Either

Generic disjoint union representing a value of one of two possible types. Either[E, A] can be Left (typically error) or Right (typically success).

01

Overview

Either represents a choice between two types:

  • Left: Value of type E (typically error or failure)
  • Right: Value of type A (typically success)

Unlike Result[A] which fixes the error type to error, Either[E, A] is generic over both types.

When to Use

Use Either WhenUse Result When
Custom error types with rich informationStandard Go error handling
Domain modeling with sum typesLibrary interoperability
Non-error alternatives (cache miss, etc.)Simpler type signatures
Type-level distinction between error typesWorking with existing Go code
02

Core API

Constructors

FunctionSignatureDescription
Leftfunc Left[A, E any](value E) Either[E, A]Create Left value (failure)
Rightfunc Right[E, A any](value A) Either[E, A]Create Right value (success)
Offunc Of[E, A any](value A) Either[E, A]Alias for Right (monadic pure)
TryCatchfunc TryCatch[E, A any](f func() (A, E)) Either[E, A]From function returning tuple
FromPredicatefunc FromPredicate[E, A any](pred func(A) bool, onFalse func(A) E) func(A) Either[E, A]Conditional Either

Pattern Matching

FunctionSignatureDescription
Foldfunc Fold[E, A, B any](onLeft func(E) B, onRight func(A) B) func(Either[E, A]) BExtract value from both cases
Matchfunc Match[E, A, B any](onLeft func(E) Either[E, B], onRight func(A) Either[E, B]) func(Either[E, A]) Either[E, B]Pattern match with Either return

Transformations

FunctionSignatureDescription
Mapfunc Map[E, A, B any](f func(A) B) func(Either[E, A]) Either[E, B]Transform Right value
MapLeftfunc MapLeft[A, E1, E2 any](f func(E1) E2) func(Either[E1, A]) Either[E2, A]Transform Left value
BiMapfunc BiMap[E1, E2, A, B any](fe func(E1) E2, fa func(A) B) func(Either[E1, A]) Either[E2, B]Transform both sides
Chainfunc Chain[E, A, B any](f func(A) Either[E, B]) func(Either[E, A]) Either[E, B]FlatMap - sequence operations

Combining

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

Extraction

FunctionSignatureDescription
Unwrapfunc Unwrap[A any](Either[error, A]) (A, error)Convert to tuple (only for Either[error, A])
GetOrElsefunc GetOrElse[E, A any](f func() A) func(Either[E, A]) AExtract with default
ToOptionfunc ToOption[E, A any](Either[E, A]) Option[A]Convert to Option (discards Left)

Testing

FunctionSignatureDescription
IsLeftfunc IsLeft[E, A any](Either[E, A]) boolTest for Left
IsRightfunc IsRight[E, A any](Either[E, A]) boolTest for Right
Existsfunc Exists[E, A any](pred func(A) bool) func(Either[E, A]) boolTest Right value
03

Usage Examples

Basic Operations

basic.go
package main

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

func main() {
  // Create Either values
  success := E.Right[string](42)
  failure := E.Left[int]("error message")
  
  // Check values
  fmt.Println(E.IsRight(success)) // true
  fmt.Println(E.IsLeft(failure))  // true
  
  // Pattern match
  result := E.Fold(
      func(err string) string { return "Error: " + err },
      func(n int) string { return fmt.Sprintf("Value: %d", n) },
  )(success)
  fmt.Println(result) // "Value: 42"
}

Custom Error Types

custom_errors.go
package main

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

type ValidationError struct {
  Field   string
  Message string
}

type AppError struct {
  Code      int
  Message   string
  Timestamp time.Time
}

func validateEmail(email string) E.Either[ValidationError, string] {
  if !strings.Contains(email, "@") {
      return E.Left[string](ValidationError{
          Field:   "email",
          Message: "must contain @",
      })
  }
  return E.Right[ValidationError](email)
}

func fetchUser(id string) E.Either[AppError, User] {
  if id == "" {
      return E.Left[User](AppError{
          Code:      400,
          Message:   "Invalid user ID",
          Timestamp: time.Now(),
      })
  }
  // ... fetch logic
  return E.Right[AppError](user)
}

Transformations

transformations.go
package main

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

func main() {
  // Map: transform Right value
  doubled := F.Pipe1(
      E.Right[string](21),
      E.Map(func(n int) int { return n * 2 }),
  )
  // Right(42)
  
  // MapLeft: transform Left value
  withContext := F.Pipe1(
      E.Left[int]("error"),
      E.MapLeft(func(msg string) AppError {
          return AppError{Code: 500, Message: msg}
      }),
  )
  // Left(AppError{Code: 500, Message: "error"})
  
  // Chain: sequence operations
  result := F.Pipe2(
      parseInput("42"),
      E.Chain(validateInput),
      E.Chain(processInput),
  )
}

Domain Modeling

domain_modeling.go
package main

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

// Payment methods as sum type
type CreditCard struct {
  Number string
  CVV    string
}

type BankTransfer struct {
  AccountNumber string
  RoutingNumber string
}

type PaymentMethod = E.Either[CreditCard, BankTransfer]

func processPayment(method PaymentMethod, amount float64) E.Either[string, Receipt] {
  return E.Fold(
      func(cc CreditCard) E.Either[string, Receipt] {
          return processCreditCard(cc, amount)
      },
      func(bt BankTransfer) E.Either[string, Receipt] {
          return processBankTransfer(bt, amount)
      },
  )(method)
}

// Cache results
type CacheMiss struct {
  Key    string
  Reason string
}

type CacheResult[A any] = E.Either[CacheMiss, A]

func getFromCache[A any](key string) CacheResult[A] {
  if val, ok := cache.Get(key); ok {
      return E.Right[CacheMiss](val.(A))
  }
  return E.Left[A](CacheMiss{
      Key:    key,
      Reason: "not found",
  })
}

Sequence Operations

sequence.go
package main

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

func main() {
  // Parse all - fails on first error
  parseAll := E.TraverseArray(func(s string) E.Either[string, int] {
      n, err := strconv.Atoi(s)
      if err != nil {
          return E.Left[int](err.Error())
      }
      return E.Right[string](n)
  })
  
  result := parseAll([]string{"1", "2", "3"})
  // Right([1, 2, 3])
  
  result = parseAll([]string{"1", "bad", "3"})
  // Left("invalid syntax")
}
04

Common Patterns

Pattern 1: Error Type Conversion

error_conversion.go
package main

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

type DBError struct{ Msg string }
type AppError struct{ Code int; Msg string }

func processOrder(id string) E.Either[AppError, Order] {
  return F.Pipe3(
      fetchOrder(id),  // Either[DBError, Order]
      E.MapLeft(func(e DBError) AppError {
          return AppError{Code: 500, Msg: e.Msg}
      }),
      E.Chain(validateOrder),  // Either[AppError, Order]
      E.Chain(saveOrder),
  )
}

Pattern 2: Validation with Multiple Errors

validation.go
package main

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

type ValidationErrors []ValidationError

func (ve ValidationErrors) Error() string {
  var msgs []string
  for _, e := range ve {
      msgs = append(msgs, fmt.Sprintf("%s: %s", e.Field, e.Message))
  }
  return strings.Join(msgs, "; ")
}

func validateUser(user User) E.Either[ValidationErrors, User] {
  var errors ValidationErrors
  
  if user.Name == "" {
      errors = append(errors, ValidationError{
          Field:   "name",
          Message: "required",
      })
  }
  
  if !strings.Contains(user.Email, "@") {
      errors = append(errors, ValidationError{
          Field:   "email",
          Message: "invalid format",
      })
  }
  
  if len(errors) > 0 {
      return E.Left[User](errors)
  }
  
  return E.Right[ValidationErrors](user)
}

Comparison: Either vs Result

Before
either_for_errors.go
// ❌ Don't use Either[error, A] for standard errors
func fetchUser(id string) E.Either[error, User] {
  // Use Result[User] instead
}
After
either_for_custom.go
// ✅ Use Either for custom error types
func fetchUser(id string) E.Either[AppError, User] {
  // Rich error information
}

// ✅ Use Result for standard errors
func fetchUser(id string) R.Result[User] {
  // Simpler, idiomatic Go
}