1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-23 22:14:53 +02:00

fix: examples

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
Dr. Carsten Leue
2025-11-13 09:05:57 +01:00
parent d2dbce6e8b
commit d586428cb0
10 changed files with 2332 additions and 218 deletions

View File

@@ -76,15 +76,25 @@ type templateData struct {
const lensStructTemplate = ` const lensStructTemplate = `
// {{.Name}}Lenses provides lenses for accessing fields of {{.Name}} // {{.Name}}Lenses provides lenses for accessing fields of {{.Name}}
type {{.Name}}Lenses struct { type {{.Name}}Lenses struct {
// mandatory fields
{{- range .Fields}} {{- range .Fields}}
{{.Name}} {{if .IsOptional}}LO.LensO[{{$.Name}}, {{.TypeName}}]{{else}}L.Lens[{{$.Name}}, {{.TypeName}}]{{end}} {{.Name}} L.Lens[{{$.Name}}, {{.TypeName}}]
{{- end}}
// optional fields
{{- range .Fields}}
{{.Name}}O LO.LensO[{{$.Name}}, {{.TypeName}}]
{{- end}} {{- end}}
} }
// {{.Name}}RefLenses provides lenses for accessing fields of {{.Name}} via a reference to {{.Name}} // {{.Name}}RefLenses provides lenses for accessing fields of {{.Name}} via a reference to {{.Name}}
type {{.Name}}RefLenses struct { type {{.Name}}RefLenses struct {
// mandatory fields
{{- range .Fields}} {{- range .Fields}}
{{.Name}} {{if .IsOptional}}LO.LensO[*{{$.Name}}, {{.TypeName}}]{{else}}L.Lens[*{{$.Name}}, {{.TypeName}}]{{end}} {{.Name}} L.Lens[*{{$.Name}}, {{.TypeName}}]
{{- end}}
// optional fields
{{- range .Fields}}
{{.Name}}O LO.LensO[*{{$.Name}}, {{.TypeName}}]
{{- end}} {{- end}}
} }
` `
@@ -92,57 +102,57 @@ type {{.Name}}RefLenses struct {
const lensConstructorTemplate = ` const lensConstructorTemplate = `
// Make{{.Name}}Lenses creates a new {{.Name}}Lenses with lenses for all fields // Make{{.Name}}Lenses creates a new {{.Name}}Lenses with lenses for all fields
func Make{{.Name}}Lenses() {{.Name}}Lenses { func Make{{.Name}}Lenses() {{.Name}}Lenses {
// mandatory lenses
{{- range .Fields}} {{- range .Fields}}
{{- if .IsOptional}} lens{{.Name}} := L.MakeLens(
iso{{.Name}} := I.FromZero[{{.TypeName}}]()
{{- end}}
{{- end}}
return {{.Name}}Lenses{
{{- range .Fields}}
{{- if .IsOptional}}
{{.Name}}: L.MakeLens(
func(s {{$.Name}}) O.Option[{{.TypeName}}] { return iso{{.Name}}.Get(s.{{.Name}}) },
func(s {{$.Name}}, v O.Option[{{.TypeName}}]) {{$.Name}} { s.{{.Name}} = iso{{.Name}}.ReverseGet(v); return s },
),
{{- else}}
{{.Name}}: L.MakeLens(
func(s {{$.Name}}) {{.TypeName}} { return s.{{.Name}} }, func(s {{$.Name}}) {{.TypeName}} { return s.{{.Name}} },
func(s {{$.Name}}, v {{.TypeName}}) {{$.Name}} { s.{{.Name}} = v; return s }, func(s {{$.Name}}, v {{.TypeName}}) {{$.Name}} { s.{{.Name}} = v; return s },
), )
{{- end}} {{- end}}
// optional lenses
{{- range .Fields}}
lens{{.Name}}O := LO.FromIso[{{$.Name}}](IO.FromZero[{{.TypeName}}]())(lens{{.Name}})
{{- end}}
return {{.Name}}Lenses{
// mandatory lenses
{{- range .Fields}}
{{.Name}}: lens{{.Name}},
{{- end}}
// optional lenses
{{- range .Fields}}
{{.Name}}O: lens{{.Name}}O,
{{- end}} {{- end}}
} }
} }
// Make{{.Name}}RefLenses creates a new {{.Name}}RefLenses with lenses for all fields // Make{{.Name}}RefLenses creates a new {{.Name}}RefLenses with lenses for all fields
func Make{{.Name}}RefLenses() {{.Name}}RefLenses { func Make{{.Name}}RefLenses() {{.Name}}RefLenses {
return {{.Name}}RefLenses{ // mandatory lenses
{{- range .Fields}} {{- range .Fields}}
{{- if .IsOptional}}
{{- if .IsComparable}} {{- if .IsComparable}}
{{.Name}}: LO.FromIso[*{{$.Name}}](I.FromZero[{{.TypeName}}]())(L.MakeLensStrict( lens{{.Name}} := L.MakeLensStrict(
func(s *{{$.Name}}) {{.TypeName}} { return s.{{.Name}} }, func(s *{{$.Name}}) {{.TypeName}} { return s.{{.Name}} },
func(s *{{$.Name}}, v {{.TypeName}}) *{{$.Name}} { s.{{.Name}} = v; return s }, func(s *{{$.Name}}, v {{.TypeName}}) *{{$.Name}} { s.{{.Name}} = v; return s },
)), )
{{- else}} {{- else}}
{{.Name}}: LO.FromIso[*{{$.Name}}](I.FromZero[{{.TypeName}}]())(L.MakeLensRef( lens{{.Name}} := L.MakeLensRef(
func(s *{{$.Name}}) {{.TypeName}} { return s.{{.Name}} }, func(s *{{$.Name}}) {{.TypeName}} { return s.{{.Name}} },
func(s *{{$.Name}}, v {{.TypeName}}) *{{$.Name}} { s.{{.Name}} = v; return s }, func(s *{{$.Name}}, v {{.TypeName}}) *{{$.Name}} { s.{{.Name}} = v; return s },
)), )
{{- end}}
{{- else}}
{{- if .IsComparable}}
{{.Name}}: L.MakeLensStrict(
func(s *{{$.Name}}) {{.TypeName}} { return s.{{.Name}} },
func(s *{{$.Name}}, v {{.TypeName}}) *{{$.Name}} { s.{{.Name}} = v; return s },
),
{{- else}}
{{.Name}}: L.MakeLensRef(
func(s *{{$.Name}}) {{.TypeName}} { return s.{{.Name}} },
func(s *{{$.Name}}, v {{.TypeName}}) *{{$.Name}} { s.{{.Name}} = v; return s },
),
{{- end}} {{- end}}
{{- end}} {{- end}}
// optional lenses
{{- range .Fields}}
lens{{.Name}}O := LO.FromIso[*{{$.Name}}](IO.FromZero[{{.TypeName}}]())(lens{{.Name}})
{{- end}}
return {{.Name}}RefLenses{
// mandatory lenses
{{- range .Fields}}
{{.Name}}: lens{{.Name}},
{{- end}}
// optional lenses
{{- range .Fields}}
{{.Name}}O: lens{{.Name}}O,
{{- end}} {{- end}}
} }
} }
@@ -362,6 +372,7 @@ func isComparableType(expr ast.Expr) bool {
} }
} }
// For other generic types, conservatively assume not comparable // For other generic types, conservatively assume not comparable
log.Printf("Not comparable type: %v\n", t)
return false return false
case *ast.ChanType: case *ast.ChanType:
// Channel types are comparable // Channel types are comparable
@@ -463,6 +474,7 @@ func parseFile(filename string) ([]structInfo, string, error) {
// Check if the type is comparable (for non-optional fields) // Check if the type is comparable (for non-optional fields)
// For optional fields, we don't need to check since they use LensO // For optional fields, we don't need to check since they use LensO
isComparable = isComparableType(field.Type) isComparable = isComparableType(field.Type)
// log.Printf("field %s, type: %v, isComparable: %b\n", name, field.Type, isComparable)
// Extract imports from this field's type // Extract imports from this field's type
fieldImports := make(map[string]string) fieldImports := make(map[string]string)
@@ -590,8 +602,8 @@ func generateLensHelpers(dir, filename string, verbose bool) error {
// Standard fp-go imports always needed // Standard fp-go imports always needed
f.WriteString("\tL \"github.com/IBM/fp-go/v2/optics/lens\"\n") f.WriteString("\tL \"github.com/IBM/fp-go/v2/optics/lens\"\n")
f.WriteString("\tLO \"github.com/IBM/fp-go/v2/optics/lens/option\"\n") f.WriteString("\tLO \"github.com/IBM/fp-go/v2/optics/lens/option\"\n")
f.WriteString("\tO \"github.com/IBM/fp-go/v2/option\"\n") // f.WriteString("\tO \"github.com/IBM/fp-go/v2/option\"\n")
f.WriteString("\tI \"github.com/IBM/fp-go/v2/optics/iso/option\"\n") f.WriteString("\tIO \"github.com/IBM/fp-go/v2/optics/iso/option\"\n")
// Add additional imports collected from field types // Add additional imports collected from field types
for importPath, alias := range allImports { for importPath, alias := range allImports {

View File

@@ -514,15 +514,15 @@ func TestLensRefTemplatesWithComparable(t *testing.T) {
assert.Contains(t, constructorStr, "func MakeTestStructRefLenses() TestStructRefLenses") assert.Contains(t, constructorStr, "func MakeTestStructRefLenses() TestStructRefLenses")
// Name field - comparable, should use MakeLensStrict // Name field - comparable, should use MakeLensStrict
assert.Contains(t, constructorStr, "Name: L.MakeLensStrict(", assert.Contains(t, constructorStr, "lensName := L.MakeLensStrict(",
"comparable field Name should use MakeLensStrict in RefLenses") "comparable field Name should use MakeLensStrict in RefLenses")
// Age field - comparable, should use MakeLensStrict // Age field - comparable, should use MakeLensStrict
assert.Contains(t, constructorStr, "Age: L.MakeLensStrict(", assert.Contains(t, constructorStr, "lensAge := L.MakeLensStrict(",
"comparable field Age should use MakeLensStrict in RefLenses") "comparable field Age should use MakeLensStrict in RefLenses")
// Data field - not comparable, should use MakeLensRef // Data field - not comparable, should use MakeLensRef
assert.Contains(t, constructorStr, "Data: L.MakeLensRef(", assert.Contains(t, constructorStr, "lensData := L.MakeLensRef(",
"non-comparable field Data should use MakeLensRef in RefLenses") "non-comparable field Data should use MakeLensRef in RefLenses")
} }
@@ -573,12 +573,12 @@ type TestStruct struct {
"non-comparable fields should use MakeLensRef in RefLenses") "non-comparable fields should use MakeLensRef in RefLenses")
// Verify the pattern appears for Name field (comparable) // Verify the pattern appears for Name field (comparable)
namePattern := "Name: L.MakeLensStrict(" namePattern := "lensName := L.MakeLensStrict("
assert.Contains(t, contentStr, namePattern, assert.Contains(t, contentStr, namePattern,
"Name field should use MakeLensStrict") "Name field should use MakeLensStrict")
// Verify the pattern appears for Data field (not comparable) // Verify the pattern appears for Data field (not comparable)
dataPattern := "Data: L.MakeLensRef(" dataPattern := "lensData := L.MakeLensRef("
assert.Contains(t, contentStr, dataPattern, assert.Contains(t, contentStr, dataPattern,
"Data field should use MakeLensRef") "Data field should use MakeLensRef")
} }
@@ -619,11 +619,11 @@ type TestStruct struct {
// Check for expected content // Check for expected content
assert.Contains(t, contentStr, "package testpkg") assert.Contains(t, contentStr, "package testpkg")
assert.Contains(t, contentStr, "Code generated by go generate") assert.Contains(t, contentStr, "Code generated by go generate")
assert.Contains(t, contentStr, "TestStructLens") assert.Contains(t, contentStr, "TestStructLenses")
assert.Contains(t, contentStr, "MakeTestStructLens") assert.Contains(t, contentStr, "MakeTestStructLenses")
assert.Contains(t, contentStr, "L.Lens[TestStruct, string]") assert.Contains(t, contentStr, "L.Lens[TestStruct, string]")
assert.Contains(t, contentStr, "LO.LensO[TestStruct, *int]") assert.Contains(t, contentStr, "LO.LensO[TestStruct, *int]")
assert.Contains(t, contentStr, "I.FromZero") assert.Contains(t, contentStr, "IO.FromZero")
} }
func TestGenerateLensHelpersNoAnnotations(t *testing.T) { func TestGenerateLensHelpersNoAnnotations(t *testing.T) {
@@ -670,7 +670,9 @@ func TestLensTemplates(t *testing.T) {
structStr := structBuf.String() structStr := structBuf.String()
assert.Contains(t, structStr, "type TestStructLenses struct") assert.Contains(t, structStr, "type TestStructLenses struct")
assert.Contains(t, structStr, "Name L.Lens[TestStruct, string]") assert.Contains(t, structStr, "Name L.Lens[TestStruct, string]")
assert.Contains(t, structStr, "Value LO.LensO[TestStruct, *int]") assert.Contains(t, structStr, "NameO LO.LensO[TestStruct, string]")
assert.Contains(t, structStr, "Value L.Lens[TestStruct, *int]")
assert.Contains(t, structStr, "ValueO LO.LensO[TestStruct, *int]")
// Test constructor template // Test constructor template
var constructorBuf bytes.Buffer var constructorBuf bytes.Buffer
@@ -680,9 +682,11 @@ func TestLensTemplates(t *testing.T) {
constructorStr := constructorBuf.String() constructorStr := constructorBuf.String()
assert.Contains(t, constructorStr, "func MakeTestStructLenses() TestStructLenses") assert.Contains(t, constructorStr, "func MakeTestStructLenses() TestStructLenses")
assert.Contains(t, constructorStr, "return TestStructLenses{") assert.Contains(t, constructorStr, "return TestStructLenses{")
assert.Contains(t, constructorStr, "Name: L.MakeLens(") assert.Contains(t, constructorStr, "Name: lensName,")
assert.Contains(t, constructorStr, "Value: L.MakeLens(") assert.Contains(t, constructorStr, "NameO: lensNameO,")
assert.Contains(t, constructorStr, "I.FromZero") assert.Contains(t, constructorStr, "Value: lensValue,")
assert.Contains(t, constructorStr, "ValueO: lensValueO,")
assert.Contains(t, constructorStr, "IO.FromZero")
} }
func TestLensTemplatesWithOmitEmpty(t *testing.T) { func TestLensTemplatesWithOmitEmpty(t *testing.T) {
@@ -704,9 +708,13 @@ func TestLensTemplatesWithOmitEmpty(t *testing.T) {
structStr := structBuf.String() structStr := structBuf.String()
assert.Contains(t, structStr, "type ConfigStructLenses struct") assert.Contains(t, structStr, "type ConfigStructLenses struct")
assert.Contains(t, structStr, "Name L.Lens[ConfigStruct, string]") assert.Contains(t, structStr, "Name L.Lens[ConfigStruct, string]")
assert.Contains(t, structStr, "Value LO.LensO[ConfigStruct, string]", "non-pointer with omitempty should use LensO") assert.Contains(t, structStr, "NameO LO.LensO[ConfigStruct, string]")
assert.Contains(t, structStr, "Count LO.LensO[ConfigStruct, int]", "non-pointer with omitempty should use LensO") assert.Contains(t, structStr, "Value L.Lens[ConfigStruct, string]")
assert.Contains(t, structStr, "Pointer LO.LensO[ConfigStruct, *string]") assert.Contains(t, structStr, "ValueO LO.LensO[ConfigStruct, string]", "non-pointer with omitempty should have optional lens")
assert.Contains(t, structStr, "Count L.Lens[ConfigStruct, int]")
assert.Contains(t, structStr, "CountO LO.LensO[ConfigStruct, int]", "non-pointer with omitempty should have optional lens")
assert.Contains(t, structStr, "Pointer L.Lens[ConfigStruct, *string]")
assert.Contains(t, structStr, "PointerO LO.LensO[ConfigStruct, *string]")
// Test constructor template // Test constructor template
var constructorBuf bytes.Buffer var constructorBuf bytes.Buffer
@@ -715,9 +723,9 @@ func TestLensTemplatesWithOmitEmpty(t *testing.T) {
constructorStr := constructorBuf.String() constructorStr := constructorBuf.String()
assert.Contains(t, constructorStr, "func MakeConfigStructLenses() ConfigStructLenses") assert.Contains(t, constructorStr, "func MakeConfigStructLenses() ConfigStructLenses")
assert.Contains(t, constructorStr, "isoValue := I.FromZero[string]()") assert.Contains(t, constructorStr, "IO.FromZero[string]()")
assert.Contains(t, constructorStr, "isoCount := I.FromZero[int]()") assert.Contains(t, constructorStr, "IO.FromZero[int]()")
assert.Contains(t, constructorStr, "isoPointer := I.FromZero[*string]()") assert.Contains(t, constructorStr, "IO.FromZero[*string]()")
} }
func TestLensCommandFlags(t *testing.T) { func TestLensCommandFlags(t *testing.T) {
@@ -726,7 +734,7 @@ func TestLensCommandFlags(t *testing.T) {
assert.Equal(t, "lens", cmd.Name) assert.Equal(t, "lens", cmd.Name)
assert.Equal(t, "generate lens code for annotated structs", cmd.Usage) assert.Equal(t, "generate lens code for annotated structs", cmd.Usage)
assert.Contains(t, strings.ToLower(cmd.Description), "fp-go:lens") assert.Contains(t, strings.ToLower(cmd.Description), "fp-go:lens")
assert.Contains(t, strings.ToLower(cmd.Description), "lenso") assert.Contains(t, strings.ToLower(cmd.Description), "lenso", "Description should mention LensO for optional lenses")
// Check flags // Check flags
assert.Len(t, cmd.Flags, 3) assert.Len(t, cmd.Flags, 3)

View File

@@ -1,4 +1,234 @@
# Optics # Optics
Refer to [Introduction to optics: lenses and prisms](https://medium.com/@gcanti/introduction-to-optics-lenses-and-prisms-3230e73bfcfe) for an introduction about functional optics. Functional optics for composable data access and manipulation in Go.
## Overview
Optics are first-class, composable references to parts of data structures. They provide a uniform interface for reading, writing, and transforming nested immutable data without verbose boilerplate code.
## Quick Start
```go
import (
"github.com/IBM/fp-go/v2/optics/lens"
F "github.com/IBM/fp-go/v2/function"
)
type Person struct {
Name string
Age int
}
// Create a lens for the Name field
nameLens := lens.MakeLens(
func(p Person) string { return p.Name },
func(p Person, name string) Person {
p.Name = name
return p
},
)
person := Person{Name: "Alice", Age: 30}
// Get the name
name := nameLens.Get(person) // "Alice"
// Set a new name (returns a new Person)
updated := nameLens.Set("Bob")(person)
// person.Name is still "Alice", updated.Name is "Bob"
```
## Core Optics Types
### Lens - Product Types (Structs)
Focus on a single field within a struct. Provides get and set operations.
**Use when:** Working with struct fields that always exist.
```go
ageLens := lens.MakeLens(
func(p Person) int { return p.Age },
func(p Person, age int) Person {
p.Age = age
return p
},
)
```
### Prism - Sum Types (Variants)
Focus on one variant of a sum type. Provides optional get and definite set.
**Use when:** Working with Either, Result, or custom sum types.
```go
import "github.com/IBM/fp-go/v2/optics/prism"
successPrism := prism.MakePrism(
func(r Result) option.Option[int] {
if s, ok := r.(Success); ok {
return option.Some(s.Value)
}
return option.None[int]()
},
func(v int) Result { return Success{Value: v} },
)
```
### Iso - Isomorphisms
Bidirectional transformation between equivalent types with no information loss.
**Use when:** Converting between equivalent representations (e.g., Celsius ↔ Fahrenheit).
```go
import "github.com/IBM/fp-go/v2/optics/iso"
celsiusToFahrenheit := iso.MakeIso(
func(c float64) float64 { return c*9/5 + 32 },
func(f float64) float64 { return (f - 32) * 5 / 9 },
)
```
### Optional - Maybe Values
Focus on a value that may or may not exist.
**Use when:** Working with nullable fields or values that may be absent.
```go
import "github.com/IBM/fp-go/v2/optics/optional"
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
},
)
```
### Traversal - Multiple Values
Focus on multiple values simultaneously, allowing batch operations.
**Use when:** Working with collections or updating multiple fields at once.
```go
import (
"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}
// Double all elements
doubled := F.Pipe2(
numbers,
TA.Traversal[int](),
traversal.Modify[[]int, int](func(n int) int { return n * 2 }),
)
// Result: [2, 4, 6, 8, 10]
```
## Composition
The real power of optics comes from composition:
```go
type Company struct {
Name string
Address Address
}
type Address struct {
Street string
City string
}
// Individual lenses
addressLens := lens.MakeLens(
func(c Company) Address { return c.Address },
func(c Company, a Address) Company {
c.Address = a
return c
},
)
cityLens := lens.MakeLens(
func(a Address) string { return a.City },
func(a Address, city string) Address {
a.City = city
return a
},
)
// Compose to access city directly from company
companyCityLens := F.Pipe1(
addressLens,
lens.Compose[Company](cityLens),
)
company := Company{
Name: "Acme Corp",
Address: Address{Street: "Main St", City: "NYC"},
}
city := companyCityLens.Get(company) // "NYC"
updated := companyCityLens.Set("Boston")(company)
```
## Optics Hierarchy
```
Iso[S, A]
Lens[S, A]
Optional[S, A]
Traversal[S, A]
Prism[S, A]
Optional[S, A]
Traversal[S, A]
```
More specific optics can be converted to more general ones.
## Package Structure
- **optics/lens**: Lenses for product types (structs)
- **optics/prism**: Prisms for sum types (Either, Result, etc.)
- **optics/iso**: Isomorphisms for equivalent types
- **optics/optional**: Optional optics for maybe values
- **optics/traversal**: Traversals for multiple values
Each package includes specialized sub-packages for common patterns:
- **array**: Optics for arrays/slices
- **either**: Optics for Either types
- **option**: Optics for Option types
- **record**: Optics for maps
## Documentation
For detailed documentation on each optic type, see:
- [Main Package Documentation](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics)
- [Lens Documentation](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/lens)
- [Prism Documentation](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/prism)
- [Iso Documentation](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/iso)
- [Optional Documentation](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/optional)
- [Traversal Documentation](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/traversal)
## Further Reading
For an introduction to functional optics concepts:
- [Introduction to optics: lenses and prisms](https://medium.com/@gcanti/introduction-to-optics-lenses-and-prisms-3230e73bfcfe) by Giulio Canti
## Examples
See the [samples/lens](../samples/lens) directory for complete working examples.
## License
Apache License 2.0 - See LICENSE file for details.

305
v2/optics/iso/option/doc.go Normal file
View File

@@ -0,0 +1,305 @@
// 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

366
v2/optics/lens/iso/doc.go Normal file
View File

@@ -0,0 +1,366 @@
// 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 iso provides utilities for composing lenses with isomorphisms.
# Overview
This package bridges lenses and isomorphisms, allowing you to transform the focus type
of a lens using an isomorphism. It provides functions to compose lenses with isomorphisms
and to create isomorphisms for common patterns like nullable pointers.
The key insight is that if you have a Lens[S, A] and an Iso[A, B], you can create a
Lens[S, B] by composing them. This allows you to work with transformed views of your
data without changing the underlying structure.
# Core Functions
## FromNillable
Creates an isomorphism between a nullable pointer and an Option type:
type Config struct {
Timeout *int
}
// Create isomorphism: *int ↔ Option[int]
timeoutIso := iso.FromNillable[int]()
// nil → None, &value → Some(value)
opt := timeoutIso.Get(nil) // None[int]
num := 42
opt = timeoutIso.Get(&num) // Some(42)
// None → nil, Some(value) → &value
ptr := timeoutIso.ReverseGet(O.None[int]()) // nil
ptr = timeoutIso.ReverseGet(O.Some(42)) // &42
## Compose
Composes a lens with an isomorphism to transform the focus type:
type Person struct {
Name string
Age int
}
type Celsius float64
type Fahrenheit float64
type Weather struct {
Temperature Celsius
}
// Lens to access temperature
tempLens := L.MakeLens(
func(w Weather) Celsius { return w.Temperature },
func(w Weather, t Celsius) Weather {
w.Temperature = t
return w
},
)
// Isomorphism: Celsius ↔ Fahrenheit
celsiusToFahrenheit := I.MakeIso(
func(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) },
func(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) },
)
// Compose to work with Fahrenheit
tempFahrenheitLens := F.Pipe1(
tempLens,
iso.Compose[Weather, Celsius, Fahrenheit](celsiusToFahrenheit),
)
weather := Weather{Temperature: 20} // 20°C
tempF := tempFahrenheitLens.Get(weather) // 68°F
updated := tempFahrenheitLens.Set(86)(weather) // Set to 86°F (30°C)
# Use Cases
## Working with Nullable Fields
Convert between nullable pointers and Option types:
type DatabaseConfig struct {
Host string
Port int
Username string
Password *string // Nullable
}
type AppConfig struct {
Database *DatabaseConfig
}
// Lens to database config
dbLens := L.MakeLens(
func(c AppConfig) *DatabaseConfig { return c.Database },
func(c AppConfig, db *DatabaseConfig) AppConfig {
c.Database = db
return c
},
)
// Isomorphism for nullable pointer
dbIso := iso.FromNillable[DatabaseConfig]()
// Compose to work with Option
dbOptLens := F.Pipe1(
dbLens,
iso.Compose[AppConfig, *DatabaseConfig, O.Option[DatabaseConfig]](dbIso),
)
config := AppConfig{Database: nil}
dbOpt := dbOptLens.Get(config) // None[DatabaseConfig]
// Set with Some
newDB := DatabaseConfig{Host: "localhost", Port: 5432}
updated := dbOptLens.Set(O.Some(newDB))(config)
## Unit Conversions
Work with different units of measurement:
type Distance struct {
Meters float64
}
type Kilometers float64
type Miles float64
// Lens to meters
metersLens := L.MakeLens(
func(d Distance) float64 { return d.Meters },
func(d Distance, m float64) Distance {
d.Meters = m
return d
},
)
// Isomorphism: meters ↔ kilometers
metersToKm := I.MakeIso(
func(m float64) Kilometers { return Kilometers(m / 1000) },
func(km Kilometers) float64 { return float64(km * 1000) },
)
// Compose to work with kilometers
kmLens := F.Pipe1(
metersLens,
iso.Compose[Distance, float64, Kilometers](metersToKm),
)
distance := Distance{Meters: 5000}
km := kmLens.Get(distance) // 5 km
updated := kmLens.Set(Kilometers(10))(distance) // 10000 meters
## Type Wrappers
Work with newtype wrappers:
type UserId int
type User struct {
ID UserId
Name string
}
// Lens to user ID
idLens := L.MakeLens(
func(u User) UserId { return u.ID },
func(u User, id UserId) User {
u.ID = id
return u
},
)
// Isomorphism: UserId ↔ int
userIdIso := I.MakeIso(
func(id UserId) int { return int(id) },
func(i int) UserId { return UserId(i) },
)
// Compose to work with raw int
idIntLens := F.Pipe1(
idLens,
iso.Compose[User, UserId, int](userIdIso),
)
user := User{ID: 42, Name: "Alice"}
rawId := idIntLens.Get(user) // 42 (int)
updated := idIntLens.Set(100)(user) // UserId(100)
## Nested Nullable Fields
Safely navigate through nullable nested structures:
type Address struct {
Street string
City string
}
type Person struct {
Name string
Address *Address
}
type Company struct {
Name string
CEO *Person
}
// Lens to CEO
ceoLens := L.MakeLens(
func(c Company) *Person { return c.CEO },
func(c Company, p *Person) Company {
c.CEO = p
return c
},
)
// Isomorphism for nullable person
personIso := iso.FromNillable[Person]()
// Compose to work with Option[Person]
ceoOptLens := F.Pipe1(
ceoLens,
iso.Compose[Company, *Person, O.Option[Person]](personIso),
)
company := Company{Name: "Acme Corp", CEO: nil}
ceo := ceoOptLens.Get(company) // None[Person]
// Set CEO
newCEO := Person{Name: "Alice", Address: nil}
updated := ceoOptLens.Set(O.Some(newCEO))(company)
# Composition Patterns
## Chaining Multiple Isomorphisms
type Meters float64
type Kilometers float64
type Miles float64
type Journey struct {
Distance Meters
}
// Lens to distance
distLens := L.MakeLens(
func(j Journey) Meters { return j.Distance },
func(j Journey, d Meters) Journey {
j.Distance = d
return j
},
)
// Isomorphisms
metersToKm := I.MakeIso(
func(m Meters) Kilometers { return Kilometers(m / 1000) },
func(km Kilometers) Meters { return Meters(km * 1000) },
)
kmToMiles := I.MakeIso(
func(km Kilometers) Miles { return Miles(km * 0.621371) },
func(mi Miles) Kilometers { return Kilometers(mi / 0.621371) },
)
// Compose lens with chained isomorphisms
milesLens := F.Pipe2(
distLens,
iso.Compose[Journey, Meters, Kilometers](metersToKm),
iso.Compose[Journey, Kilometers, Miles](kmToMiles),
)
journey := Journey{Distance: 5000} // 5000 meters
miles := milesLens.Get(journey) // ~3.11 miles
## Combining with Optional Lenses
type Config struct {
Database *DatabaseConfig
}
type DatabaseConfig struct {
Port int
}
// Lens to database (nullable)
dbLens := L.MakeLens(
func(c Config) *DatabaseConfig { return c.Database },
func(c Config, db *DatabaseConfig) Config {
c.Database = db
return c
},
)
// Convert to Option lens
dbIso := iso.FromNillable[DatabaseConfig]()
dbOptLens := F.Pipe1(
dbLens,
iso.Compose[Config, *DatabaseConfig, O.Option[DatabaseConfig]](dbIso),
)
// Now compose with lens to port
portLens := L.MakeLens(
func(db DatabaseConfig) int { return db.Port },
func(db DatabaseConfig, port int) DatabaseConfig {
db.Port = port
return db
},
)
// Use ComposeOption to handle the Option
defaultDB := DatabaseConfig{Port: 5432}
configPortLens := F.Pipe1(
dbOptLens,
L.ComposeOption[Config, int](defaultDB)(portLens),
)
# Performance Considerations
Composing lenses with isomorphisms is efficient:
- No additional allocations beyond the lens and iso structures
- Composition creates function closures but is still performant
- The isomorphism transformations are applied on-demand
- Consider caching composed lenses for frequently used paths
# Type Safety
All operations are fully type-safe:
- Compile-time type checking ensures correct composition
- Generic type parameters prevent type mismatches
- No runtime type assertions needed
- The compiler enforces that isomorphisms are properly reversible
# Related Packages
- github.com/IBM/fp-go/v2/optics/lens: Core lens functionality
- github.com/IBM/fp-go/v2/optics/iso: Core isomorphism functionality
- github.com/IBM/fp-go/v2/optics/iso/lens: Convert isomorphisms to lenses
- github.com/IBM/fp-go/v2/option: Option type and operations
- github.com/IBM/fp-go/v2/function: Function composition utilities
# See Also
For more information on lenses and isomorphisms:
- optics/lens package documentation
- optics/iso package documentation
- optics package overview
*/
package iso
// Made with Bob

479
v2/optics/optional/doc.go Normal file
View File

@@ -0,0 +1,479 @@
// 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 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)
# 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)
*/
package optional
// Made with Bob

495
v2/optics/traversal/doc.go Normal file
View File

@@ -0,0 +1,495 @@
// 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 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](func(n int) int { return n * 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](func(n int) int { return n * 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](func(n int) int { return n * 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](func(n int) int { return n + 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.
# Related Packages
- 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
*/
package traversal
// Made with Bob

View File

@@ -47,6 +47,12 @@ type Company struct {
Website *string Website *string
} }
// fp-go:Lens
type CompanyExtended struct {
Company
Extended string
}
// fp-go:Lens // fp-go:Lens
type CheckOption struct { type CheckOption struct {
Name option.Option[string] Name option.Option[string]

View File

@@ -214,7 +214,7 @@ func TestPersonRefLensesOptionalIdempotent(t *testing.T) {
refLenses := MakePersonRefLenses() refLenses := MakePersonRefLenses()
// Test that setting Phone to the same value returns the same pointer // Test that setting Phone to the same value returns the same pointer
samePhone := refLenses.Phone.Set(O.Some(&phoneValue))(person) samePhone := refLenses.PhoneO.Set(O.Some(&phoneValue))(person)
assert.Same(t, person, samePhone, "Setting Phone to same value should return identical pointer") assert.Same(t, person, samePhone, "Setting Phone to same value should return identical pointer")
// Test with Phone field set to nil // Test with Phone field set to nil
@@ -226,24 +226,24 @@ func TestPersonRefLensesOptionalIdempotent(t *testing.T) {
} }
// Setting Phone to None when it's already nil should return same pointer // Setting Phone to None when it's already nil should return same pointer
sameNilPhone := refLenses.Phone.Set(O.None[*string]())(personNoPhone) sameNilPhone := refLenses.PhoneO.Set(O.None[*string]())(personNoPhone)
assert.Same(t, personNoPhone, sameNilPhone, "Setting Phone to None when already nil should return identical pointer") assert.Same(t, personNoPhone, sameNilPhone, "Setting Phone to None when already nil should return identical pointer")
// Test that setting to a different value creates a new pointer // Test that setting to a different value creates a new pointer
newPhoneValue := "555-5678" newPhoneValue := "555-5678"
differentPhone := refLenses.Phone.Set(O.Some(&newPhoneValue))(person) differentPhone := refLenses.PhoneO.Set(O.Some(&newPhoneValue))(person)
assert.NotSame(t, person, differentPhone, "Setting Phone to different value should return new pointer") assert.NotSame(t, person, differentPhone, "Setting Phone to different value should return new pointer")
assert.Equal(t, &newPhoneValue, differentPhone.Phone) assert.Equal(t, &newPhoneValue, differentPhone.Phone)
assert.Equal(t, &phoneValue, person.Phone, "Original should be unchanged") assert.Equal(t, &phoneValue, person.Phone, "Original should be unchanged")
// Test setting from nil to Some creates new pointer // Test setting from nil to Some creates new pointer
somePhone := refLenses.Phone.Set(O.Some(&phoneValue))(personNoPhone) somePhone := refLenses.PhoneO.Set(O.Some(&phoneValue))(personNoPhone)
assert.NotSame(t, personNoPhone, somePhone, "Setting Phone from nil to Some should return new pointer") assert.NotSame(t, personNoPhone, somePhone, "Setting Phone from nil to Some should return new pointer")
assert.Equal(t, &phoneValue, somePhone.Phone) assert.Equal(t, &phoneValue, somePhone.Phone)
assert.Nil(t, personNoPhone.Phone, "Original should be unchanged") assert.Nil(t, personNoPhone.Phone, "Original should be unchanged")
// Test setting from Some to None creates new pointer // Test setting from Some to None creates new pointer
nonePhone := refLenses.Phone.Set(O.None[*string]())(person) nonePhone := refLenses.PhoneO.Set(O.None[*string]())(person)
assert.NotSame(t, person, nonePhone, "Setting Phone from Some to None should return new pointer") assert.NotSame(t, person, nonePhone, "Setting Phone from Some to None should return new pointer")
assert.Nil(t, nonePhone.Phone) assert.Nil(t, nonePhone.Phone)
assert.Equal(t, &phoneValue, person.Phone, "Original should be unchanged") assert.Equal(t, &phoneValue, person.Phone, "Original should be unchanged")
@@ -264,7 +264,7 @@ func TestAddressRefLensesOptionalIdempotent(t *testing.T) {
refLenses := MakeAddressRefLenses() refLenses := MakeAddressRefLenses()
// Test that setting State to the same value returns the same pointer // Test that setting State to the same value returns the same pointer
sameState := refLenses.State.Set(O.Some(&stateValue))(address) sameState := refLenses.StateO.Set(O.Some(&stateValue))(address)
assert.Same(t, address, sameState, "Setting State to same value should return identical pointer") assert.Same(t, address, sameState, "Setting State to same value should return identical pointer")
// Test with State field set to nil // Test with State field set to nil
@@ -277,12 +277,12 @@ func TestAddressRefLensesOptionalIdempotent(t *testing.T) {
} }
// Setting State to None when it's already nil should return same pointer // Setting State to None when it's already nil should return same pointer
sameNilState := refLenses.State.Set(O.None[*string]())(addressNoState) sameNilState := refLenses.StateO.Set(O.None[*string]())(addressNoState)
assert.Same(t, addressNoState, sameNilState, "Setting State to None when already nil should return identical pointer") assert.Same(t, addressNoState, sameNilState, "Setting State to None when already nil should return identical pointer")
// Test that setting to a different value creates a new pointer // Test that setting to a different value creates a new pointer
newStateValue := "New York" newStateValue := "New York"
differentState := refLenses.State.Set(O.Some(&newStateValue))(address) differentState := refLenses.StateO.Set(O.Some(&newStateValue))(address)
assert.NotSame(t, address, differentState, "Setting State to different value should return new pointer") assert.NotSame(t, address, differentState, "Setting State to different value should return new pointer")
assert.Equal(t, &newStateValue, differentState.State) assert.Equal(t, &newStateValue, differentState.State)
assert.Equal(t, &stateValue, address.State, "Original should be unchanged") assert.Equal(t, &stateValue, address.State, "Original should be unchanged")
@@ -311,7 +311,7 @@ func TestCompanyRefLensesOptionalIdempotent(t *testing.T) {
refLenses := MakeCompanyRefLenses() refLenses := MakeCompanyRefLenses()
// Test that setting Website to the same value returns the same pointer // Test that setting Website to the same value returns the same pointer
sameWebsite := refLenses.Website.Set(O.Some(&websiteValue))(company) sameWebsite := refLenses.WebsiteO.Set(O.Some(&websiteValue))(company)
assert.Same(t, company, sameWebsite, "Setting Website to same value should return identical pointer") assert.Same(t, company, sameWebsite, "Setting Website to same value should return identical pointer")
// Test with Website field set to nil // Test with Website field set to nil
@@ -331,12 +331,12 @@ func TestCompanyRefLensesOptionalIdempotent(t *testing.T) {
} }
// Setting Website to None when it's already nil should return same pointer // Setting Website to None when it's already nil should return same pointer
sameNilWebsite := refLenses.Website.Set(O.None[*string]())(companyNoWebsite) sameNilWebsite := refLenses.WebsiteO.Set(O.None[*string]())(companyNoWebsite)
assert.Same(t, companyNoWebsite, sameNilWebsite, "Setting Website to None when already nil should return identical pointer") assert.Same(t, companyNoWebsite, sameNilWebsite, "Setting Website to None when already nil should return identical pointer")
// Test that setting to a different value creates a new pointer // Test that setting to a different value creates a new pointer
newWebsiteValue := "https://newsite.com" newWebsiteValue := "https://newsite.com"
differentWebsite := refLenses.Website.Set(O.Some(&newWebsiteValue))(company) differentWebsite := refLenses.WebsiteO.Set(O.Some(&newWebsiteValue))(company)
assert.NotSame(t, company, differentWebsite, "Setting Website to different value should return new pointer") assert.NotSame(t, company, differentWebsite, "Setting Website to different value should return new pointer")
assert.Equal(t, &newWebsiteValue, differentWebsite.Website) assert.Equal(t, &newWebsiteValue, differentWebsite.Website)
assert.Equal(t, &websiteValue, company.Website, "Original should be unchanged") assert.Equal(t, &websiteValue, company.Website, "Original should be unchanged")

View File

@@ -2,246 +2,459 @@ package lens
// Code generated by go generate; DO NOT EDIT. // Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at // This file was generated by robots at
// 2025-11-12 18:15:07.69943 +0100 CET m=+0.005345401 // 2025-11-13 09:05:02.7405925 +0100 CET m=+0.003158301
import ( import (
IO "github.com/IBM/fp-go/v2/optics/iso/option"
L "github.com/IBM/fp-go/v2/optics/lens" L "github.com/IBM/fp-go/v2/optics/lens"
LO "github.com/IBM/fp-go/v2/optics/lens/option" LO "github.com/IBM/fp-go/v2/optics/lens/option"
O "github.com/IBM/fp-go/v2/option"
I "github.com/IBM/fp-go/v2/optics/iso/option"
option "github.com/IBM/fp-go/v2/optics/lens/option" option "github.com/IBM/fp-go/v2/optics/lens/option"
) )
// PersonLenses provides lenses for accessing fields of Person // PersonLenses provides lenses for accessing fields of Person
type PersonLenses struct { type PersonLenses struct {
// mandatory fields
Name L.Lens[Person, string] Name L.Lens[Person, string]
Age L.Lens[Person, int] Age L.Lens[Person, int]
Email L.Lens[Person, string] Email L.Lens[Person, string]
Phone LO.LensO[Person, *string] Phone L.Lens[Person, *string]
// optional fields
NameO LO.LensO[Person, string]
AgeO LO.LensO[Person, int]
EmailO LO.LensO[Person, string]
PhoneO LO.LensO[Person, *string]
} }
// PersonRefLenses provides lenses for accessing fields of Person via a reference to Person // PersonRefLenses provides lenses for accessing fields of Person via a reference to Person
type PersonRefLenses struct { type PersonRefLenses struct {
// mandatory fields
Name L.Lens[*Person, string] Name L.Lens[*Person, string]
Age L.Lens[*Person, int] Age L.Lens[*Person, int]
Email L.Lens[*Person, string] Email L.Lens[*Person, string]
Phone LO.LensO[*Person, *string] Phone L.Lens[*Person, *string]
// optional fields
NameO LO.LensO[*Person, string]
AgeO LO.LensO[*Person, int]
EmailO LO.LensO[*Person, string]
PhoneO LO.LensO[*Person, *string]
} }
// MakePersonLenses creates a new PersonLenses with lenses for all fields // MakePersonLenses creates a new PersonLenses with lenses for all fields
func MakePersonLenses() PersonLenses { func MakePersonLenses() PersonLenses {
isoPhone := I.FromZero[*string]() // mandatory lenses
return PersonLenses{ lensName := L.MakeLens(
Name: L.MakeLens(
func(s Person) string { return s.Name }, func(s Person) string { return s.Name },
func(s Person, v string) Person { s.Name = v; return s }, func(s Person, v string) Person { s.Name = v; return s },
), )
Age: L.MakeLens( lensAge := L.MakeLens(
func(s Person) int { return s.Age }, func(s Person) int { return s.Age },
func(s Person, v int) Person { s.Age = v; return s }, func(s Person, v int) Person { s.Age = v; return s },
), )
Email: L.MakeLens( lensEmail := L.MakeLens(
func(s Person) string { return s.Email }, func(s Person) string { return s.Email },
func(s Person, v string) Person { s.Email = v; return s }, func(s Person, v string) Person { s.Email = v; return s },
), )
Phone: L.MakeLens( lensPhone := L.MakeLens(
func(s Person) O.Option[*string] { return isoPhone.Get(s.Phone) }, func(s Person) *string { return s.Phone },
func(s Person, v O.Option[*string]) Person { s.Phone = isoPhone.ReverseGet(v); return s }, func(s Person, v *string) Person { s.Phone = v; return s },
), )
// optional lenses
lensNameO := LO.FromIso[Person](IO.FromZero[string]())(lensName)
lensAgeO := LO.FromIso[Person](IO.FromZero[int]())(lensAge)
lensEmailO := LO.FromIso[Person](IO.FromZero[string]())(lensEmail)
lensPhoneO := LO.FromIso[Person](IO.FromZero[*string]())(lensPhone)
return PersonLenses{
// mandatory lenses
Name: lensName,
Age: lensAge,
Email: lensEmail,
Phone: lensPhone,
// optional lenses
NameO: lensNameO,
AgeO: lensAgeO,
EmailO: lensEmailO,
PhoneO: lensPhoneO,
} }
} }
// MakePersonRefLenses creates a new PersonRefLenses with lenses for all fields // MakePersonRefLenses creates a new PersonRefLenses with lenses for all fields
func MakePersonRefLenses() PersonRefLenses { func MakePersonRefLenses() PersonRefLenses {
return PersonRefLenses{ // mandatory lenses
Name: L.MakeLensStrict( lensName := L.MakeLensStrict(
func(s *Person) string { return s.Name }, func(s *Person) string { return s.Name },
func(s *Person, v string) *Person { s.Name = v; return s }, func(s *Person, v string) *Person { s.Name = v; return s },
), )
Age: L.MakeLensStrict( lensAge := L.MakeLensStrict(
func(s *Person) int { return s.Age }, func(s *Person) int { return s.Age },
func(s *Person, v int) *Person { s.Age = v; return s }, func(s *Person, v int) *Person { s.Age = v; return s },
), )
Email: L.MakeLensStrict( lensEmail := L.MakeLensStrict(
func(s *Person) string { return s.Email }, func(s *Person) string { return s.Email },
func(s *Person, v string) *Person { s.Email = v; return s }, func(s *Person, v string) *Person { s.Email = v; return s },
), )
Phone: LO.FromIso[*Person](I.FromZero[*string]())(L.MakeLensStrict( lensPhone := L.MakeLensStrict(
func(s *Person) *string { return s.Phone }, func(s *Person) *string { return s.Phone },
func(s *Person, v *string) *Person { s.Phone = v; return s }, func(s *Person, v *string) *Person { s.Phone = v; return s },
)), )
// optional lenses
lensNameO := LO.FromIso[*Person](IO.FromZero[string]())(lensName)
lensAgeO := LO.FromIso[*Person](IO.FromZero[int]())(lensAge)
lensEmailO := LO.FromIso[*Person](IO.FromZero[string]())(lensEmail)
lensPhoneO := LO.FromIso[*Person](IO.FromZero[*string]())(lensPhone)
return PersonRefLenses{
// mandatory lenses
Name: lensName,
Age: lensAge,
Email: lensEmail,
Phone: lensPhone,
// optional lenses
NameO: lensNameO,
AgeO: lensAgeO,
EmailO: lensEmailO,
PhoneO: lensPhoneO,
} }
} }
// AddressLenses provides lenses for accessing fields of Address // AddressLenses provides lenses for accessing fields of Address
type AddressLenses struct { type AddressLenses struct {
// mandatory fields
Street L.Lens[Address, string] Street L.Lens[Address, string]
City L.Lens[Address, string] City L.Lens[Address, string]
ZipCode L.Lens[Address, string] ZipCode L.Lens[Address, string]
Country L.Lens[Address, string] Country L.Lens[Address, string]
State LO.LensO[Address, *string] State L.Lens[Address, *string]
// optional fields
StreetO LO.LensO[Address, string]
CityO LO.LensO[Address, string]
ZipCodeO LO.LensO[Address, string]
CountryO LO.LensO[Address, string]
StateO LO.LensO[Address, *string]
} }
// AddressRefLenses provides lenses for accessing fields of Address via a reference to Address // AddressRefLenses provides lenses for accessing fields of Address via a reference to Address
type AddressRefLenses struct { type AddressRefLenses struct {
// mandatory fields
Street L.Lens[*Address, string] Street L.Lens[*Address, string]
City L.Lens[*Address, string] City L.Lens[*Address, string]
ZipCode L.Lens[*Address, string] ZipCode L.Lens[*Address, string]
Country L.Lens[*Address, string] Country L.Lens[*Address, string]
State LO.LensO[*Address, *string] State L.Lens[*Address, *string]
// optional fields
StreetO LO.LensO[*Address, string]
CityO LO.LensO[*Address, string]
ZipCodeO LO.LensO[*Address, string]
CountryO LO.LensO[*Address, string]
StateO LO.LensO[*Address, *string]
} }
// MakeAddressLenses creates a new AddressLenses with lenses for all fields // MakeAddressLenses creates a new AddressLenses with lenses for all fields
func MakeAddressLenses() AddressLenses { func MakeAddressLenses() AddressLenses {
isoState := I.FromZero[*string]() // mandatory lenses
return AddressLenses{ lensStreet := L.MakeLens(
Street: L.MakeLens(
func(s Address) string { return s.Street }, func(s Address) string { return s.Street },
func(s Address, v string) Address { s.Street = v; return s }, func(s Address, v string) Address { s.Street = v; return s },
), )
City: L.MakeLens( lensCity := L.MakeLens(
func(s Address) string { return s.City }, func(s Address) string { return s.City },
func(s Address, v string) Address { s.City = v; return s }, func(s Address, v string) Address { s.City = v; return s },
), )
ZipCode: L.MakeLens( lensZipCode := L.MakeLens(
func(s Address) string { return s.ZipCode }, func(s Address) string { return s.ZipCode },
func(s Address, v string) Address { s.ZipCode = v; return s }, func(s Address, v string) Address { s.ZipCode = v; return s },
), )
Country: L.MakeLens( lensCountry := L.MakeLens(
func(s Address) string { return s.Country }, func(s Address) string { return s.Country },
func(s Address, v string) Address { s.Country = v; return s }, func(s Address, v string) Address { s.Country = v; return s },
), )
State: L.MakeLens( lensState := L.MakeLens(
func(s Address) O.Option[*string] { return isoState.Get(s.State) }, func(s Address) *string { return s.State },
func(s Address, v O.Option[*string]) Address { s.State = isoState.ReverseGet(v); return s }, func(s Address, v *string) Address { s.State = v; return s },
), )
// optional lenses
lensStreetO := LO.FromIso[Address](IO.FromZero[string]())(lensStreet)
lensCityO := LO.FromIso[Address](IO.FromZero[string]())(lensCity)
lensZipCodeO := LO.FromIso[Address](IO.FromZero[string]())(lensZipCode)
lensCountryO := LO.FromIso[Address](IO.FromZero[string]())(lensCountry)
lensStateO := LO.FromIso[Address](IO.FromZero[*string]())(lensState)
return AddressLenses{
// mandatory lenses
Street: lensStreet,
City: lensCity,
ZipCode: lensZipCode,
Country: lensCountry,
State: lensState,
// optional lenses
StreetO: lensStreetO,
CityO: lensCityO,
ZipCodeO: lensZipCodeO,
CountryO: lensCountryO,
StateO: lensStateO,
} }
} }
// MakeAddressRefLenses creates a new AddressRefLenses with lenses for all fields // MakeAddressRefLenses creates a new AddressRefLenses with lenses for all fields
func MakeAddressRefLenses() AddressRefLenses { func MakeAddressRefLenses() AddressRefLenses {
return AddressRefLenses{ // mandatory lenses
Street: L.MakeLensStrict( lensStreet := L.MakeLensStrict(
func(s *Address) string { return s.Street }, func(s *Address) string { return s.Street },
func(s *Address, v string) *Address { s.Street = v; return s }, func(s *Address, v string) *Address { s.Street = v; return s },
), )
City: L.MakeLensStrict( lensCity := L.MakeLensStrict(
func(s *Address) string { return s.City }, func(s *Address) string { return s.City },
func(s *Address, v string) *Address { s.City = v; return s }, func(s *Address, v string) *Address { s.City = v; return s },
), )
ZipCode: L.MakeLensStrict( lensZipCode := L.MakeLensStrict(
func(s *Address) string { return s.ZipCode }, func(s *Address) string { return s.ZipCode },
func(s *Address, v string) *Address { s.ZipCode = v; return s }, func(s *Address, v string) *Address { s.ZipCode = v; return s },
), )
Country: L.MakeLensStrict( lensCountry := L.MakeLensStrict(
func(s *Address) string { return s.Country }, func(s *Address) string { return s.Country },
func(s *Address, v string) *Address { s.Country = v; return s }, func(s *Address, v string) *Address { s.Country = v; return s },
), )
State: LO.FromIso[*Address](I.FromZero[*string]())(L.MakeLensStrict( lensState := L.MakeLensStrict(
func(s *Address) *string { return s.State }, func(s *Address) *string { return s.State },
func(s *Address, v *string) *Address { s.State = v; return s }, func(s *Address, v *string) *Address { s.State = v; return s },
)), )
// optional lenses
lensStreetO := LO.FromIso[*Address](IO.FromZero[string]())(lensStreet)
lensCityO := LO.FromIso[*Address](IO.FromZero[string]())(lensCity)
lensZipCodeO := LO.FromIso[*Address](IO.FromZero[string]())(lensZipCode)
lensCountryO := LO.FromIso[*Address](IO.FromZero[string]())(lensCountry)
lensStateO := LO.FromIso[*Address](IO.FromZero[*string]())(lensState)
return AddressRefLenses{
// mandatory lenses
Street: lensStreet,
City: lensCity,
ZipCode: lensZipCode,
Country: lensCountry,
State: lensState,
// optional lenses
StreetO: lensStreetO,
CityO: lensCityO,
ZipCodeO: lensZipCodeO,
CountryO: lensCountryO,
StateO: lensStateO,
} }
} }
// CompanyLenses provides lenses for accessing fields of Company // CompanyLenses provides lenses for accessing fields of Company
type CompanyLenses struct { type CompanyLenses struct {
// mandatory fields
Name L.Lens[Company, string] Name L.Lens[Company, string]
Address L.Lens[Company, Address] Address L.Lens[Company, Address]
CEO L.Lens[Company, Person] CEO L.Lens[Company, Person]
Website LO.LensO[Company, *string] Website L.Lens[Company, *string]
// optional fields
NameO LO.LensO[Company, string]
AddressO LO.LensO[Company, Address]
CEOO LO.LensO[Company, Person]
WebsiteO LO.LensO[Company, *string]
} }
// CompanyRefLenses provides lenses for accessing fields of Company via a reference to Company // CompanyRefLenses provides lenses for accessing fields of Company via a reference to Company
type CompanyRefLenses struct { type CompanyRefLenses struct {
// mandatory fields
Name L.Lens[*Company, string] Name L.Lens[*Company, string]
Address L.Lens[*Company, Address] Address L.Lens[*Company, Address]
CEO L.Lens[*Company, Person] CEO L.Lens[*Company, Person]
Website LO.LensO[*Company, *string] Website L.Lens[*Company, *string]
// optional fields
NameO LO.LensO[*Company, string]
AddressO LO.LensO[*Company, Address]
CEOO LO.LensO[*Company, Person]
WebsiteO LO.LensO[*Company, *string]
} }
// MakeCompanyLenses creates a new CompanyLenses with lenses for all fields // MakeCompanyLenses creates a new CompanyLenses with lenses for all fields
func MakeCompanyLenses() CompanyLenses { func MakeCompanyLenses() CompanyLenses {
isoWebsite := I.FromZero[*string]() // mandatory lenses
return CompanyLenses{ lensName := L.MakeLens(
Name: L.MakeLens(
func(s Company) string { return s.Name }, func(s Company) string { return s.Name },
func(s Company, v string) Company { s.Name = v; return s }, func(s Company, v string) Company { s.Name = v; return s },
), )
Address: L.MakeLens( lensAddress := L.MakeLens(
func(s Company) Address { return s.Address }, func(s Company) Address { return s.Address },
func(s Company, v Address) Company { s.Address = v; return s }, func(s Company, v Address) Company { s.Address = v; return s },
), )
CEO: L.MakeLens( lensCEO := L.MakeLens(
func(s Company) Person { return s.CEO }, func(s Company) Person { return s.CEO },
func(s Company, v Person) Company { s.CEO = v; return s }, func(s Company, v Person) Company { s.CEO = v; return s },
), )
Website: L.MakeLens( lensWebsite := L.MakeLens(
func(s Company) O.Option[*string] { return isoWebsite.Get(s.Website) }, func(s Company) *string { return s.Website },
func(s Company, v O.Option[*string]) Company { s.Website = isoWebsite.ReverseGet(v); return s }, func(s Company, v *string) Company { s.Website = v; return s },
), )
// optional lenses
lensNameO := LO.FromIso[Company](IO.FromZero[string]())(lensName)
lensAddressO := LO.FromIso[Company](IO.FromZero[Address]())(lensAddress)
lensCEOO := LO.FromIso[Company](IO.FromZero[Person]())(lensCEO)
lensWebsiteO := LO.FromIso[Company](IO.FromZero[*string]())(lensWebsite)
return CompanyLenses{
// mandatory lenses
Name: lensName,
Address: lensAddress,
CEO: lensCEO,
Website: lensWebsite,
// optional lenses
NameO: lensNameO,
AddressO: lensAddressO,
CEOO: lensCEOO,
WebsiteO: lensWebsiteO,
} }
} }
// MakeCompanyRefLenses creates a new CompanyRefLenses with lenses for all fields // MakeCompanyRefLenses creates a new CompanyRefLenses with lenses for all fields
func MakeCompanyRefLenses() CompanyRefLenses { func MakeCompanyRefLenses() CompanyRefLenses {
return CompanyRefLenses{ // mandatory lenses
Name: L.MakeLensStrict( lensName := L.MakeLensStrict(
func(s *Company) string { return s.Name }, func(s *Company) string { return s.Name },
func(s *Company, v string) *Company { s.Name = v; return s }, func(s *Company, v string) *Company { s.Name = v; return s },
), )
Address: L.MakeLensStrict( lensAddress := L.MakeLensStrict(
func(s *Company) Address { return s.Address }, func(s *Company) Address { return s.Address },
func(s *Company, v Address) *Company { s.Address = v; return s }, func(s *Company, v Address) *Company { s.Address = v; return s },
), )
CEO: L.MakeLensStrict( lensCEO := L.MakeLensStrict(
func(s *Company) Person { return s.CEO }, func(s *Company) Person { return s.CEO },
func(s *Company, v Person) *Company { s.CEO = v; return s }, func(s *Company, v Person) *Company { s.CEO = v; return s },
), )
Website: LO.FromIso[*Company](I.FromZero[*string]())(L.MakeLensStrict( lensWebsite := L.MakeLensStrict(
func(s *Company) *string { return s.Website }, func(s *Company) *string { return s.Website },
func(s *Company, v *string) *Company { s.Website = v; return s }, func(s *Company, v *string) *Company { s.Website = v; return s },
)), )
// optional lenses
lensNameO := LO.FromIso[*Company](IO.FromZero[string]())(lensName)
lensAddressO := LO.FromIso[*Company](IO.FromZero[Address]())(lensAddress)
lensCEOO := LO.FromIso[*Company](IO.FromZero[Person]())(lensCEO)
lensWebsiteO := LO.FromIso[*Company](IO.FromZero[*string]())(lensWebsite)
return CompanyRefLenses{
// mandatory lenses
Name: lensName,
Address: lensAddress,
CEO: lensCEO,
Website: lensWebsite,
// optional lenses
NameO: lensNameO,
AddressO: lensAddressO,
CEOO: lensCEOO,
WebsiteO: lensWebsiteO,
}
}
// CompanyExtendedLenses provides lenses for accessing fields of CompanyExtended
type CompanyExtendedLenses struct {
// mandatory fields
Extended L.Lens[CompanyExtended, string]
// optional fields
ExtendedO LO.LensO[CompanyExtended, string]
}
// CompanyExtendedRefLenses provides lenses for accessing fields of CompanyExtended via a reference to CompanyExtended
type CompanyExtendedRefLenses struct {
// mandatory fields
Extended L.Lens[*CompanyExtended, string]
// optional fields
ExtendedO LO.LensO[*CompanyExtended, string]
}
// MakeCompanyExtendedLenses creates a new CompanyExtendedLenses with lenses for all fields
func MakeCompanyExtendedLenses() CompanyExtendedLenses {
// mandatory lenses
lensExtended := L.MakeLens(
func(s CompanyExtended) string { return s.Extended },
func(s CompanyExtended, v string) CompanyExtended { s.Extended = v; return s },
)
// optional lenses
lensExtendedO := LO.FromIso[CompanyExtended](IO.FromZero[string]())(lensExtended)
return CompanyExtendedLenses{
// mandatory lenses
Extended: lensExtended,
// optional lenses
ExtendedO: lensExtendedO,
}
}
// MakeCompanyExtendedRefLenses creates a new CompanyExtendedRefLenses with lenses for all fields
func MakeCompanyExtendedRefLenses() CompanyExtendedRefLenses {
// mandatory lenses
lensExtended := L.MakeLensStrict(
func(s *CompanyExtended) string { return s.Extended },
func(s *CompanyExtended, v string) *CompanyExtended { s.Extended = v; return s },
)
// optional lenses
lensExtendedO := LO.FromIso[*CompanyExtended](IO.FromZero[string]())(lensExtended)
return CompanyExtendedRefLenses{
// mandatory lenses
Extended: lensExtended,
// optional lenses
ExtendedO: lensExtendedO,
} }
} }
// CheckOptionLenses provides lenses for accessing fields of CheckOption // CheckOptionLenses provides lenses for accessing fields of CheckOption
type CheckOptionLenses struct { type CheckOptionLenses struct {
// mandatory fields
Name L.Lens[CheckOption, option.Option[string]] Name L.Lens[CheckOption, option.Option[string]]
Value LO.LensO[CheckOption, string] Value L.Lens[CheckOption, string]
// optional fields
NameO LO.LensO[CheckOption, option.Option[string]]
ValueO LO.LensO[CheckOption, string]
} }
// CheckOptionRefLenses provides lenses for accessing fields of CheckOption via a reference to CheckOption // CheckOptionRefLenses provides lenses for accessing fields of CheckOption via a reference to CheckOption
type CheckOptionRefLenses struct { type CheckOptionRefLenses struct {
// mandatory fields
Name L.Lens[*CheckOption, option.Option[string]] Name L.Lens[*CheckOption, option.Option[string]]
Value LO.LensO[*CheckOption, string] Value L.Lens[*CheckOption, string]
// optional fields
NameO LO.LensO[*CheckOption, option.Option[string]]
ValueO LO.LensO[*CheckOption, string]
} }
// MakeCheckOptionLenses creates a new CheckOptionLenses with lenses for all fields // MakeCheckOptionLenses creates a new CheckOptionLenses with lenses for all fields
func MakeCheckOptionLenses() CheckOptionLenses { func MakeCheckOptionLenses() CheckOptionLenses {
isoValue := I.FromZero[string]() // mandatory lenses
return CheckOptionLenses{ lensName := L.MakeLens(
Name: L.MakeLens(
func(s CheckOption) option.Option[string] { return s.Name }, func(s CheckOption) option.Option[string] { return s.Name },
func(s CheckOption, v option.Option[string]) CheckOption { s.Name = v; return s }, func(s CheckOption, v option.Option[string]) CheckOption { s.Name = v; return s },
), )
Value: L.MakeLens( lensValue := L.MakeLens(
func(s CheckOption) O.Option[string] { return isoValue.Get(s.Value) }, func(s CheckOption) string { return s.Value },
func(s CheckOption, v O.Option[string]) CheckOption { s.Value = isoValue.ReverseGet(v); return s }, func(s CheckOption, v string) CheckOption { s.Value = v; return s },
), )
// optional lenses
lensNameO := LO.FromIso[CheckOption](IO.FromZero[option.Option[string]]())(lensName)
lensValueO := LO.FromIso[CheckOption](IO.FromZero[string]())(lensValue)
return CheckOptionLenses{
// mandatory lenses
Name: lensName,
Value: lensValue,
// optional lenses
NameO: lensNameO,
ValueO: lensValueO,
} }
} }
// MakeCheckOptionRefLenses creates a new CheckOptionRefLenses with lenses for all fields // MakeCheckOptionRefLenses creates a new CheckOptionRefLenses with lenses for all fields
func MakeCheckOptionRefLenses() CheckOptionRefLenses { func MakeCheckOptionRefLenses() CheckOptionRefLenses {
return CheckOptionRefLenses{ // mandatory lenses
Name: L.MakeLensRef( lensName := L.MakeLensRef(
func(s *CheckOption) option.Option[string] { return s.Name }, func(s *CheckOption) option.Option[string] { return s.Name },
func(s *CheckOption, v option.Option[string]) *CheckOption { s.Name = v; return s }, func(s *CheckOption, v option.Option[string]) *CheckOption { s.Name = v; return s },
), )
Value: LO.FromIso[*CheckOption](I.FromZero[string]())(L.MakeLensStrict( lensValue := L.MakeLensStrict(
func(s *CheckOption) string { return s.Value }, func(s *CheckOption) string { return s.Value },
func(s *CheckOption, v string) *CheckOption { s.Value = v; return s }, func(s *CheckOption, v string) *CheckOption { s.Value = v; return s },
)), )
// optional lenses
lensNameO := LO.FromIso[*CheckOption](IO.FromZero[option.Option[string]]())(lensName)
lensValueO := LO.FromIso[*CheckOption](IO.FromZero[string]())(lensValue)
return CheckOptionRefLenses{
// mandatory lenses
Name: lensName,
Value: lensValue,
// optional lenses
NameO: lensNameO,
ValueO: lensValueO,
} }
} }