1
0
mirror of https://github.com/IBM/fp-go.git synced 2026-01-29 10:36:04 +02:00

Compare commits

..

6 Commits

Author SHA1 Message Date
Obed Tetteh
da0344f9bd feat(iterator): add Last function with Option return type (#155)
- Add Last function to retrieve the final element from an iterator,
  returning Some(element) for non-empty sequences and None for empty ones.
- Includes tests covering simple types and  complex types
- Add documentation including example code
2026-01-26 09:04:51 +01:00
Dr. Carsten Leue
cd79dd56b9 fix: simplify tests a bit
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-01-23 17:56:28 +01:00
Dr. Carsten Leue
df07599a9e fix: some docs
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-01-23 16:40:45 +01:00
Dr. Carsten Leue
30ad0e4dd8 doc: add validation docs
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-01-23 16:26:53 +01:00
Dr. Carsten Leue
2374d7f1e4 fix: support unexported fields for lenses
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-01-23 16:18:44 +01:00
Dr. Carsten Leue
eafc008798 fix: doc for lens generation
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-01-23 16:00:11 +01:00
16 changed files with 1208 additions and 85 deletions

View File

@@ -465,7 +465,7 @@ func process() IOResult[string] {
- **ReaderIOResult** - Combine Reader, IO, and Result for complex workflows
- **Array** - Functional array operations
- **Record** - Functional record/map operations
- **Optics** - Lens, Prism, Optional, and Traversal for immutable updates
- **[Optics](./optics/README.md)** - Lens, Prism, Optional, and Traversal for immutable updates
#### Idiomatic Packages (Tuple-based, High Performance)
- **idiomatic/option** - Option monad using native Go `(value, bool)` tuples

View File

@@ -536,9 +536,9 @@ func extractEmbeddedFields(embedType ast.Expr, fileImports map[string]string, fi
}
for _, name := range field.Names {
// Only export lenses for exported fields
if name.IsExported() {
fieldTypeName := getTypeName(field.Type)
// Generate lenses for both exported and unexported fields
fieldTypeName := getTypeName(field.Type)
if true { // Keep the block structure for minimal changes
isOptional := false
baseType := fieldTypeName
@@ -698,9 +698,9 @@ func parseFile(filename string) ([]structInfo, string, error) {
continue
}
for _, name := range field.Names {
// Only export lenses for exported fields
if name.IsExported() {
typeName := getTypeName(field.Type)
// Generate lenses for both exported and unexported fields
typeName := getTypeName(field.Type)
if true { // Keep the block structure for minimal changes
isOptional := false
baseType := typeName
isComparable := false

View File

@@ -1086,3 +1086,255 @@ type ComparableBox[T comparable] struct {
// Verify that MakeLensRef is NOT used (since both fields are comparable)
assert.NotContains(t, contentStr, "__lens.MakeLensRefWithName(", "Should not use MakeLensRefWithName when all fields are comparable")
}
func TestParseFileWithUnexportedFields(t *testing.T) {
// Create a temporary test file
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "test.go")
testCode := `package testpkg
// fp-go:Lens
type Config struct {
PublicName string
privateName string
PublicValue int
privateValue *int
}
`
err := os.WriteFile(testFile, []byte(testCode), 0o644)
require.NoError(t, err)
// Parse the file
structs, pkg, err := parseFile(testFile)
require.NoError(t, err)
// Verify results
assert.Equal(t, "testpkg", pkg)
assert.Len(t, structs, 1)
// Check Config struct
config := structs[0]
assert.Equal(t, "Config", config.Name)
assert.Len(t, config.Fields, 4, "Should include both exported and unexported fields")
// Check exported field
assert.Equal(t, "PublicName", config.Fields[0].Name)
assert.Equal(t, "string", config.Fields[0].TypeName)
assert.False(t, config.Fields[0].IsOptional)
// Check unexported field
assert.Equal(t, "privateName", config.Fields[1].Name)
assert.Equal(t, "string", config.Fields[1].TypeName)
assert.False(t, config.Fields[1].IsOptional)
// Check exported int field
assert.Equal(t, "PublicValue", config.Fields[2].Name)
assert.Equal(t, "int", config.Fields[2].TypeName)
assert.False(t, config.Fields[2].IsOptional)
// Check unexported pointer field
assert.Equal(t, "privateValue", config.Fields[3].Name)
assert.Equal(t, "*int", config.Fields[3].TypeName)
assert.True(t, config.Fields[3].IsOptional)
}
func TestGenerateLensHelpersWithUnexportedFields(t *testing.T) {
// Create a temporary directory with test files
tmpDir := t.TempDir()
testCode := `package testpkg
// fp-go:Lens
type MixedStruct struct {
PublicField string
privateField int
OptionalPrivate *string
}
`
testFile := filepath.Join(tmpDir, "test.go")
err := os.WriteFile(testFile, []byte(testCode), 0o644)
require.NoError(t, err)
// Generate lens code
outputFile := "gen_lens.go"
err = generateLensHelpers(tmpDir, outputFile, false, false)
require.NoError(t, err)
// Verify the generated file exists
genPath := filepath.Join(tmpDir, outputFile)
_, err = os.Stat(genPath)
require.NoError(t, err)
// Read and verify the generated content
content, err := os.ReadFile(genPath)
require.NoError(t, err)
contentStr := string(content)
// Check for expected content
assert.Contains(t, contentStr, "package testpkg")
assert.Contains(t, contentStr, "MixedStructLenses")
assert.Contains(t, contentStr, "MakeMixedStructLenses")
// Check that lenses are generated for all fields (exported and unexported)
assert.Contains(t, contentStr, "PublicField __lens.Lens[MixedStruct, string]")
assert.Contains(t, contentStr, "privateField __lens.Lens[MixedStruct, int]")
assert.Contains(t, contentStr, "OptionalPrivate __lens.Lens[MixedStruct, *string]")
// Check lens constructors
assert.Contains(t, contentStr, "func(s MixedStruct) string { return s.PublicField }")
assert.Contains(t, contentStr, "func(s MixedStruct) int { return s.privateField }")
assert.Contains(t, contentStr, "func(s MixedStruct) *string { return s.OptionalPrivate }")
// Check setters
assert.Contains(t, contentStr, "func(s MixedStruct, v string) MixedStruct { s.PublicField = v; return s }")
assert.Contains(t, contentStr, "func(s MixedStruct, v int) MixedStruct { s.privateField = v; return s }")
assert.Contains(t, contentStr, "func(s MixedStruct, v *string) MixedStruct { s.OptionalPrivate = v; return s }")
}
func TestParseFileWithOnlyUnexportedFields(t *testing.T) {
// Create a temporary test file
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "test.go")
testCode := `package testpkg
// fp-go:Lens
type PrivateConfig struct {
name string
value int
enabled bool
}
`
err := os.WriteFile(testFile, []byte(testCode), 0o644)
require.NoError(t, err)
// Parse the file
structs, pkg, err := parseFile(testFile)
require.NoError(t, err)
// Verify results
assert.Equal(t, "testpkg", pkg)
assert.Len(t, structs, 1)
// Check PrivateConfig struct
config := structs[0]
assert.Equal(t, "PrivateConfig", config.Name)
assert.Len(t, config.Fields, 3, "Should include all unexported fields")
// Check all fields are unexported
assert.Equal(t, "name", config.Fields[0].Name)
assert.Equal(t, "value", config.Fields[1].Name)
assert.Equal(t, "enabled", config.Fields[2].Name)
}
func TestGenerateLensHelpersWithUnexportedEmbeddedFields(t *testing.T) {
// Create a temporary directory with test files
tmpDir := t.TempDir()
testCode := `package testpkg
type BaseConfig struct {
publicBase string
privateBase int
}
// fp-go:Lens
type ExtendedConfig struct {
BaseConfig
PublicField string
privateField bool
}
`
testFile := filepath.Join(tmpDir, "test.go")
err := os.WriteFile(testFile, []byte(testCode), 0o644)
require.NoError(t, err)
// Generate lens code
outputFile := "gen_lens.go"
err = generateLensHelpers(tmpDir, outputFile, false, false)
require.NoError(t, err)
// Verify the generated file exists
genPath := filepath.Join(tmpDir, outputFile)
_, err = os.Stat(genPath)
require.NoError(t, err)
// Read and verify the generated content
content, err := os.ReadFile(genPath)
require.NoError(t, err)
contentStr := string(content)
// Check for expected content
assert.Contains(t, contentStr, "package testpkg")
assert.Contains(t, contentStr, "ExtendedConfigLenses")
// Check that lenses are generated for embedded unexported fields
assert.Contains(t, contentStr, "publicBase __lens.Lens[ExtendedConfig, string]")
assert.Contains(t, contentStr, "privateBase __lens.Lens[ExtendedConfig, int]")
// Check that lenses are generated for direct fields (both exported and unexported)
assert.Contains(t, contentStr, "PublicField __lens.Lens[ExtendedConfig, string]")
assert.Contains(t, contentStr, "privateField __lens.Lens[ExtendedConfig, bool]")
}
func TestParseFileWithMixedFieldVisibility(t *testing.T) {
// Create a temporary test file with various field visibility patterns
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "test.go")
testCode := `package testpkg
// fp-go:Lens
type ComplexStruct struct {
// Exported fields
Name string
Age int
Email *string
// Unexported fields
password string
secretKey []byte
internalID *int
// Mixed with tags
PublicWithTag string ` + "`json:\"public,omitempty\"`" + `
privateWithTag int ` + "`json:\"private,omitempty\"`" + `
}
`
err := os.WriteFile(testFile, []byte(testCode), 0o644)
require.NoError(t, err)
// Parse the file
structs, pkg, err := parseFile(testFile)
require.NoError(t, err)
// Verify results
assert.Equal(t, "testpkg", pkg)
assert.Len(t, structs, 1)
// Check ComplexStruct
complex := structs[0]
assert.Equal(t, "ComplexStruct", complex.Name)
assert.Len(t, complex.Fields, 8, "Should include all fields regardless of visibility")
// Verify field names and types
fieldNames := []string{"Name", "Age", "Email", "password", "secretKey", "internalID", "PublicWithTag", "privateWithTag"}
for i, expectedName := range fieldNames {
assert.Equal(t, expectedName, complex.Fields[i].Name, "Field %d should be %s", i, expectedName)
}
// Check optional fields
assert.False(t, complex.Fields[0].IsOptional, "Name should not be optional")
assert.True(t, complex.Fields[2].IsOptional, "Email (pointer) should be optional")
assert.True(t, complex.Fields[5].IsOptional, "internalID (pointer) should be optional")
assert.True(t, complex.Fields[6].IsOptional, "PublicWithTag (with omitempty) should be optional")
assert.True(t, complex.Fields[7].IsOptional, "privateWithTag (with omitempty) should be optional")
}

54
v2/iterator/iter/last.go Normal file
View File

@@ -0,0 +1,54 @@
package iter
import (
"github.com/IBM/fp-go/v2/option"
)
// Last returns the last element from an [Iterator] wrapped in an [Option].
//
// This function retrieves the last element from the iterator by consuming the entire
// sequence. If the iterator contains at least one element, it returns Some(element).
// If the iterator is empty, it returns None.
//
// RxJS Equivalent: [last] - https://rxjs.dev/api/operators/last
//
// Type Parameters:
// - U: The type of elements in the iterator
//
// Parameters:
// - it: The input iterator to get the last element from
//
// Returns:
// - Option[U]: Some(last element) if the iterator is non-empty, None otherwise
//
// Example with non-empty sequence:
//
// seq := iter.From(1, 2, 3, 4, 5)
// last := iter.Last(seq)
// // Returns: Some(5)
//
// Example with empty sequence:
//
// seq := iter.Empty[int]()
// last := iter.Last(seq)
// // Returns: None
//
// Example with filtered sequence:
//
// seq := iter.From(1, 2, 3, 4, 5)
// filtered := iter.Filter(func(x int) bool { return x < 4 })(seq)
// last := iter.Last(filtered)
// // Returns: Some(3)
func Last[U any](it Seq[U]) Option[U] {
var last U
found := false
for last = range it {
found = true
}
if !found {
return option.None[U]()
}
return option.Some(last)
}

View File

@@ -0,0 +1,305 @@
package iter
import (
"fmt"
"testing"
"github.com/IBM/fp-go/v2/function"
F "github.com/IBM/fp-go/v2/function"
N "github.com/IBM/fp-go/v2/number"
O "github.com/IBM/fp-go/v2/option"
"github.com/stretchr/testify/assert"
)
// TestLast test getting the last element from a non-empty sequence
func TestLastSimple(t *testing.T) {
t.Run("returns last element from integer sequence", func(t *testing.T) {
seq := From(1, 2, 3)
last := Last(seq)
assert.Equal(t, O.Of(3), last)
})
t.Run("returns last element from string sequence", func(t *testing.T) {
seq := From("a", "b", "c")
last := Last(seq)
assert.Equal(t, O.Of("c"), last)
})
t.Run("returns last element from single element sequence", func(t *testing.T) {
seq := From(42)
last := Last(seq)
assert.Equal(t, O.Of(42), last)
})
t.Run("returns last element from large sequence", func(t *testing.T) {
seq := From(100, 200, 300, 400, 500)
last := Last(seq)
assert.Equal(t, O.Of(500), last)
})
}
// TestLastEmpty tests getting the last element from an empty sequence
func TestLastEmpty(t *testing.T) {
t.Run("returns None for empty integer sequence", func(t *testing.T) {
seq := Empty[int]()
last := Last(seq)
assert.Equal(t, O.None[int](), last)
})
t.Run("returns None for empty string sequence", func(t *testing.T) {
seq := Empty[string]()
last := Last(seq)
assert.Equal(t, O.None[string](), last)
})
t.Run("returns None for empty struct sequence", func(t *testing.T) {
type TestStruct struct {
Value int
}
seq := Empty[TestStruct]()
last := Last(seq)
assert.Equal(t, O.None[TestStruct](), last)
})
t.Run("returns None for empty sequence of functions", func(t *testing.T) {
type TestFunc func(int)
seq := Empty[TestFunc]()
last := Last(seq)
assert.Equal(t, O.None[TestFunc](), last)
})
}
// TestLastWithComplex tests Last with complex types
func TestLastWithComplex(t *testing.T) {
type Person struct {
Name string
Age int
}
t.Run("returns last person", func(t *testing.T) {
seq := From(
Person{"Alice", 30},
Person{"Bob", 25},
Person{"Charlie", 35},
)
last := Last(seq)
expected := O.Of(Person{"Charlie", 35})
assert.Equal(t, expected, last)
})
t.Run("returns last pointer", func(t *testing.T) {
p1 := &Person{"Alice", 30}
p2 := &Person{"Bob", 25}
seq := From(p1, p2)
last := Last(seq)
assert.Equal(t, O.Of(p2), last)
})
}
func TestLastWithFunctions(t *testing.T) {
t.Run("return function", func(t *testing.T) {
want := "last"
f1 := function.Constant("first")
f2 := function.Constant("last")
seq := From(f1, f2)
getLast := function.Flow2(
Last,
O.Map(funcReader),
)
assert.Equal(t, O.Of(want), getLast(seq))
})
}
func funcReader(f func() string) string {
return f()
}
// TestLastWithChan tests Last with channels
func TestLastWithChan(t *testing.T) {
t.Run("return function", func(t *testing.T) {
want := 30
seq := From(intChan(10),
intChan(20),
intChan(want))
getLast := function.Flow2(
Last,
O.Map(chanReader[int]),
)
assert.Equal(t, O.Of(want), getLast(seq))
})
}
func chanReader[T any](c <-chan T) T {
return <-c
}
func intChan(val int) <-chan int {
ch := make(chan int, 1)
ch <- val
close(ch)
return ch
}
// TestLastWithChainedOperations tests Last with multiple chained operations
func TestLastWithChainedOperations(t *testing.T) {
t.Run("chains filter, map, and last", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
filtered := MonadFilter(seq, N.MoreThan(5))
mapped := MonadMap(filtered, N.Mul(10))
result := Last(mapped)
assert.Equal(t, O.Of(100), result)
})
t.Run("chains map and filter", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
mapped := MonadMap(seq, N.Mul(2))
filtered := MonadFilter(mapped, N.MoreThan(5))
result := Last(filtered)
assert.Equal(t, O.Of(10), result)
})
}
// TestLastWithReplicate tests Last with replicated values
func TestLastWithReplicate(t *testing.T) {
t.Run("returns last from replicated sequence", func(t *testing.T) {
seq := Replicate(5, 42)
last := Last(seq)
assert.Equal(t, O.Of(42), last)
})
t.Run("returns None from zero replications", func(t *testing.T) {
seq := Replicate(0, 42)
last := Last(seq)
assert.Equal(t, O.None[int](), last)
})
}
// TestLastWithMakeBy tests Last with MakeBy
func TestLastWithMakeBy(t *testing.T) {
t.Run("returns last generated element", func(t *testing.T) {
seq := MakeBy(5, func(i int) int { return i * i })
last := Last(seq)
assert.Equal(t, O.Of(16), last)
})
t.Run("returns None for zero elements", func(t *testing.T) {
seq := MakeBy(0, F.Identity[int])
last := Last(seq)
assert.Equal(t, O.None[int](), last)
})
}
// TestLastWithPrepend tests Last with Prepend
func TestLastWithPrepend(t *testing.T) {
t.Run("returns last element, not prepended", func(t *testing.T) {
seq := From(2, 3, 4)
prepended := Prepend(1)(seq)
last := Last(prepended)
assert.Equal(t, O.Of(4), last)
})
t.Run("returns prepended element from empty sequence", func(t *testing.T) {
seq := Empty[int]()
prepended := Prepend(42)(seq)
last := Last(prepended)
assert.Equal(t, O.Of(42), last)
})
}
// TestLastWithAppend tests Last with Append
func TestLastWithAppend(t *testing.T) {
t.Run("returns appended element", func(t *testing.T) {
seq := From(1, 2, 3)
appended := Append(4)(seq)
last := Last(appended)
assert.Equal(t, O.Of(4), last)
})
t.Run("returns appended element from empty sequence", func(t *testing.T) {
seq := Empty[int]()
appended := Append(42)(seq)
last := Last(appended)
assert.Equal(t, O.Of(42), last)
})
}
// TestLastWithChain tests Last with Chain (flatMap)
func TestLastWithChain(t *testing.T) {
t.Run("returns last from chained sequence", func(t *testing.T) {
seq := From(1, 2, 3)
chained := MonadChain(seq, func(x int) Seq[int] {
return From(x, x*10)
})
last := Last(chained)
assert.Equal(t, O.Of(30), last)
})
t.Run("returns None when chain produces empty", func(t *testing.T) {
seq := From(1, 2, 3)
chained := MonadChain(seq, func(x int) Seq[int] {
return Empty[int]()
})
last := Last(chained)
assert.Equal(t, O.None[int](), last)
})
}
// TestLastWithFlatten tests Last with Flatten
func TestLastWithFlatten(t *testing.T) {
t.Run("returns last from flattened sequence", func(t *testing.T) {
nested := From(From(1, 2), From(3, 4), From(5))
flattened := Flatten(nested)
last := Last(flattened)
assert.Equal(t, O.Of(5), last)
})
t.Run("returns None from empty nested sequence", func(t *testing.T) {
nested := Empty[Seq[int]]()
flattened := Flatten(nested)
last := Last(flattened)
assert.Equal(t, O.None[int](), last)
})
}
// Example tests for documentation
func ExampleLast() {
seq := From(1, 2, 3, 4, 5)
last := Last(seq)
if value, ok := O.Unwrap(last); ok {
fmt.Printf("Last element: %d\n", value)
}
// Output: Last element: 5
}
func ExampleLast_empty() {
seq := Empty[int]()
last := Last(seq)
if _, ok := O.Unwrap(last); !ok {
fmt.Println("Sequence is empty")
}
// Output: Sequence is empty
}
func ExampleLast_functions() {
f1 := function.Constant("first")
f2 := function.Constant("middle")
f3 := function.Constant("last")
seq := From(f1, f2, f3)
last := Last(seq)
if fn, ok := O.Unwrap(last); ok {
result := fn()
fmt.Printf("Last function result: %s\n", result)
}
// Output: Last function result: last
}

View File

@@ -221,7 +221,7 @@ Lenses can be automatically generated using the `fp-go` CLI tool and a simple an
1. **Annotate your struct** with the `fp-go:Lens` comment:
```go
//go:generate go run github.com/IBM/fp-go/v2/main.go lens --dir . --filename gen_lens.go
//go:generate go run github.com/IBM/fp-go/v2 lens --dir . --filename gen_lens.go
// fp-go:Lens
type Person struct {
@@ -230,8 +230,16 @@ type Person struct {
Email string
Phone *string // Optional field
}
// fp-go:Lens
type Config struct {
PublicField string
privateField int // Unexported fields are supported!
}
```
**Note:** The generator supports both exported (uppercase) and unexported (lowercase) fields. Generated lenses for unexported fields will have lowercase names and can only be used within the same package as the struct.
2. **Run `go generate`**:
```bash
@@ -268,6 +276,7 @@ The generator supports:
- ✅ Embedded structs (fields are promoted)
- ✅ Optional fields (pointers and `omitempty` tags)
- ✅ Custom package imports
-**Unexported fields** (lowercase names) - lenses will have lowercase names matching the field names
See [samples/lens](../samples/lens) for complete examples.
@@ -293,13 +302,23 @@ More specific optics can be converted to more general ones.
## 📦 Package Structure
### Core Optics
- **[optics/lens](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/lens)**: Lenses for product types (structs)
- **[optics/prism](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/prism)**: Prisms for sum types ([`Either`](https://pkg.go.dev/github.com/IBM/fp-go/v2/either), [`Result`](https://pkg.go.dev/github.com/IBM/fp-go/v2/result), etc.)
- **[optics/iso](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/iso)**: Isomorphisms for equivalent types
- **[optics/optional](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/optional)**: Optional optics for maybe values
- **[optics/traversal](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/traversal)**: Traversals for multiple values
Each package includes specialized sub-packages for common patterns:
### Utilities
- **[optics/builder](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/builder)**: Builder pattern for constructing complex optics
- **[optics/codec](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/codec)**: Type-safe encoding/decoding with validation
- Provides `Type[A, O, I]` for bidirectional transformations with validation
- Includes codecs for primitives (String, Int, Bool), collections (Array), and sum types (Either)
- Supports refinement types and codec composition via `Pipe`
- Integrates validation errors with context tracking
### Specialized Sub-packages
Each core optics package includes specialized sub-packages for common patterns:
- **array**: Optics for arrays/slices
- **either**: Optics for [`Either`](https://pkg.go.dev/github.com/IBM/fp-go/v2/either) types
- **option**: Optics for [`Option`](https://pkg.go.dev/github.com/IBM/fp-go/v2/option) types

View File

@@ -0,0 +1,235 @@
# 🏗️ Builder Pattern with fp-go
This package demonstrates a functional builder pattern using fp-go's optics library. It shows how to construct and validate objects using lenses, prisms, and codecs, separating the building phase from validation.
## 📋 Overview
The builder pattern here uses two key types:
- **`PartialPerson`** 🚧: An intermediate type with unvalidated fields (raw `string` and `int`)
- **`Person`** ✅: A validated type with refined fields (`NonEmptyString` and `AdultAge`)
The pattern provides two approaches for validation:
1. **Prism-based validation** 🔍 (simple, no error messages)
2. **Codec-based validation** 📝 (detailed error reporting)
## 🎯 Core Concepts
### 1. 🔧 Auto-Generated Lenses
The `fp-go:Lens` directive in `types.go` generates lens accessors for both types:
```go
// fp-go:Lens
type PartialPerson struct {
name string
age int
}
// fp-go:Lens
type Person struct {
Name NonEmptyString
Age AdultAge
}
```
This generates:
- `partialPersonLenses` with `.name` and `.age` lenses
- `personLenses` with `.Name` and `.Age` lenses
### 2. 🎁 Exporting Setters as `WithXXX` Methods
The lens setters are exported as builder methods:
```go
// WithName sets the Name field of a PartialPerson
WithName = partialPersonLenses.name.Set
// WithAge sets the Age field of a PartialPerson
WithAge = partialPersonLenses.age.Set
```
These return `Endomorphism[*PartialPerson]` functions that can be composed:
```go
builder := F.Pipe1(
A.From(
WithName("Alice"),
WithAge(25),
),
allOfPartialPerson,
)
partial := builder(&PartialPerson{})
```
Or use the convenience function:
```go
builder := MakePerson("Alice", 25)
```
## 🔍 Approach 1: Prism-Based Validation (No Error Messages)
### Creating Validation Prisms
Define prisms that validate individual fields:
> 💡 **Tip**: The `optics/prism` package provides many helpful out-of-the-box prisms for common validations, including:
> - `NonEmptyString()` - validates non-empty strings
> - `ParseInt()`, `ParseInt64()` - parses integers from strings
> - `ParseFloat32()`, `ParseFloat64()` - parses floats from strings
> - `ParseBool()` - parses booleans from strings
> - `ParseDate(layout)` - parses dates with custom layouts
> - `ParseURL()` - parses URLs
> - `FromZero()`, `FromNonZero()` - validates zero/non-zero values
> - `RegexMatcher()`, `RegexNamedMatcher()` - regex-based validation
> - `FromOption()`, `FromEither()`, `FromResult()` - extracts from monadic types
> - And many more! Check `optics/prism/prisms.go` for the full list.
>
> For custom validation logic, create your own prisms:
```go
namePrism = prism.MakePrismWithName(
func(s string) Option[NonEmptyString] {
if S.IsEmpty(s) {
return option.None[NonEmptyString]()
}
return option.Of(NonEmptyString(s))
},
func(ns NonEmptyString) string {
return string(ns)
},
"NonEmptyString",
)
agePrism = prism.MakePrismWithName(
func(a int) Option[AdultAge] {
if a < 18 {
return option.None[AdultAge]()
}
return option.Of(AdultAge(a))
},
func(aa AdultAge) int {
return int(aa)
},
"AdultAge",
)
```
### 🎭 Creating the PersonPrism
The `PersonPrism` converts between a builder and a validated `Person`:
```go
PersonPrism = prism.MakePrismWithName(
buildPerson(), // Forward: builder -> Option[*Person]
buildEndomorphism(), // Reverse: *Person -> builder
"Person",
)
```
**Forward direction** ➡️ (`buildPerson`):
1. Applies the builder to an empty `PartialPerson`
2. Validates each field using field prisms
3. Returns `Some(*Person)` if all validations pass, `None` otherwise
**Reverse direction** ⬅️ (`buildEndomorphism`):
1. Extracts validated fields from `Person`
2. Converts them back to raw types
3. Returns a builder that reconstructs the `PartialPerson`
### 💡 Usage Example
```go
// Create a builder
builder := MakePerson("Alice", 25)
// Validate and convert to Person
maybePerson := PersonPrism.GetOption(builder)
// maybePerson is Option[*Person]
// - Some(*Person) if validation succeeds ✅
// - None if validation fails (no error details) ❌
```
## 📝 Approach 2: Codec-Based Validation (With Error Messages)
### Creating Field Codecs
Convert prisms to codecs for detailed validation:
```go
nameCodec = codec.FromRefinement(namePrism)
ageCodec = codec.FromRefinement(agePrism)
```
### 🎯 Creating the PersonCodec
The `PersonCodec` provides bidirectional transformation with validation:
```go
func makePersonCodec() PersonCodec {
return codec.MakeType(
"Person",
codec.Is[*Person](),
makePersonValidate(), // Validation with error reporting
buildEndomorphism(), // Encoding (same as prism)
)
}
```
The `makePersonValidate` function:
1. Applies the builder to an empty `PartialPerson`
2. Validates each field using field codecs
3. Accumulates validation errors using applicative composition 📚
4. Returns `Validation[*Person]` (either errors or a valid `Person`)
### 💡 Usage Example
```go
// Create a builder
builder := MakePerson("", 15) // Invalid: empty name, age < 18
// Validate with detailed errors
personCodec := makePersonCodec()
validation := personCodec.Validate(builder)
// validation is Validation[*Person]
// - Right(*Person) if validation succeeds ✅
// - Left(ValidationErrors) with detailed error messages if validation fails ❌
```
## ⚖️ Key Differences
| Feature | Prism-Based 🔍 | Codec-Based 📝 |
|---------|-------------|-------------|
| Error Messages | No (returns `None`) ❌ | Yes (returns detailed errors) ✅ |
| Complexity | Simpler 🟢 | More complex 🟡 |
| Use Case | Simple validation | Production validation with user feedback |
| Return Type | `Option[*Person]` | `Validation[*Person]` |
## 📝 Pattern Summary
1. **Define types** 📐: Create `PartialPerson` (unvalidated) and `Person` (validated)
2. **Generate lenses** 🔧: Use `fp-go:Lens` directive
3. **Export setters** 🎁: Create `WithXXX` methods from lens setters
4. **Create validation prisms** 🎭: Define validation rules for each field
5. **Choose validation approach** ⚖️:
- **Simple** 🔍: Create a `Prism` for quick validation without errors
- **Detailed** 📝: Create a `Codec` for validation with error reporting
## ✨ Benefits
- **Type Safety** 🛡️: Validated types guarantee business rules at compile time
- **Composability** 🧩: Builders can be composed using monoid operations
- **Bidirectional** ↔️: Both prisms and codecs support encoding and decoding
- **Separation of Concerns** 🎯: Building and validation are separate phases
- **Functional** 🔄: Pure functions, no mutation, easy to test
## 📁 Files
- `types.go`: Type definitions and lens generation directives
- `builder.go`: Prism-based builder implementation
- `codec.go`: Codec-based validation implementation
- `codec_test.go`: Tests demonstrating usage patterns

View File

@@ -27,10 +27,10 @@ var (
personLenses = MakePersonRefLenses()
// emptyPartialPerson is a zero-value PartialPerson used as a starting point for building.
emptyPartialPerson = &PartialPerson{}
emptyPartialPerson = F.Zero[*PartialPerson]()
// emptyPerson is a zero-value Person used as a starting point for validation.
emptyPerson = &Person{}
emptyPerson = F.Zero[*Person]()
// monoidPartialPerson is a monoid for composing endomorphisms on PartialPerson.
// Allows combining multiple builder operations.
@@ -90,7 +90,7 @@ var (
// Example:
// builder := WithName("Alice")
// person := builder(&PartialPerson{})
WithName = partialPersonLenses.Name.Set
WithName = partialPersonLenses.name.Set
// WithAge is a builder function that sets the Age field of a PartialPerson.
// It returns an endomorphism that can be composed with other builder operations.
@@ -98,7 +98,7 @@ var (
// Example:
// builder := WithAge(25)
// person := builder(&PartialPerson{})
WithAge = partialPersonLenses.Age.Set
WithAge = partialPersonLenses.age.Set
// PersonPrism is a prism that converts between a builder pattern (Endomorphism[*PartialPerson])
// and a validated Person in both directions.
@@ -160,7 +160,7 @@ func buildPerson() ReaderOption[Endomorphism[*PartialPerson], *Person] {
// maybeName extracts the name from PartialPerson, validates it,
// and creates a setter for the Person's Name field if valid
maybeName := F.Flow3(
partialPersonLenses.Name.Get,
partialPersonLenses.name.Get,
namePrism.GetOption,
option.Map(personLenses.Name.Set),
)
@@ -168,7 +168,7 @@ func buildPerson() ReaderOption[Endomorphism[*PartialPerson], *Person] {
// maybeAge extracts the age from PartialPerson, validates it,
// and creates a setter for the Person's Age field if valid
maybeAge := F.Flow3(
partialPersonLenses.Age.Get,
partialPersonLenses.age.Get,
agePrism.GetOption,
option.Map(personLenses.Age.Set),
)
@@ -200,7 +200,7 @@ func buildEndomorphism() Reader[*Person, Endomorphism[*PartialPerson]] {
name := F.Flow3(
personLenses.Name.Get,
namePrism.ReverseGet,
partialPersonLenses.Name.Set,
partialPersonLenses.name.Set,
)
// age extracts the validated age, converts it to int,
@@ -208,7 +208,7 @@ func buildEndomorphism() Reader[*Person, Endomorphism[*PartialPerson]] {
age := F.Flow3(
personLenses.Age.Get,
agePrism.ReverseGet,
partialPersonLenses.Age.Set,
partialPersonLenses.age.Set,
)
// Combine the field extractors into a single builder

View File

@@ -81,7 +81,7 @@ func makePersonValidate() Validate[Endomorphism[*PartialPerson], *Person] {
// 2. Validate using nameCodec (ensures non-empty)
// 3. Map to a Person name setter if valid
valName := F.Flow3(
partialPersonLenses.Name.Get,
partialPersonLenses.name.Get,
nameCodec.Validate,
decode.Map[validation.Context](personLenses.Name.Set),
)
@@ -91,7 +91,7 @@ func makePersonValidate() Validate[Endomorphism[*PartialPerson], *Person] {
// 2. Validate using ageCodec (ensures >= 18)
// 3. Map to a Person age setter if valid
valAge := F.Flow3(
partialPersonLenses.Age.Get,
partialPersonLenses.age.Get,
ageCodec.Validate,
decode.Map[validation.Context](personLenses.Age.Set),
)

View File

@@ -151,8 +151,8 @@ func TestMakePersonCodec_Encode(t *testing.T) {
partial := builder(emptyPartialPerson)
// Assert
assert.Equal(t, "Eve", partial.Name)
assert.Equal(t, 28, partial.Age)
assert.Equal(t, "Eve", partial.name)
assert.Equal(t, 28, partial.age)
}
// TestMakePersonCodec_RoundTrip tests encoding and decoding round-trip

View File

@@ -2,7 +2,7 @@ package builder
// Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at
// 2026-01-23 12:56:01.1431839 +0100 CET m=+0.004353901
// 2026-01-23 16:15:30.703391 +0100 CET m=+0.003782501
import (
__lens "github.com/IBM/fp-go/v2/optics/lens"
@@ -15,105 +15,105 @@ import (
// PartialPersonLenses provides lenses for accessing fields of PartialPerson
type PartialPersonLenses struct {
// mandatory fields
Name __lens.Lens[PartialPerson, string]
Age __lens.Lens[PartialPerson, int]
name __lens.Lens[PartialPerson, string]
age __lens.Lens[PartialPerson, int]
// optional fields
NameO __lens_option.LensO[PartialPerson, string]
AgeO __lens_option.LensO[PartialPerson, int]
nameO __lens_option.LensO[PartialPerson, string]
ageO __lens_option.LensO[PartialPerson, int]
}
// PartialPersonRefLenses provides lenses for accessing fields of PartialPerson via a reference to PartialPerson
type PartialPersonRefLenses struct {
// mandatory fields
Name __lens.Lens[*PartialPerson, string]
Age __lens.Lens[*PartialPerson, int]
name __lens.Lens[*PartialPerson, string]
age __lens.Lens[*PartialPerson, int]
// optional fields
NameO __lens_option.LensO[*PartialPerson, string]
AgeO __lens_option.LensO[*PartialPerson, int]
nameO __lens_option.LensO[*PartialPerson, string]
ageO __lens_option.LensO[*PartialPerson, int]
// prisms
NameP __prism.Prism[*PartialPerson, string]
AgeP __prism.Prism[*PartialPerson, int]
nameP __prism.Prism[*PartialPerson, string]
ageP __prism.Prism[*PartialPerson, int]
}
// PartialPersonPrisms provides prisms for accessing fields of PartialPerson
type PartialPersonPrisms struct {
Name __prism.Prism[PartialPerson, string]
Age __prism.Prism[PartialPerson, int]
name __prism.Prism[PartialPerson, string]
age __prism.Prism[PartialPerson, int]
}
// MakePartialPersonLenses creates a new PartialPersonLenses with lenses for all fields
func MakePartialPersonLenses() PartialPersonLenses {
// mandatory lenses
lensName := __lens.MakeLensWithName(
func(s PartialPerson) string { return s.Name },
func(s PartialPerson, v string) PartialPerson { s.Name = v; return s },
"PartialPerson.Name",
lensname := __lens.MakeLensWithName(
func(s PartialPerson) string { return s.name },
func(s PartialPerson, v string) PartialPerson { s.name = v; return s },
"PartialPerson.name",
)
lensAge := __lens.MakeLensWithName(
func(s PartialPerson) int { return s.Age },
func(s PartialPerson, v int) PartialPerson { s.Age = v; return s },
"PartialPerson.Age",
lensage := __lens.MakeLensWithName(
func(s PartialPerson) int { return s.age },
func(s PartialPerson, v int) PartialPerson { s.age = v; return s },
"PartialPerson.age",
)
// optional lenses
lensNameO := __lens_option.FromIso[PartialPerson](__iso_option.FromZero[string]())(lensName)
lensAgeO := __lens_option.FromIso[PartialPerson](__iso_option.FromZero[int]())(lensAge)
lensnameO := __lens_option.FromIso[PartialPerson](__iso_option.FromZero[string]())(lensname)
lensageO := __lens_option.FromIso[PartialPerson](__iso_option.FromZero[int]())(lensage)
return PartialPersonLenses{
// mandatory lenses
Name: lensName,
Age: lensAge,
name: lensname,
age: lensage,
// optional lenses
NameO: lensNameO,
AgeO: lensAgeO,
nameO: lensnameO,
ageO: lensageO,
}
}
// MakePartialPersonRefLenses creates a new PartialPersonRefLenses with lenses for all fields
func MakePartialPersonRefLenses() PartialPersonRefLenses {
// mandatory lenses
lensName := __lens.MakeLensStrictWithName(
func(s *PartialPerson) string { return s.Name },
func(s *PartialPerson, v string) *PartialPerson { s.Name = v; return s },
"(*PartialPerson).Name",
lensname := __lens.MakeLensStrictWithName(
func(s *PartialPerson) string { return s.name },
func(s *PartialPerson, v string) *PartialPerson { s.name = v; return s },
"(*PartialPerson).name",
)
lensAge := __lens.MakeLensStrictWithName(
func(s *PartialPerson) int { return s.Age },
func(s *PartialPerson, v int) *PartialPerson { s.Age = v; return s },
"(*PartialPerson).Age",
lensage := __lens.MakeLensStrictWithName(
func(s *PartialPerson) int { return s.age },
func(s *PartialPerson, v int) *PartialPerson { s.age = v; return s },
"(*PartialPerson).age",
)
// optional lenses
lensNameO := __lens_option.FromIso[*PartialPerson](__iso_option.FromZero[string]())(lensName)
lensAgeO := __lens_option.FromIso[*PartialPerson](__iso_option.FromZero[int]())(lensAge)
lensnameO := __lens_option.FromIso[*PartialPerson](__iso_option.FromZero[string]())(lensname)
lensageO := __lens_option.FromIso[*PartialPerson](__iso_option.FromZero[int]())(lensage)
return PartialPersonRefLenses{
// mandatory lenses
Name: lensName,
Age: lensAge,
name: lensname,
age: lensage,
// optional lenses
NameO: lensNameO,
AgeO: lensAgeO,
nameO: lensnameO,
ageO: lensageO,
}
}
// MakePartialPersonPrisms creates a new PartialPersonPrisms with prisms for all fields
func MakePartialPersonPrisms() PartialPersonPrisms {
_fromNonZeroName := __option.FromNonZero[string]()
_prismName := __prism.MakePrismWithName(
func(s PartialPerson) __option.Option[string] { return _fromNonZeroName(s.Name) },
_fromNonZeroname := __option.FromNonZero[string]()
_prismname := __prism.MakePrismWithName(
func(s PartialPerson) __option.Option[string] { return _fromNonZeroname(s.name) },
func(v string) PartialPerson {
return PartialPerson{ Name: v }
return PartialPerson{ name: v }
},
"PartialPerson.Name",
"PartialPerson.name",
)
_fromNonZeroAge := __option.FromNonZero[int]()
_prismAge := __prism.MakePrismWithName(
func(s PartialPerson) __option.Option[int] { return _fromNonZeroAge(s.Age) },
_fromNonZeroage := __option.FromNonZero[int]()
_prismage := __prism.MakePrismWithName(
func(s PartialPerson) __option.Option[int] { return _fromNonZeroage(s.age) },
func(v int) PartialPerson {
return PartialPerson{ Age: v }
return PartialPerson{ age: v }
},
"PartialPerson.Age",
"PartialPerson.age",
)
return PartialPersonPrisms {
Name: _prismName,
Age: _prismAge,
name: _prismname,
age: _prismage,
}
}

View File

@@ -38,13 +38,37 @@ type (
// a value of type A. It is an alias for reader.Reader[R, A].
Reader[R, A any] = reader.Reader[R, A]
// Prism represents an optic that focuses on a subset of values of type S that can be
// converted to type A. It provides bidirectional transformation with validation.
// It is an alias for prism.Prism[S, A].
Prism[S, A any] = prism.Prism[S, A]
Lens[S, A any] = lens.Lens[S, A]
Type[A, O, I any] = codec.Type[A, O, I]
// Lens represents an optic that focuses on a field of type A within a structure of type S.
// It provides getter and setter operations for immutable updates.
// It is an alias for lens.Lens[S, A].
Lens[S, A any] = lens.Lens[S, A]
// Type represents a codec that handles bidirectional transformation between types.
// A: The validated target type
// O: The output encoding type
// I: The input decoding type
// It is an alias for codec.Type[A, O, I].
Type[A, O, I any] = codec.Type[A, O, I]
// Validate represents a validation function that transforms input I into a validated result A.
// It returns a Validation that contains either the validated value or validation errors.
// It is an alias for validate.Validate[I, A].
Validate[I, A any] = validate.Validate[I, A]
Validation[A any] = validation.Validation[A]
Encode[A, O any] = codec.Encode[A, O]
// Validation represents the result of a validation operation.
// It contains either a validated value of type A (Right) or validation errors (Left).
// It is an alias for validation.Validation[A].
Validation[A any] = validation.Validation[A]
// Encode represents an encoding function that transforms a value of type A into type O.
// It is used in codecs for the reverse direction of validation.
// It is an alias for codec.Encode[A, O].
Encode[A, O any] = codec.Encode[A, O]
// NonEmptyString is a string type that represents a validated non-empty string.
// It is used to ensure that string fields contain meaningful data.
@@ -64,11 +88,11 @@ type (
//
// fp-go:Lens
type PartialPerson struct {
// Name is the person's name as a raw string, which may be empty or invalid.
Name string
// name is the person's name as a raw string, which may be empty or invalid.
name string
// Age is the person's age as a raw integer, which may be negative or otherwise invalid.
Age int
// age is the person's age as a raw integer, which may be negative or otherwise invalid.
age int
}
// Person represents a person record with validated fields.

View File

@@ -107,7 +107,7 @@ func TestHeterogeneousHttpRequests(t *testing.T) {
// BenchmarkHeterogeneousHttpRequests shows how to execute multiple HTTP requests in parallel when
// the response structure of these requests is different. We use [R.TraverseTuple2] to account for the different types
func BenchmarkHeterogeneousHttpRequests(b *testing.B) {
for n := 0; n < b.N; n++ {
heterogeneousHTTPRequests()(context.Background())()
for b.Loop() {
heterogeneousHTTPRequests()(b.Context())()
}
}

View File

@@ -64,3 +64,9 @@ type WithGeneric[T any] struct {
Name string
Value T
}
// fp-go:Lens
type DataBuilder struct {
name string
value string
}

View File

@@ -21,6 +21,7 @@ import (
F "github.com/IBM/fp-go/v2/function"
L "github.com/IBM/fp-go/v2/optics/lens"
O "github.com/IBM/fp-go/v2/option"
S "github.com/IBM/fp-go/v2/string"
"github.com/stretchr/testify/assert"
)
@@ -341,3 +342,125 @@ func TestCompanyRefLensesOptionalIdempotent(t *testing.T) {
assert.Equal(t, &newWebsiteValue, differentWebsite.Website)
assert.Equal(t, &websiteValue, company.Website, "Original should be unchanged")
}
func TestDataBuilderLensWithUnexportedFields(t *testing.T) {
// Test that lenses can access and modify unexported fields
// This demonstrates that the lens generator now supports unexported fields
// Create a DataBuilder with unexported fields
builder := DataBuilder{
name: "initial-name",
value: "initial-value",
}
// Create lenses
lenses := MakeDataBuilderLenses()
// Test Get on unexported fields
assert.Equal(t, "initial-name", lenses.name.Get(builder))
assert.Equal(t, "initial-value", lenses.value.Get(builder))
// Test Set on unexported fields
updatedName := lenses.name.Set("updated-name")(builder)
assert.Equal(t, "updated-name", updatedName.name)
assert.Equal(t, "initial-value", updatedName.value) // Other field unchanged
assert.Equal(t, "initial-name", builder.name) // Original unchanged
updatedValue := lenses.value.Set("updated-value")(builder)
assert.Equal(t, "initial-name", updatedValue.name) // Other field unchanged
assert.Equal(t, "updated-value", updatedValue.value)
assert.Equal(t, "initial-value", builder.value) // Original unchanged
// Test Modify on unexported fields
modifyName := F.Pipe1(
lenses.name,
L.Modify[DataBuilder](S.Append("-modified")),
)
modified := modifyName(builder)
assert.Equal(t, "initial-name-modified", modified.name)
assert.Equal(t, "initial-name", builder.name) // Original unchanged
// Test composition of modifications
updatedBoth := F.Pipe2(
builder,
lenses.name.Set("new-name"),
lenses.value.Set("new-value"),
)
assert.Equal(t, "new-name", updatedBoth.name)
assert.Equal(t, "new-value", updatedBoth.value)
assert.Equal(t, "initial-name", builder.name) // Original unchanged
assert.Equal(t, "initial-value", builder.value) // Original unchanged
}
func TestDataBuilderRefLensesWithUnexportedFields(t *testing.T) {
// Test that ref lenses work with unexported fields and maintain idempotency
builder := &DataBuilder{
name: "test-name",
value: "test-value",
}
refLenses := MakeDataBuilderRefLenses()
// Test Get on unexported fields
assert.Equal(t, "test-name", refLenses.name.Get(builder))
assert.Equal(t, "test-value", refLenses.value.Get(builder))
// Test idempotency - setting same value should return same pointer
sameName := refLenses.name.Set("test-name")(builder)
assert.Same(t, builder, sameName, "Setting name to same value should return identical pointer")
sameValue := refLenses.value.Set("test-value")(builder)
assert.Same(t, builder, sameValue, "Setting value to same value should return identical pointer")
// Test that setting different value creates new pointer
differentName := refLenses.name.Set("different-name")(builder)
assert.NotSame(t, builder, differentName, "Setting name to different value should return new pointer")
assert.Equal(t, "different-name", differentName.name)
assert.Equal(t, "test-name", builder.name, "Original should be unchanged")
differentValue := refLenses.value.Set("different-value")(builder)
assert.NotSame(t, builder, differentValue, "Setting value to different value should return new pointer")
assert.Equal(t, "different-value", differentValue.value)
assert.Equal(t, "test-value", builder.value, "Original should be unchanged")
}
func TestDataBuilderOptionalLensesWithUnexportedFields(t *testing.T) {
// Test optional lenses (LensO) with unexported fields
builder := DataBuilder{
name: "test",
value: "data",
}
lenses := MakeDataBuilderLenses()
// Test getting non-zero values as Some
nameOpt := lenses.nameO.Get(builder)
assert.True(t, O.IsSome(nameOpt))
assert.Equal(t, "test", O.GetOrElse(F.Zero[string])(nameOpt))
valueOpt := lenses.valueO.Get(builder)
assert.True(t, O.IsSome(valueOpt))
assert.Equal(t, "data", O.GetOrElse(F.Zero[string])(valueOpt))
// Test setting to Some
updatedName := lenses.nameO.Set(O.Some("new-test"))(builder)
assert.Equal(t, "new-test", updatedName.name)
// Test setting to None (zero value for string is "")
clearedName := lenses.nameO.Set(O.None[string]())(builder)
assert.Equal(t, "", clearedName.name)
// Test with zero value
emptyBuilder := DataBuilder{
name: "",
value: "",
}
emptyNameOpt := lenses.nameO.Get(emptyBuilder)
assert.True(t, O.IsNone(emptyNameOpt), "Empty string should be None")
emptyValueOpt := lenses.valueO.Get(emptyBuilder)
assert.True(t, O.IsNone(emptyValueOpt), "Empty string should be None")
}

View File

@@ -2,7 +2,7 @@ package lens
// Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at
// 2025-12-16 15:41:28.7198645 +0100 CET m=+0.003889201
// 2026-01-23 16:09:35.747264 +0100 CET m=+0.003865601
import (
__lens "github.com/IBM/fp-go/v2/optics/lens"
@@ -946,3 +946,108 @@ func MakeWithGenericPrisms[T any]() WithGenericPrisms[T] {
Value: _prismValue,
}
}
// DataBuilderLenses provides lenses for accessing fields of DataBuilder
type DataBuilderLenses struct {
// mandatory fields
name __lens.Lens[DataBuilder, string]
value __lens.Lens[DataBuilder, string]
// optional fields
nameO __lens_option.LensO[DataBuilder, string]
valueO __lens_option.LensO[DataBuilder, string]
}
// DataBuilderRefLenses provides lenses for accessing fields of DataBuilder via a reference to DataBuilder
type DataBuilderRefLenses struct {
// mandatory fields
name __lens.Lens[*DataBuilder, string]
value __lens.Lens[*DataBuilder, string]
// optional fields
nameO __lens_option.LensO[*DataBuilder, string]
valueO __lens_option.LensO[*DataBuilder, string]
// prisms
nameP __prism.Prism[*DataBuilder, string]
valueP __prism.Prism[*DataBuilder, string]
}
// DataBuilderPrisms provides prisms for accessing fields of DataBuilder
type DataBuilderPrisms struct {
name __prism.Prism[DataBuilder, string]
value __prism.Prism[DataBuilder, string]
}
// MakeDataBuilderLenses creates a new DataBuilderLenses with lenses for all fields
func MakeDataBuilderLenses() DataBuilderLenses {
// mandatory lenses
lensname := __lens.MakeLensWithName(
func(s DataBuilder) string { return s.name },
func(s DataBuilder, v string) DataBuilder { s.name = v; return s },
"DataBuilder.name",
)
lensvalue := __lens.MakeLensWithName(
func(s DataBuilder) string { return s.value },
func(s DataBuilder, v string) DataBuilder { s.value = v; return s },
"DataBuilder.value",
)
// optional lenses
lensnameO := __lens_option.FromIso[DataBuilder](__iso_option.FromZero[string]())(lensname)
lensvalueO := __lens_option.FromIso[DataBuilder](__iso_option.FromZero[string]())(lensvalue)
return DataBuilderLenses{
// mandatory lenses
name: lensname,
value: lensvalue,
// optional lenses
nameO: lensnameO,
valueO: lensvalueO,
}
}
// MakeDataBuilderRefLenses creates a new DataBuilderRefLenses with lenses for all fields
func MakeDataBuilderRefLenses() DataBuilderRefLenses {
// mandatory lenses
lensname := __lens.MakeLensStrictWithName(
func(s *DataBuilder) string { return s.name },
func(s *DataBuilder, v string) *DataBuilder { s.name = v; return s },
"(*DataBuilder).name",
)
lensvalue := __lens.MakeLensStrictWithName(
func(s *DataBuilder) string { return s.value },
func(s *DataBuilder, v string) *DataBuilder { s.value = v; return s },
"(*DataBuilder).value",
)
// optional lenses
lensnameO := __lens_option.FromIso[*DataBuilder](__iso_option.FromZero[string]())(lensname)
lensvalueO := __lens_option.FromIso[*DataBuilder](__iso_option.FromZero[string]())(lensvalue)
return DataBuilderRefLenses{
// mandatory lenses
name: lensname,
value: lensvalue,
// optional lenses
nameO: lensnameO,
valueO: lensvalueO,
}
}
// MakeDataBuilderPrisms creates a new DataBuilderPrisms with prisms for all fields
func MakeDataBuilderPrisms() DataBuilderPrisms {
_fromNonZeroname := __option.FromNonZero[string]()
_prismname := __prism.MakePrismWithName(
func(s DataBuilder) __option.Option[string] { return _fromNonZeroname(s.name) },
func(v string) DataBuilder {
return DataBuilder{ name: v }
},
"DataBuilder.name",
)
_fromNonZerovalue := __option.FromNonZero[string]()
_prismvalue := __prism.MakePrismWithName(
func(s DataBuilder) __option.Option[string] { return _fromNonZerovalue(s.value) },
func(v string) DataBuilder {
return DataBuilder{ value: v }
},
"DataBuilder.value",
)
return DataBuilderPrisms {
name: _prismname,
value: _prismvalue,
}
}