traversal

package
v2.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 20, 2025 License: Apache-2.0 Imports: 3 Imported by: 0

Documentation

Overview

Package traversal provides traversals - optics for focusing on multiple values simultaneously.

Overview

A Traversal is an optic that focuses on zero or more values within a data structure, allowing you to view, modify, or fold over multiple elements at once. Unlike lenses which focus on a single field, or prisms which focus on one variant, traversals can target collections, multiple fields, or any number of values.

Traversals are the most general optic and sit at the bottom of the optics hierarchy. They are essential for:

  • Working with collections (arrays, slices, maps)
  • Batch operations on multiple fields
  • Filtering and transforming multiple values
  • Aggregating data from multiple sources
  • Applying the same operation to all matching elements

Mathematical Foundation

A Traversal[S, A] is defined using higher-kinded types and applicative functors. In practical terms, it provides operations to:

  • Modify: Apply a function to all focused values
  • Set: Replace all focused values with a constant
  • FoldMap: Map each value to a monoid and combine results
  • GetAll: Collect all focused values into a list

Traversals must satisfy the traversal laws:

  1. Identity: traverse(Identity, id) == Identity
  2. Composition: traverse(Compose(F, G), f) == Compose(traverse(F, traverse(G, f)))

These laws ensure that traversals compose properly and behave consistently.

Basic Usage

Creating a traversal for array elements:

import (
	A "github.com/IBM/fp-go/v2/array"
	T "github.com/IBM/fp-go/v2/optics/traversal"
	TA "github.com/IBM/fp-go/v2/optics/traversal/array"
)

numbers := []int{1, 2, 3, 4, 5}

// Get all elements
all := T.GetAll(numbers)(TA.Traversal[int]())
// Result: [1, 2, 3, 4, 5]

// Modify all elements
doubled := F.Pipe2(
	numbers,
	TA.Traversal[int](),
	T.Modify[[]int, int](N.Mul(2)),
)
// Result: [2, 4, 6, 8, 10]

// Set all elements to a constant
allTens := F.Pipe2(
	numbers,
	TA.Traversal[int](),
	T.Set[[]int, int](10),
)
// Result: [10, 10, 10, 10, 10]

Identity Traversal

The identity traversal focuses on the entire structure:

idTrav := T.Id[int, int]()

value := 42
result := T.Modify[int, int](N.Mul(2))(idTrav)(value)
// Result: 84

Folding with Traversals

Aggregate values using monoids:

import (
	M "github.com/IBM/fp-go/v2/monoid"
	N "github.com/IBM/fp-go/v2/number"
)

numbers := []int{1, 2, 3, 4, 5}

// Sum all elements
sum := F.Pipe2(
	numbers,
	TA.Traversal[int](),
	T.FoldMap[int, []int, int](F.Identity[int]),
)(N.MonoidSum[int]())
// Result: 15

// Product of all elements
product := F.Pipe2(
	numbers,
	TA.Traversal[int](),
	T.FoldMap[int, []int, int](F.Identity[int]),
)(N.MonoidProduct[int]())
// Result: 120

Composing Traversals

Traversals can be composed to focus on nested collections:

type Person struct {
	Name    string
	Friends []string
}

people := []Person{
	{Name: "Alice", Friends: []string{"Bob", "Charlie"}},
	{Name: "Bob", Friends: []string{"Alice", "David"}},
}

// Traversal for people array
peopleTrav := TA.Traversal[Person]()

// Traversal for friends array within a person
friendsTrav := T.MakeTraversal(func(p Person) []string {
	return p.Friends
})

// Compose to access all friends of all people
allFriendsTrav := F.Pipe1(
	peopleTrav,
	T.Compose[[]Person, Person, string, ...](friendsTrav),
)

// Get all friends
allFriends := T.GetAll(people)(allFriendsTrav)
// Result: ["Bob", "Charlie", "Alice", "David"]

Working with Records (Maps)

Traverse over map values:

import TR "github.com/IBM/fp-go/v2/optics/traversal/record"

scores := map[string]int{
	"Alice": 85,
	"Bob":   92,
	"Charlie": 78,
}

// Get all scores
allScores := F.Pipe2(
	scores,
	TR.Traversal[string, int](),
	T.GetAll[map[string]int, int],
)
// Result: [85, 92, 78] (order may vary)

// Increase all scores by 5
boosted := F.Pipe2(
	scores,
	TR.Traversal[string, int](),
	T.Modify[map[string]int, int](func(score int) int {
		return score + 5
	}),
)
// Result: {"Alice": 90, "Bob": 97, "Charlie": 83}

Working with Either Types

Traverse over the Right values:

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

results := []E.Either[string, int]{
	E.Right[string](10),
	E.Left[int]("error"),
	E.Right[string](20),
}

// Traversal for array of Either
arrayTrav := TA.Traversal[E.Either[string, int]]()

// Traversal for Right values
rightTrav := TE.Traversal[string, int]()

// Compose to access all Right values
allRightsTrav := F.Pipe1(
	arrayTrav,
	T.Compose[[]E.Either[string, int], E.Either[string, int], int, ...](rightTrav),
)

// Get all Right values
rights := T.GetAll(results)(allRightsTrav)
// Result: [10, 20]

// Double all Right values
doubled := F.Pipe2(
	results,
	allRightsTrav,
	T.Modify[[]E.Either[string, int], int](N.Mul(2)),
)
// Result: [Right(20), Left("error"), Right(40)]

Working with Option Types

Traverse over Some values:

import (
	O "github.com/IBM/fp-go/v2/option"
	TO "github.com/IBM/fp-go/v2/optics/traversal/option"
)

values := []O.Option[int]{
	O.Some(1),
	O.None[int](),
	O.Some(2),
	O.None[int](),
	O.Some(3),
}

// Compose array and option traversals
allSomesTrav := F.Pipe1(
	TA.Traversal[O.Option[int]](),
	T.Compose[[]O.Option[int], O.Option[int], int, ...](TO.Traversal[int]()),
)

// Get all Some values
somes := T.GetAll(values)(allSomesTrav)
// Result: [1, 2, 3]

// Increment all Some values
incremented := F.Pipe2(
	values,
	allSomesTrav,
	T.Modify[[]O.Option[int], int](N.Add(1)),
)
// Result: [Some(2), None, Some(3), None, Some(4)]

Real-World Example: Nested Data Structures

type Department struct {
	Name      string
	Employees []Employee
}

type Employee struct {
	Name   string
	Salary int
}

company := []Department{
	{
		Name: "Engineering",
		Employees: []Employee{
			{Name: "Alice", Salary: 100000},
			{Name: "Bob", Salary: 95000},
		},
	},
	{
		Name: "Sales",
		Employees: []Employee{
			{Name: "Charlie", Salary: 80000},
			{Name: "David", Salary: 85000},
		},
	},
}

// Traversal for departments
deptTrav := TA.Traversal[Department]()

// Traversal for employees within a department
empTrav := T.MakeTraversal(func(d Department) []Employee {
	return d.Employees
})

// Traversal for employee array
empArrayTrav := TA.Traversal[Employee]()

// Compose to access all employees
allEmpTrav := F.Pipe2(
	deptTrav,
	T.Compose[[]Department, Department, []Employee, ...](empTrav),
	T.Compose[[]Department, []Employee, Employee, ...](empArrayTrav),
)

// Get all employee names
names := F.Pipe2(
	company,
	allEmpTrav,
	T.FoldMap[[]string, []Department, Employee](func(e Employee) []string {
		return []string{e.Name}
	}),
)(A.Monoid[string]())
// Result: ["Alice", "Bob", "Charlie", "David"]

// Give everyone a 10% raise
withRaises := F.Pipe2(
	company,
	allEmpTrav,
	T.Modify[[]Department, Employee](func(e Employee) Employee {
		e.Salary = int(float64(e.Salary) * 1.1)
		return e
	}),
)

Real-World Example: Filtering with Traversals

type Product struct {
	Name  string
	Price float64
	InStock bool
}

products := []Product{
	{Name: "Laptop", Price: 999.99, InStock: true},
	{Name: "Mouse", Price: 29.99, InStock: false},
	{Name: "Keyboard", Price: 79.99, InStock: true},
}

// Create a traversal that only focuses on in-stock products
inStockTrav := T.MakeTraversal(func(ps []Product) []Product {
	return A.Filter(func(p Product) bool {
		return p.InStock
	})(ps)
})

// Apply discount to in-stock items
discounted := F.Pipe2(
	products,
	inStockTrav,
	T.Modify[[]Product, Product](func(p Product) Product {
		p.Price = p.Price * 0.9
		return p
	}),
)
// Only Laptop and Keyboard prices are reduced

Real-World Example: Data Aggregation

type Order struct {
	ID     string
	Items  []OrderItem
	Status string
}

type OrderItem struct {
	Product  string
	Quantity int
	Price    float64
}

orders := []Order{
	{
		ID: "001",
		Items: []OrderItem{
			{Product: "Widget", Quantity: 2, Price: 10.0},
			{Product: "Gadget", Quantity: 1, Price: 25.0},
		},
		Status: "completed",
	},
	{
		ID: "002",
		Items: []OrderItem{
			{Product: "Widget", Quantity: 5, Price: 10.0},
		},
		Status: "completed",
	},
}

// Traversal for orders
orderTrav := TA.Traversal[Order]()

// Traversal for items within an order
itemsTrav := T.MakeTraversal(func(o Order) []OrderItem {
	return o.Items
})

// Traversal for item array
itemArrayTrav := TA.Traversal[OrderItem]()

// Compose to access all items
allItemsTrav := F.Pipe2(
	orderTrav,
	T.Compose[[]Order, Order, []OrderItem, ...](itemsTrav),
	T.Compose[[]Order, []OrderItem, OrderItem, ...](itemArrayTrav),
)

// Calculate total revenue
totalRevenue := F.Pipe2(
	orders,
	allItemsTrav,
	T.FoldMap[float64, []Order, OrderItem](func(item OrderItem) float64 {
		return float64(item.Quantity) * item.Price
	}),
)(N.MonoidSum[float64]())
// Result: 95.0 (2*10 + 1*25 + 5*10)

Traversals in the Optics Hierarchy

Traversals are the most general optic:

Iso[S, A]
    ↓
Lens[S, A]
    ↓
Optional[S, A]
    ↓
Traversal[S, A]

Prism[S, A]
    ↓
Optional[S, A]
    ↓
Traversal[S, A]

This means:

  • Every Iso, Lens, Prism, and Optional can be converted to a Traversal
  • Traversals are the most flexible but least specific optic
  • Use more specific optics when possible for better type safety

Performance Considerations

Traversals can be efficient but consider:

  • Each traversal operation may iterate over all elements
  • Composition creates nested iterations
  • FoldMap is often more efficient than GetAll followed by reduction
  • Modify creates new copies (immutability)

For best performance:

  • Use specialized traversals (array, record, etc.) when available
  • Avoid unnecessary composition
  • Consider batch operations
  • Cache composed traversals

Type Safety

Traversals are fully type-safe:

  • Compile-time type checking
  • Generic type parameters ensure correctness
  • Composition maintains type relationships
  • No runtime type assertions

Function Reference

Core Functions:

  • Id: Create an identity traversal
  • Modify: Apply a function to all focused values
  • Set: Replace all focused values with a constant
  • Compose: Compose two traversals

Aggregation:

  • FoldMap: Map each value to a monoid and combine
  • Fold: Fold over all values using a monoid
  • GetAll: Collect all focused values into a list

Specialized Traversals

The package includes specialized sub-packages for common patterns:

  • array: Traversals for arrays and slices
  • record: Traversals for maps
  • either: Traversals for Either types
  • option: Traversals for Option types

Each specialized package provides optimized implementations for its data type.

  • github.com/IBM/fp-go/v2/optics/lens: Lenses for single fields
  • github.com/IBM/fp-go/v2/optics/prism: Prisms for sum types
  • github.com/IBM/fp-go/v2/optics/optional: Optionals for maybe values
  • github.com/IBM/fp-go/v2/optics/traversal/array: Array traversals
  • github.com/IBM/fp-go/v2/optics/traversal/record: Record/map traversals
  • github.com/IBM/fp-go/v2/optics/traversal/either: Either traversals
  • github.com/IBM/fp-go/v2/optics/traversal/option: Option traversals
  • github.com/IBM/fp-go/v2/array: Array utilities
  • github.com/IBM/fp-go/v2/monoid: Monoid type class

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Compose

func Compose[
	S, A, B, HKTS, HKTA, HKTB any](ab G.Traversal[A, B, HKTA, HKTB]) func(sa G.Traversal[S, A, HKTS, HKTA]) G.Traversal[S, B, HKTS, HKTB]

Compose composes two traversables

func Fold

func Fold[S, A any](sa G.Traversal[S, A, C.Const[A, S], C.Const[A, A]]) func(S) A

Fold maps each target to a `Monoid` and combines the result

func FoldMap

func FoldMap[M, S, A any](f func(A) M) func(sa G.Traversal[S, A, C.Const[M, S], C.Const[M, A]]) func(S) M

FoldMap maps each target to a `Monoid` and combines the result

func GetAll

func GetAll[S, A any](s S) func(sa G.Traversal[S, A, C.Const[[]A, S], C.Const[[]A, A]]) []A

GetAll gets all the targets of a traversal

func Id

func Id[S, A any]() G.Traversal[S, S, A, A]

Id is the identity constructor of a traversal

func Modify

func Modify[S, A any](f func(A) A) func(sa G.Traversal[S, A, S, A]) func(S) S

Modify applies a transformation function to a traversal

func Set

func Set[S, A any](a A) func(sa G.Traversal[S, A, S, A]) func(S) S

Set sets a constant value for all values of the traversal

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL