mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-23 22:14:53 +02:00
306 lines
7.9 KiB
Go
306 lines
7.9 KiB
Go
// Copyright (c) 2023 - 2025 IBM Corp.
|
|
// All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
/*
|
|
Package option provides isomorphisms for working with Option types.
|
|
|
|
# Overview
|
|
|
|
This package offers utilities to convert between regular values and Option-wrapped values,
|
|
particularly useful for handling zero values and optional data. It provides isomorphisms
|
|
that treat certain values (like zero values) as representing absence, mapping them to None,
|
|
while other values map to Some.
|
|
|
|
# Core Functionality
|
|
|
|
The main function in this package is FromZero, which creates an isomorphism between a
|
|
comparable type T and Option[T], treating the zero value as None.
|
|
|
|
# FromZero Isomorphism
|
|
|
|
FromZero creates a bidirectional transformation where:
|
|
- Forward (Get): T → Option[T]
|
|
- Zero value → None
|
|
- Non-zero value → Some(value)
|
|
- Reverse (ReverseGet): Option[T] → T
|
|
- None → Zero value
|
|
- Some(value) → value
|
|
|
|
# Basic Usage
|
|
|
|
Working with integers:
|
|
|
|
import (
|
|
"github.com/IBM/fp-go/v2/optics/iso/option"
|
|
O "github.com/IBM/fp-go/v2/option"
|
|
)
|
|
|
|
isoInt := option.FromZero[int]()
|
|
|
|
// Convert zero to None
|
|
opt := isoInt.Get(0) // None[int]
|
|
|
|
// Convert non-zero to Some
|
|
opt = isoInt.Get(42) // Some(42)
|
|
|
|
// Convert None to zero
|
|
val := isoInt.ReverseGet(O.None[int]()) // 0
|
|
|
|
// Convert Some to value
|
|
val = isoInt.ReverseGet(O.Some(42)) // 42
|
|
|
|
# Use Cases
|
|
|
|
## Database Nullable Columns
|
|
|
|
Convert between database NULL and Go zero values:
|
|
|
|
type User struct {
|
|
ID int
|
|
Name string
|
|
Age *int // NULL in database
|
|
Email *string
|
|
}
|
|
|
|
ageIso := option.FromZero[*int]()
|
|
|
|
// Reading from database
|
|
var dbAge *int = nil
|
|
optAge := ageIso.Get(dbAge) // None[*int]
|
|
|
|
// Writing to database
|
|
userAge := 25
|
|
dbAge = ageIso.ReverseGet(O.Some(&userAge)) // &25
|
|
|
|
## Configuration with Defaults
|
|
|
|
Handle optional configuration values:
|
|
|
|
type Config struct {
|
|
Port int
|
|
Timeout int
|
|
MaxConn int
|
|
}
|
|
|
|
portIso := option.FromZero[int]()
|
|
|
|
// Use zero as "not configured"
|
|
config := Config{Port: 0, Timeout: 30, MaxConn: 100}
|
|
portOpt := portIso.Get(config.Port) // None[int] (use default)
|
|
|
|
// Set explicit value
|
|
config.Port = portIso.ReverseGet(O.Some(8080)) // 8080
|
|
|
|
## API Response Handling
|
|
|
|
Work with APIs that use zero values to indicate absence:
|
|
|
|
type APIResponse struct {
|
|
UserID int // 0 means not set
|
|
Score float64 // 0.0 means not available
|
|
Message string // "" means no message
|
|
}
|
|
|
|
userIDIso := option.FromZero[int]()
|
|
scoreIso := option.FromZero[float64]()
|
|
messageIso := option.FromZero[string]()
|
|
|
|
response := APIResponse{UserID: 0, Score: 0.0, Message: ""}
|
|
|
|
userID := userIDIso.Get(response.UserID) // None[int]
|
|
score := scoreIso.Get(response.Score) // None[float64]
|
|
message := messageIso.Get(response.Message) // None[string]
|
|
|
|
## Validation Logic
|
|
|
|
Simplify required vs optional field validation:
|
|
|
|
type FormData struct {
|
|
Name string // Required
|
|
Email string // Required
|
|
Phone string // Optional (empty = not provided)
|
|
Comments string // Optional
|
|
}
|
|
|
|
phoneIso := option.FromZero[string]()
|
|
commentsIso := option.FromZero[string]()
|
|
|
|
form := FormData{
|
|
Name: "Alice",
|
|
Email: "alice@example.com",
|
|
Phone: "",
|
|
Comments: "",
|
|
}
|
|
|
|
// Check optional fields
|
|
phone := phoneIso.Get(form.Phone) // None[string]
|
|
comments := commentsIso.Get(form.Comments) // None[string]
|
|
|
|
// Validate: required fields must be non-empty
|
|
if form.Name == "" || form.Email == "" {
|
|
// Validation error
|
|
}
|
|
|
|
# Working with Different Types
|
|
|
|
## Strings
|
|
|
|
strIso := option.FromZero[string]()
|
|
|
|
opt := strIso.Get("") // None[string]
|
|
opt = strIso.Get("hello") // Some("hello")
|
|
|
|
val := strIso.ReverseGet(O.None[string]()) // ""
|
|
val = strIso.ReverseGet(O.Some("world")) // "world"
|
|
|
|
## Pointers
|
|
|
|
ptrIso := option.FromZero[*int]()
|
|
|
|
opt := ptrIso.Get(nil) // None[*int]
|
|
num := 42
|
|
opt = ptrIso.Get(&num) // Some(&num)
|
|
|
|
val := ptrIso.ReverseGet(O.None[*int]()) // nil
|
|
val = ptrIso.ReverseGet(O.Some(&num)) // &num
|
|
|
|
## Floating Point Numbers
|
|
|
|
floatIso := option.FromZero[float64]()
|
|
|
|
opt := floatIso.Get(0.0) // None[float64]
|
|
opt = floatIso.Get(3.14) // Some(3.14)
|
|
|
|
val := floatIso.ReverseGet(O.None[float64]()) // 0.0
|
|
val = floatIso.ReverseGet(O.Some(2.71)) // 2.71
|
|
|
|
## Booleans
|
|
|
|
boolIso := option.FromZero[bool]()
|
|
|
|
opt := boolIso.Get(false) // None[bool]
|
|
opt = boolIso.Get(true) // Some(true)
|
|
|
|
val := boolIso.ReverseGet(O.None[bool]()) // false
|
|
val = boolIso.ReverseGet(O.Some(true)) // true
|
|
|
|
# Composition with Other Optics
|
|
|
|
Combine with lenses for nested structures:
|
|
|
|
import (
|
|
L "github.com/IBM/fp-go/v2/optics/lens"
|
|
I "github.com/IBM/fp-go/v2/optics/iso"
|
|
)
|
|
|
|
type Settings struct {
|
|
Volume int // 0 means muted
|
|
}
|
|
|
|
volumeLens := L.MakeLens(
|
|
func(s Settings) int { return s.Volume },
|
|
func(s Settings, v int) Settings {
|
|
s.Volume = v
|
|
return s
|
|
},
|
|
)
|
|
|
|
volumeIso := option.FromZero[int]()
|
|
|
|
// Compose lens with iso
|
|
volumeOptLens := F.Pipe1(
|
|
volumeLens,
|
|
L.IMap[Settings](volumeIso.Get, volumeIso.ReverseGet),
|
|
)
|
|
|
|
settings := Settings{Volume: 0}
|
|
vol := volumeOptLens.Get(settings) // None[int] (muted)
|
|
|
|
// Set volume
|
|
updated := volumeOptLens.Set(O.Some(75))(settings)
|
|
// updated.Volume == 75
|
|
|
|
# Isomorphism Laws
|
|
|
|
FromZero satisfies the isomorphism round-trip laws:
|
|
|
|
1. **ReverseGet(Get(t)) == t** for all t: T
|
|
|
|
isoInt := option.FromZero[int]()
|
|
value := 42
|
|
result := isoInt.ReverseGet(isoInt.Get(value))
|
|
// result == 42
|
|
|
|
2. **Get(ReverseGet(opt)) == opt** for all opt: Option[T]
|
|
|
|
isoInt := option.FromZero[int]()
|
|
opt := O.Some(42)
|
|
result := isoInt.Get(isoInt.ReverseGet(opt))
|
|
// result == Some(42)
|
|
|
|
These laws ensure that the transformation is truly reversible with no information loss.
|
|
|
|
# Performance Considerations
|
|
|
|
The FromZero isomorphism is very efficient:
|
|
- No allocations for the iso structure itself
|
|
- Simple equality comparison for zero check
|
|
- Direct value unwrapping for ReverseGet
|
|
- No reflection or runtime type assertions
|
|
|
|
# Type Safety
|
|
|
|
The isomorphism is fully type-safe:
|
|
- Compile-time type checking ensures T is comparable
|
|
- Generic type parameters prevent type mismatches
|
|
- No runtime type assertions needed
|
|
- The compiler enforces correct usage
|
|
|
|
# Limitations
|
|
|
|
The FromZero isomorphism has some limitations to be aware of:
|
|
|
|
1. **Zero Value Ambiguity**: Cannot distinguish between "intentionally zero" and "absent"
|
|
- For int: 0 always maps to None, even if 0 is a valid value
|
|
- For string: "" always maps to None, even if empty string is valid
|
|
- Solution: Use a different representation (e.g., pointers) if zero is meaningful
|
|
|
|
2. **Comparable Constraint**: Only works with comparable types
|
|
- Cannot use with slices, maps, or functions
|
|
- Cannot use with structs containing non-comparable fields
|
|
- Solution: Use pointers to such types, or custom isomorphisms
|
|
|
|
3. **Boolean Limitation**: false always maps to None
|
|
- Cannot represent "explicitly false" vs "not set"
|
|
- Solution: Use *bool or a custom type if this distinction matters
|
|
|
|
# Related Packages
|
|
|
|
- github.com/IBM/fp-go/v2/optics/iso: Core isomorphism functionality
|
|
- github.com/IBM/fp-go/v2/option: Option type and operations
|
|
- github.com/IBM/fp-go/v2/optics/lens: Lenses for focused access
|
|
- github.com/IBM/fp-go/v2/optics/lens/option: Lenses for optional values
|
|
|
|
# See Also
|
|
|
|
For more information on isomorphisms and optics:
|
|
- optics/iso package documentation
|
|
- optics package overview
|
|
- option package documentation
|
|
*/
|
|
package option
|
|
|
|
// Made with Bob
|