optional

package
v2.1.25 Latest Latest
Warning

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

Go to latest
Published: Jan 23, 2026 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)

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

Overview

Optional is an optic used to zoom inside a product. Unlike the Lens, the element that the Optional focuses on may not exist. An Optional[S, A] represents a relationship between a source type S and a focus type A, where the focus may or may not be present.

Optional Laws

An Optional must satisfy the following laws, which are consistent with other functional programming libraries such as monocle-ts (https://gcanti.github.io/monocle-ts/modules/Optional.ts.html) and the Haskell lens library (https://hackage.haskell.org/package/lens):

  1. GetSet Law (No-op on None): If GetOption(s) returns None, then Set(a)(s) must return s unchanged (no-op). This ensures that attempting to update a value that doesn't exist has no effect.

    Formally: GetOption(s) = None => Set(a)(s) = s

  2. SetGet Law (Get what you Set): If GetOption(s) returns Some(_), then GetOption(Set(a)(s)) must return Some(a). This ensures that after setting a value, you can retrieve it.

    Formally: GetOption(s) = Some(_) => GetOption(Set(a)(s)) = Some(a)

  3. SetSet Law (Last Set Wins): Setting twice is the same as setting once with the final value.

    Formally: Set(b)(Set(a)(s)) = Set(b)(s)

No-op Behavior

A key property of Optional is that updating a value for which GetOption returns None is a no-op. This behavior is implemented through the optionalModify function, which only applies the modification if the optional value exists. When GetOption returns None, the original structure is returned unchanged.

This is consistent with the behavior in:

  • monocle-ts: Optional.modify returns the original value when the optional doesn't match
  • Haskell lens: over and set operations are no-ops when the traversal finds no targets

Example

type Person struct {
    Name string
    Age  int
}

// Create an optional that focuses on non-empty names
nameOptional := MakeOptional(
    func(p Person) option.Option[string] {
        if p.Name != "" {
            return option.Some(p.Name)
        }
        return option.None[string]()
    },
    func(p Person, name string) Person {
        p.Name = name
        return p
    },
)

// When the optional matches, Set updates the value
person1 := Person{Name: "Alice", Age: 30}
updated1 := nameOptional.Set("Bob")(person1)
// updated1.Name == "Bob"

// When the optional doesn't match (Name is empty), Set is a no-op
person2 := Person{Name: "", Age: 30}
updated2 := nameOptional.Set("Bob")(person2)
// updated2 == person2 (unchanged)

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 MakeOptionalCurried added in v2.1.20

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

func MakeOptionalCurriedWithName added in v2.1.20

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

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 MakeOptionalRefCurriedWithName added in v2.1.20

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

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