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

Record Traverse

Working with maps of effectful computations. Traverse operations allow you to map over maps with effects and sequence the results.


Core API

FunctionSignatureDescription
Traversefunc Traverse[K comparable, A, B, F any](Applicative[F]) func(func(A) HKT[F, B]) func(map[K]A) HKT[F, map[K]B]Map and sequence
TraverseWithIndexfunc TraverseWithIndex[K comparable, A, B, F any](Applicative[F]) func(func(K, A) HKT[F, B]) func(map[K]A) HKT[F, map[K]B]Traverse with keys
Sequencefunc Sequence[K comparable, A, F any](Applicative[F]) func(map[K]HKT[F, A]) HKT[F, map[K]A]Flip map and effect

Usage Examples

Traverse with Option

option.go
import (
  R "github.com/IBM/fp-go/v2/record"
  O "github.com/IBM/fp-go/v2/option"
  F "github.com/IBM/fp-go/v2/function"
)

m := map[string]int{"a": 1, "b": 2, "c": 3}

// Validate all values
validated := R.Traverse(O.Applicative[int]())(
  func(v int) O.Option[int] {
      if v > 0 {
          return O.Some(v)
      }
      return O.None[int]()
  },
)(m)
// Some(map[string]int{"a": 1, "b": 2, "c": 3})

// With invalid value
m2 := map[string]int{"a": 1, "b": -1, "c": 3}
validated2 := R.Traverse(O.Applicative[int]())(
  func(v int) O.Option[int] {
      if v > 0 {
          return O.Some(v)
      }
      return O.None[int]()
  },
)(m2)
// None - one invalid value fails all

Traverse with Result

result.go
import Res "github.com/IBM/fp-go/v2/result"

configs := map[string]string{
  "port":    "8080",
  "timeout": "30",
  "retries": "abc",  // Invalid
}

// Parse all values
parsed := R.Traverse(Res.Applicative[error, int]())(
  func(s string) Res.Result[int] {
      if n, err := strconv.Atoi(s); err == nil {
          return Res.Success(n)
      } else {
          return Res.Error[int](err)
      }
  },
)(configs)
// Error - "abc" is not a valid integer

TraverseWithIndex

with_index.go
configs := map[string]string{
  "api_url": "https://api.example.com",
  "timeout": "30",
  "retries": "3",
}

// Parse with context from keys
parsed := R.TraverseWithIndex(O.Applicative[int]())(
  func(key string, value string) O.Option[int] {
      if key == "api_url" {
          // Skip non-numeric configs
          return O.Some(0)
      }
      if n, err := strconv.Atoi(value); err == nil {
          return O.Some(n)
      }
      return O.None[int]()
  },
)(configs)
// Option[map[string]int]

Sequence

sequence.go
// Map of Options
options := map[string]O.Option[int]{
  "a": O.Some(1),
  "b": O.Some(2),
  "c": O.Some(3),
}

// Flip to Option of map
result := R.Sequence(O.Applicative[int]())(options)
// Some(map[string]int{"a": 1, "b": 2, "c": 3})

// With None
withNone := map[string]O.Option[int]{
  "a": O.Some(1),
  "b": O.None[int](),
  "c": O.Some(3),
}

result2 := R.Sequence(O.Applicative[int]())(withNone)
// None

Configuration Parsing

config_parse.go
type Config struct {
  Port    int
  Timeout int
  Retries int
}

raw := map[string]string{
  "port":    "8080",
  "timeout": "30",
  "retries": "3",
}

// Parse all fields
parsed := R.Traverse(Res.Applicative[error, int]())(
  func(s string) Res.Result[int] {
      n, err := strconv.Atoi(s)
      if err != nil {
          return Res.Error[int](err)
      }
      return Res.Success(n)
  },
)(raw)

// Build config from result
config := F.Pipe2(
  parsed,
  Res.Map(func(m map[string]int) Config {
      return Config{
          Port:    m["port"],
          Timeout: m["timeout"],
          Retries: m["retries"],
      }
  }),
)
// Result[Config]

Validation Suite

validation.go
type User struct {
  Email string
  Age   int
}

user := User{Email: "test@example.com", Age: 25}

validators := map[string]func(User) Res.Result[bool]{
  "email": func(u User) Res.Result[bool] {
      if strings.Contains(u.Email, "@") {
          return Res.Success(true)
      }
      return Res.Error[bool](errors.New("invalid email"))
  },
  "age": func(u User) Res.Result[bool] {
      if u.Age >= 18 {
          return Res.Success(true)
      }
      return Res.Error[bool](errors.New("must be 18+"))
  },
}

// Run all validations
results := R.Traverse(Res.Applicative[error, bool]())(
  func(validate func(User) Res.Result[bool]) Res.Result[bool] {
      return validate(user)
  },
)(validators)
// Result[map[string]bool] - Success if all pass

API Batch Requests

api.go
import IOE "github.com/IBM/fp-go/v2/ioeither"

type UserData struct {
  ID   int
  Name string
}

userIDs := map[string]int{
  "alice": 1,
  "bob":   2,
  "charlie": 3,
}

// Fetch all users
fetchAll := R.Traverse(IOE.Applicative[error, UserData]())(
  func(id int) IOE.IOEither[error, UserData] {
      return fetchUserAPI(id)
  },
)(userIDs)
// IOEither[error, map[string]UserData]

// Execute
users := fetchAll()
// Either[error, map[string]UserData]

Common Patterns

All or Nothing Validation

all_or_nothing.go
// Validate all entries - fail if any invalid
func ValidateAll(data map[string]string) Res.Result[map[string]int] {
  return R.Traverse(Res.Applicative[error, int]())(
      func(s string) Res.Result[int] {
          n, err := strconv.Atoi(s)
          if err != nil {
              return Res.Error[int](
                  fmt.Errorf("invalid value: %s", s),
              )
          }
          return Res.Success(n)
      },
  )(data)
}

Collecting Errors

collect_errors.go
// Validate with error collection
type ValidationErrors []error

func ValidateWithErrors(
  data map[string]string,
) (map[string]int, ValidationErrors) {
  result := make(map[string]int)
  var errors ValidationErrors
  
  for k, v := range data {
      if n, err := strconv.Atoi(v); err == nil {
          result[k] = n
      } else {
          errors = append(errors, 
              fmt.Errorf("%s: %w", k, err))
      }
  }
  
  return result, errors
}

Conditional Processing

conditional.go
// Process only certain keys
func ProcessSelected(
  data map[string]string,
  keys []string,
) O.Option[map[string]int] {
  selected := make(map[string]string)
  for _, k := range keys {
      if v, ok := data[k]; ok {
          selected[k] = v
      }
  }
  
  return R.Traverse(O.Applicative[int]())(
      func(s string) O.Option[int] {
          if n, err := strconv.Atoi(s); err == nil {
              return O.Some(n)
          }
          return O.None[int]()
      },
  )(selected)
}

Short-Circuit Behavior: Traverse operations short-circuit on the first failure. With Option, the first None returns None. With Result, the first Error returns Error.

Use Cases: Traverse is ideal for:

  • Validating all map values
  • Parsing configuration maps
  • Batch API requests
  • All-or-nothing transformations