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:
- GetOptionSet: if GetOption(s) == Some(a), then GetOption(Set(a)(s)) == Some(a)
- SetGetOption: if GetOption(s) == Some(a), then Set(a)(s) preserves other parts of s
- 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)
Related Packages ¶
- 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 ¶
- func FromPredicate[S, A any](pred func(A) bool) func(func(S) A, func(S, A) S) Optional[S, A]
- func FromPredicateRef[S, A any](pred func(A) bool) func(func(*S) A, func(*S, A) *S) Optional[*S, A]
- func ModifyOption[S, A any](f func(A) A) func(Optional[S, A]) O.Kleisli[S, S]
- func SetOption[S, A any](a A) func(Optional[S, A]) O.Kleisli[S, S]
- type Kleisli
- type Operator
- func Compose[S, A, B any](ab Optional[A, B]) Operator[S, A, B]
- func ComposeRef[S, A, B any](ab Optional[A, B]) Operator[*S, A, B]
- func IChain[S, A, B any](ab func(A) O.Option[B], ba func(B) O.Option[A]) Operator[S, A, B]
- func IChainAny[S, A any]() Operator[S, any, A]
- func IMap[S, A, B any](ab func(A) B, ba func(B) A) Operator[S, A, B]
- type Optional
- func Id[S any]() Optional[S, S]
- func IdRef[S any]() Optional[*S, *S]
- func MakeOptional[S, A any](get O.Kleisli[S, A], set func(S, A) S) Optional[S, A]
- func MakeOptionalRef[S, A any](get O.Kleisli[*S, A], set func(*S, A) *S) Optional[*S, A]
- func MakeOptionalRefWithName[S, A any](get O.Kleisli[*S, A], set func(*S, A) *S, name string) Optional[*S, A]
- func MakeOptionalWithName[S, A any](get O.Kleisli[S, A], set func(S, A) S, name string) Optional[S, A]
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FromPredicate ¶
FromPredicate creates an optional from getter and setter functions. It checks for optional values and the correct update procedure
func FromPredicateRef ¶
FromPredicate creates an optional from getter and setter functions. It checks for optional values and the correct update procedure
Types ¶
type Kleisli ¶
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 ¶
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 ComposeRef ¶
ComposeRef combines two Optional and allows to narrow down the focus to a sub-Optional
func IChain ¶
IChain implements a bidirectional mapping of the transform if the transform can produce optionals (e.g. in case of type mappings)
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 MakeOptional ¶
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 ¶
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 MakeOptionalWithName ¶
func (Optional[S, T]) Format ¶
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 ¶
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 ¶
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"}