optional

package
v2.0.0 Latest Latest
Warning

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

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

Documentation

Overview

Package optional provides optional optics for focusing on values that may not exist.

Overview

An Optional is an optic that focuses on a subpart of a data structure that may or may not be present. Unlike lenses which always focus on an existing field, optionals handle cases where the target value might be absent, returning Option[A] instead of A.

Optionals are the bridge between lenses (which always succeed) and prisms (which may fail to match). They combine aspects of both:

  • Like lenses: Focus on a specific location in a structure
  • Like prisms: The value at that location may not exist

Optionals are essential for:

  • Working with nullable fields (pointers that may be nil)
  • Accessing nested optional values
  • Conditional updates based on value presence
  • Safe navigation through potentially missing data

Mathematical Foundation

An Optional[S, A] consists of two operations:

  • GetOption: S → Option[A] (try to extract A from S, may return None)
  • Set: A → S → S (update A in S, may be a no-op if value doesn't exist)

Optionals must satisfy the optional laws:

  1. GetOptionSet: if GetOption(s) == Some(a), then GetOption(Set(a)(s)) == Some(a)
  2. SetGetOption: if GetOption(s) == Some(a), then Set(a)(s) preserves other parts of s
  3. SetSet: Set(a2)(Set(a1)(s)) == Set(a2)(s)

Basic Usage

Creating an optional for a nullable field:

type Config struct {
	Timeout *int
	MaxSize *int
}

timeoutOptional := optional.MakeOptional(
	func(c Config) option.Option[*int] {
		return option.FromNillable(c.Timeout)
	},
	func(c Config, t *int) Config {
		c.Timeout = t
		return c
	},
)

config := Config{Timeout: nil, MaxSize: ptr(100)}

// Get returns None for nil
timeout := timeoutOptional.GetOption(config) // None[*int]

// Set updates the value
newTimeout := 30
updated := timeoutOptional.Set(&newTimeout)(config)
// updated.Timeout points to 30

Working with Pointers

For pointer-based structures, use MakeOptionalRef which handles copying automatically:

type Database struct {
	Host string
	Port int
}

type Config struct {
	Database *Database
}

dbOptional := optional.MakeOptionalRef(
	func(c *Config) option.Option[*Database] {
		return option.FromNillable(c.Database)
	},
	func(c *Config, db *Database) *Config {
		c.Database = db
		return c
	},
)

config := &Config{Database: nil}

// Get returns None when database is nil
db := dbOptional.GetOption(config) // None[*Database]

// Set creates a new config with the database
newDB := &Database{Host: "localhost", Port: 5432}
updated := dbOptional.Set(newDB)(config)
// config.Database is still nil, updated.Database points to newDB

Identity Optional

The identity optional focuses on the entire structure:

idOpt := optional.Id[Config]()

config := Config{Timeout: ptr(30)}
value := idOpt.GetOption(config) // Some(config)
updated := idOpt.Set(Config{Timeout: ptr(60)})(config)

Composing Optionals

Optionals can be composed to navigate through nested optional structures:

type Address struct {
	Street string
	City   string
}

type Person struct {
	Name    string
	Address *Address
}

addressOpt := optional.MakeOptional(
	func(p Person) option.Option[*Address] {
		return option.FromNillable(p.Address)
	},
	func(p Person, a *Address) Person {
		p.Address = a
		return p
	},
)

cityOpt := optional.MakeOptionalRef(
	func(a *Address) option.Option[string] {
		if a == nil {
			return option.None[string]()
		}
		return option.Some(a.City)
	},
	func(a *Address, city string) *Address {
		a.City = city
		return a
	},
)

// Compose to access city from person
personCityOpt := F.Pipe1(
	addressOpt,
	optional.Compose[Person, *Address, string](cityOpt),
)

person := Person{Name: "Alice", Address: nil}

// Get returns None when address is nil
city := personCityOpt.GetOption(person) // None[string]

// Set updates the city if address exists
withAddress := Person{
	Name:    "Alice",
	Address: &Address{Street: "Main St", City: "NYC"},
}
updated := personCityOpt.Set("Boston")(withAddress)
// updated.Address.City == "Boston"

From Predicate

Create an optional that only focuses on values satisfying a predicate:

type User struct {
	Age int
}

ageOpt := optional.FromPredicate[User, int](
	func(age int) bool { return age >= 18 },
)(
	func(u User) int { return u.Age },
	func(u User, age int) User {
		u.Age = age
		return u
	},
)

adult := User{Age: 25}
age := ageOpt.GetOption(adult) // Some(25)

minor := User{Age: 15}
minorAge := ageOpt.GetOption(minor) // None[int]

// Set only works if predicate is satisfied
updated := ageOpt.Set(30)(adult) // Age becomes 30
unchanged := ageOpt.Set(30)(minor) // Age stays 15 (predicate fails)

Modifying Values

Use ModifyOption to transform values that exist:

type Counter struct {
	Value *int
}

valueOpt := optional.MakeOptional(
	func(c Counter) option.Option[*int] {
		return option.FromNillable(c.Value)
	},
	func(c Counter, v *int) Counter {
		c.Value = v
		return c
	},
)

counter := Counter{Value: ptr(5)}

// Increment if value exists
incremented := F.Pipe3(
	counter,
	valueOpt,
	optional.ModifyOption[Counter, *int](func(v *int) *int {
		newVal := *v + 1
		return &newVal
	}),
	option.GetOrElse(F.Constant(counter)),
)
// incremented.Value points to 6

// No change if value is nil
nilCounter := Counter{Value: nil}
result := F.Pipe3(
	nilCounter,
	valueOpt,
	optional.ModifyOption[Counter, *int](func(v *int) *int {
		newVal := *v + 1
		return &newVal
	}),
	option.GetOrElse(F.Constant(nilCounter)),
)
// result.Value is still nil

Bidirectional Mapping

Transform the focus type of an optional:

type Celsius float64
type Fahrenheit float64

type Weather struct {
	Temperature *Celsius
}

tempCelsiusOpt := optional.MakeOptional(
	func(w Weather) option.Option[*Celsius] {
		return option.FromNillable(w.Temperature)
	},
	func(w Weather, t *Celsius) Weather {
		w.Temperature = t
		return w
	},
)

// Create optional that works with Fahrenheit
tempFahrenheitOpt := F.Pipe1(
	tempCelsiusOpt,
	optional.IMap[Weather, *Celsius, *Fahrenheit](
		func(c *Celsius) *Fahrenheit {
			f := Fahrenheit(*c*9/5 + 32)
			return &f
		},
		func(f *Fahrenheit) *Celsius {
			c := Celsius((*f - 32) * 5 / 9)
			return &c
		},
	),
)

celsius := Celsius(20)
weather := Weather{Temperature: &celsius}

tempF := tempFahrenheitOpt.GetOption(weather) // Some(68°F)

Real-World Example: Configuration with Defaults

type DatabaseConfig struct {
	Host     string
	Port     int
	Username string
	Password string
}

type AppConfig struct {
	Database *DatabaseConfig
	Debug    bool
}

dbOpt := optional.MakeOptional(
	func(c AppConfig) option.Option[*DatabaseConfig] {
		return option.FromNillable(c.Database)
	},
	func(c AppConfig, db *DatabaseConfig) AppConfig {
		c.Database = db
		return c
	},
)

dbHostOpt := optional.MakeOptionalRef(
	func(db *DatabaseConfig) option.Option[string] {
		if db == nil {
			return option.None[string]()
		}
		return option.Some(db.Host)
	},
	func(db *DatabaseConfig, host string) *DatabaseConfig {
		db.Host = host
		return db
	},
)

// Compose to access database host
appDbHostOpt := F.Pipe1(
	dbOpt,
	optional.Compose[AppConfig, *DatabaseConfig, string](dbHostOpt),
)

config := AppConfig{Database: nil, Debug: true}

// Get returns None when database is not configured
host := appDbHostOpt.GetOption(config) // None[string]

// Set creates database if needed
withDB := AppConfig{
	Database: &DatabaseConfig{Host: "localhost", Port: 5432},
	Debug:    true,
}
updated := appDbHostOpt.Set("prod.example.com")(withDB)
// updated.Database.Host == "prod.example.com"

Real-World Example: Safe Navigation

type Company struct {
	Name string
	CEO  *Person
}

type Person struct {
	Name    string
	Address *Address
}

type Address struct {
	City string
}

ceoOpt := optional.MakeOptional(
	func(c Company) option.Option[*Person] {
		return option.FromNillable(c.CEO)
	},
	func(c Company, p *Person) Company {
		c.CEO = p
		return c
	},
)

addressOpt := optional.MakeOptionalRef(
	func(p *Person) option.Option[*Address] {
		return option.FromNillable(p.Address)
	},
	func(p *Person, a *Address) *Person {
		p.Address = a
		return p
	},
)

cityOpt := optional.MakeOptionalRef(
	func(a *Address) option.Option[string] {
		if a == nil {
			return option.None[string]()
		}
		return option.Some(a.City)
	},
	func(a *Address, city string) *Address {
		a.City = city
		return a
	},
)

// Compose all optionals for safe navigation
ceoCityOpt := F.Pipe2(
	ceoOpt,
	optional.Compose[Company, *Person, *Address](addressOpt),
	optional.Compose[Company, *Address, string](cityOpt),
)

company := Company{Name: "Acme Corp", CEO: nil}

// Safe navigation returns None at any missing level
city := ceoCityOpt.GetOption(company) // None[string]

Optionals in the Optics Hierarchy

Optionals sit between lenses and traversals in the optics hierarchy:

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

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

This means:

  • Every Lens can be converted to an Optional (value always exists)
  • Every Prism can be converted to an Optional (variant may not match)
  • Every Optional can be converted to a Traversal (0 or 1 values)

Performance Considerations

Optionals are efficient:

  • No reflection - all operations are type-safe at compile time
  • Minimal allocations - optionals themselves are lightweight
  • GetOption short-circuits on None
  • Set operations create new copies (immutability)

For best performance:

  • Use MakeOptionalRef for pointer structures to ensure proper copying
  • Cache composed optionals rather than recomposing
  • Consider batch operations when updating multiple optional values

Type Safety

Optionals are fully type-safe:

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

Function Reference

Core Optional Creation:

  • MakeOptional: Create an optional from getter and setter functions
  • MakeOptionalRef: Create an optional for pointer-based structures
  • Id: Create an identity optional
  • IdRef: Create an identity optional for pointers

Composition:

  • Compose: Compose two optionals
  • ComposeRef: Compose optionals for pointer structures

Transformation:

  • ModifyOption: Transform a value through an optional (returns Option[S])
  • SetOption: Set a value through an optional (returns Option[S])
  • IMap: Bidirectionally map an optional
  • IChain: Bidirectionally map with optional results
  • IChainAny: Map to/from any type

Predicate-Based:

  • FromPredicate: Create optional from predicate
  • FromPredicateRef: Create optional from predicate (ref version)
  • github.com/IBM/fp-go/v2/optics/lens: Lenses for fields that always exist
  • github.com/IBM/fp-go/v2/optics/prism: Prisms for sum types
  • github.com/IBM/fp-go/v2/optics/traversal: Traversals for multiple values
  • github.com/IBM/fp-go/v2/option: Optional values
  • github.com/IBM/fp-go/v2/endomorphism: Endomorphisms (A → A functions)

Optional is an optic used to zoom inside a product. Unlike the `Lens`, the element that the `Optional` focuses on may not exist.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FromPredicate

func FromPredicate[S, A any](pred func(A) bool) func(func(S) A, func(S, A) S) Optional[S, A]

FromPredicate creates an optional from getter and setter functions. It checks for optional values and the correct update procedure

func FromPredicateRef

func FromPredicateRef[S, A any](pred func(A) bool) func(func(*S) A, func(*S, A) *S) Optional[*S, A]

FromPredicate creates an optional from getter and setter functions. It checks for optional values and the correct update procedure

func ModifyOption

func ModifyOption[S, A any](f func(A) A) func(Optional[S, A]) O.Kleisli[S, S]

func SetOption

func SetOption[S, A any](a A) func(Optional[S, A]) O.Kleisli[S, S]

Types

type Kleisli

type Kleisli[S, A, B any] = func(A) Optional[S, B]

Kleisli represents a function that takes a value of type A and returns an Optional[S, B]. This is commonly used for composing optionals in a monadic style.

Type Parameters:

  • S: The source type of the resulting optional
  • A: The input type to the function
  • B: The focus type of the resulting optional

type Operator

type Operator[S, A, B any] = func(Optional[S, A]) Optional[S, B]

Operator represents a function that transforms one optional into another. It takes an Optional[S, A] and returns an Optional[S, B], allowing for optional transformations.

Type Parameters:

  • S: The source type (remains constant)
  • A: The original focus type
  • B: The new focus type

func Compose

func Compose[S, A, B any](ab Optional[A, B]) Operator[S, A, B]

Compose combines two Optional and allows to narrow down the focus to a sub-Optional

func ComposeRef

func ComposeRef[S, A, B any](ab Optional[A, B]) Operator[*S, A, B]

ComposeRef combines two Optional and allows to narrow down the focus to a sub-Optional

func IChain

func IChain[S, A, B any](ab func(A) O.Option[B], ba func(B) O.Option[A]) Operator[S, A, B]

IChain implements a bidirectional mapping of the transform if the transform can produce optionals (e.g. in case of type mappings)

func IChainAny

func IChainAny[S, A any]() Operator[S, any, A]

IChainAny implements a bidirectional mapping to and from any

func IMap

func IMap[S, A, B any](ab func(A) B, ba func(B) A) Operator[S, A, B]

IMap implements a bidirectional mapping of the transform

type Optional

type Optional[S, A any] struct {
	GetOption func(s S) O.Option[A]
	Set       func(a A) EM.Endomorphism[S]
	// contains filtered or unexported fields
}

Optional is an optional reference to a subpart of a data type

func Id

func Id[S any]() Optional[S, S]

Id returns am optional implementing the identity operation

func IdRef

func IdRef[S any]() Optional[*S, *S]

Id returns am optional implementing the identity operation

func MakeOptional

func MakeOptional[S, A any](get O.Kleisli[S, A], set func(S, A) S) Optional[S, A]

MakeOptional creates an Optional based on a getter and a setter function. Make sure that the setter creates a (shallow) copy of the data. This happens automatically if the data is passed by value. For pointers consider to use `MakeOptionalRef` and for other kinds of data structures that are copied by reference make sure the setter creates the copy.

func MakeOptionalRef

func MakeOptionalRef[S, A any](get O.Kleisli[*S, A], set func(*S, A) *S) Optional[*S, A]

MakeOptionalRef creates an Optional based on a getter and a setter function. The setter passed in does not have to create a shallow copy, the implementation wraps the setter into one that copies the pointer before modifying it

func MakeOptionalRefWithName

func MakeOptionalRefWithName[S, A any](get O.Kleisli[*S, A], set func(*S, A) *S, name string) Optional[*S, A]

func MakeOptionalWithName

func MakeOptionalWithName[S, A any](get O.Kleisli[S, A], set func(S, A) S, name string) Optional[S, A]

func (Optional[S, T]) Format

func (o Optional[S, T]) Format(f fmt.State, c rune)

Format implements fmt.Formatter for Optional. Supports all standard format verbs:

  • %s, %v, %+v: uses String() representation (optional name)
  • %#v: uses GoString() representation
  • %q: quoted String() representation
  • other verbs: uses String() representation

Example:

fieldOptional := optional.MakeOptionalWithName(..., "Person.Email")
fmt.Printf("%s", fieldOptional)   // "Person.Email"
fmt.Printf("%v", fieldOptional)   // "Person.Email"
fmt.Printf("%#v", fieldOptional)  // "optional.Optional[Person, string]{name: \"Person.Email\"}"

func (Optional[S, T]) GoString

func (o Optional[S, T]) GoString() string

GoString implements fmt.GoStringer for Optional. Returns a Go-syntax representation of the Optional value.

Example:

fieldOptional := optional.MakeOptionalWithName(..., "Person.Email")
fieldOptional.GoString() // "optional.Optional[Person, string]{name: \"Person.Email\"}"

func (Optional[S, T]) LogValue

func (o Optional[S, T]) LogValue() slog.Value

LogValue implements slog.LogValuer for Optional. Returns a slog.Value that represents the Optional for structured logging. Logs the optional name as a string value.

Example:

logger := slog.Default()
fieldOptional := optional.MakeOptionalWithName(..., "Person.Email")
logger.Info("using optional", "optional", fieldOptional)
// Logs: {"msg":"using optional","optional":"Person.Email"}

func (Optional[S, T]) String

func (o Optional[S, T]) String() string

String returns the name of the optional for debugging and display purposes.

Example:

fieldOptional := optional.MakeOptionalWithName(..., "Person.Email")
fmt.Println(fieldOptional)  // Prints: "Person.Email"

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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