Skip to content

Commit ff2a429

Browse files
committed
fix: add some useful lenses
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
1 parent edd66d6 commit ff2a429

File tree

4 files changed

+2165
-0
lines changed

4 files changed

+2165
-0
lines changed

v2/optics/lenses/doc.go

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
// Copyright (c) 2023 - 2025 IBM Corp.
2+
// All rights reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
// Package lenses provides pre-built lens and prism implementations for common data structures.
17+
//
18+
// This package offers ready-to-use optics (lenses and prisms) for working with regex match
19+
// structures and URL components in a functional programming style. Lenses enable immutable
20+
// updates to nested data structures, while prisms provide safe optional access to fields.
21+
//
22+
// # Overview
23+
//
24+
// The package includes optics for:
25+
// - Match structures: For working with regex match results (indexed capture groups)
26+
// - NamedMatch structures: For working with regex matches with named capture groups
27+
// - url.Userinfo: For working with URL authentication information
28+
//
29+
// Each structure has three variants of optics:
30+
// - Value lenses: Work with value types (immutable updates)
31+
// - Reference lenses: Work with pointer types (mutable updates)
32+
// - Prisms: Provide optional access treating zero values as None
33+
//
34+
// # Lenses vs Prisms
35+
//
36+
// Lenses provide guaranteed access to a field within a structure:
37+
// - Get: Extract a field value
38+
// - Set: Update a field value (returns new structure for values, mutates for pointers)
39+
//
40+
// Prisms provide optional access to fields that may not be present:
41+
// - GetOption: Try to extract a field, returning Option[T]
42+
// - ReverseGet: Construct a structure from a field value
43+
//
44+
// # Match Structures
45+
//
46+
// Match represents a regex match with indexed capture groups:
47+
//
48+
// type Match struct {
49+
// Before string // Text before the match
50+
// Groups []string // Capture groups (index 0 is full match)
51+
// After string // Text after the match
52+
// }
53+
//
54+
// NamedMatch represents a regex match with named capture groups:
55+
//
56+
// type NamedMatch struct {
57+
// Before string // Text before the match
58+
// Groups map[string]string // Named capture groups
59+
// Full string // Full matched text
60+
// After string // Text after the match
61+
// }
62+
//
63+
// # Usage Examples
64+
//
65+
// Working with Match (value-based):
66+
//
67+
// lenses := MakeMatchLenses()
68+
// match := Match{
69+
// Before: "Hello ",
70+
// Groups: []string{"world", "world"},
71+
// After: "!",
72+
// }
73+
//
74+
// // Get a field
75+
// before := lenses.Before.Get(match) // "Hello "
76+
//
77+
// // Update a field (returns new Match)
78+
// updated := lenses.Before.Set(match, "Hi ")
79+
// // updated.Before is "Hi ", original match unchanged
80+
//
81+
// // Use optional lens (treats empty string as None)
82+
// emptyMatch := Match{Before: "", Groups: []string{}, After: ""}
83+
// beforeOpt := lenses.BeforeO.GetOption(emptyMatch) // None
84+
//
85+
// Working with Match (reference-based):
86+
//
87+
// lenses := MakeMatchRefLenses()
88+
// match := &Match{
89+
// Before: "Hello ",
90+
// Groups: []string{"world"},
91+
// After: "!",
92+
// }
93+
//
94+
// // Get a field
95+
// before := lenses.Before.Get(match) // "Hello "
96+
//
97+
// // Update a field (mutates the pointer)
98+
// lenses.Before.Set(match, "Hi ")
99+
// // match.Before is now "Hi "
100+
//
101+
// // Use prism for optional access
102+
// afterOpt := lenses.AfterP.GetOption(match) // Some("!")
103+
//
104+
// Working with NamedMatch:
105+
//
106+
// lenses := MakeNamedMatchLenses()
107+
// match := NamedMatch{
108+
// Before: "Email: ",
109+
// Groups: map[string]string{
110+
// "user": "john",
111+
// "domain": "example.com",
112+
// },
113+
// Full: "john@example.com",
114+
// After: "",
115+
// }
116+
//
117+
// // Get field values
118+
// full := lenses.Full.Get(match) // "john@example.com"
119+
// groups := lenses.Groups.Get(match) // map with user and domain
120+
//
121+
// // Update a field
122+
// updated := lenses.Before.Set(match, "Contact: ")
123+
//
124+
// // Use optional lens
125+
// afterOpt := lenses.AfterO.GetOption(match) // None (empty string)
126+
//
127+
// Working with url.Userinfo:
128+
//
129+
// lenses := MakeUserinfoRefLenses()
130+
// userinfo := url.UserPassword("john", "secret123")
131+
//
132+
// // Get username
133+
// username := lenses.Username.Get(userinfo) // "john"
134+
//
135+
// // Update password (returns new Userinfo)
136+
// updated := lenses.Password.Set(userinfo, "newpass")
137+
//
138+
// // Use optional lens for password
139+
// pwdOpt := lenses.PasswordO.GetOption(userinfo) // Some("secret123")
140+
//
141+
// // Handle userinfo without password
142+
// userOnly := url.User("alice")
143+
//
144+
// Working with url.URL:
145+
//
146+
// lenses := MakeURLLenses()
147+
// u := url.URL{
148+
// Scheme: "https",
149+
// Host: "example.com",
150+
// Path: "/api/v1/users",
151+
// }
152+
//
153+
// // Get field values
154+
// scheme := lenses.Scheme.Get(u) // "https"
155+
// host := lenses.Host.Get(u) // "example.com"
156+
//
157+
// // Update fields (returns new URL)
158+
// updated := lenses.Path.Set("/api/v2/users")(u)
159+
// // updated.Path is "/api/v2/users", original u unchanged
160+
//
161+
// // Use optional lens for query string
162+
// queryOpt := lenses.RawQueryO.Get(u) // None (no query string)
163+
//
164+
// // Set query string
165+
// withQuery := lenses.RawQuery.Set("page=1&limit=10")(u)
166+
//
167+
// Working with url.Error:
168+
//
169+
// lenses := MakeErrorLenses()
170+
// urlErr := url.Error{
171+
// Op: "Get",
172+
// URL: "https://example.com",
173+
// Err: errors.New("connection timeout"),
174+
// }
175+
//
176+
// // Get field values
177+
// op := lenses.Op.Get(urlErr) // "Get"
178+
// urlStr := lenses.URL.Get(urlErr) // "https://example.com"
179+
// err := lenses.Err.Get(urlErr) // error: "connection timeout"
180+
//
181+
// // Update fields (returns new Error)
182+
// updated := lenses.Op.Set("Post")(urlErr)
183+
// // updated.Op is "Post", original urlErr unchanged
184+
// pwdOpt = lenses.PasswordO.GetOption(userOnly) // None
185+
//
186+
// # Composing Optics
187+
//
188+
// Lenses and prisms can be composed to access nested structures:
189+
//
190+
// // Compose lenses to access nested fields
191+
// outerLens := MakeSomeLens()
192+
// innerLens := MakeSomeOtherLens()
193+
// composed := lens.Compose(outerLens, innerLens)
194+
//
195+
// // Compose prisms for optional nested access
196+
// outerPrism := MakeSomePrism()
197+
// innerPrism := MakeSomeOtherPrism()
198+
// composed := prism.Compose(outerPrism, innerPrism)
199+
//
200+
// # Optional Lenses
201+
//
202+
// Optional lenses (suffixed with 'O') treat zero values as None:
203+
// - Empty strings become None
204+
// - Zero values of other types become None
205+
// - Non-zero values become Some(value)
206+
//
207+
// This is useful for distinguishing between "field not set" and "field set to zero value":
208+
//
209+
// lenses := MakeMatchLenses()
210+
// match := Match{Before: "", Groups: []string{"test"}, After: "!"}
211+
//
212+
// // Regular lens returns empty string
213+
// before := lenses.Before.Get(match) // ""
214+
//
215+
// // Optional lens returns None
216+
// beforeOpt := lenses.BeforeO.GetOption(match) // None
217+
//
218+
// // Setting None clears the field
219+
// cleared := lenses.BeforeO.Set(match, option.None[string]())
220+
// // cleared.Before is ""
221+
//
222+
// // Setting Some updates the field
223+
// updated := lenses.BeforeO.Set(match, option.Some("prefix "))
224+
// // updated.Before is "prefix "
225+
//
226+
// # Code Generation
227+
//
228+
// This package uses code generation for creating lens implementations.
229+
// The generate directive at the top of this file triggers the lens generator:
230+
//
231+
// //go:generate go run ../../main.go lens --dir . --filename gen_lens.go
232+
//
233+
// To regenerate lenses after modifying structures, run:
234+
//
235+
// go generate ./optics/lenses
236+
//
237+
// # Performance Considerations
238+
//
239+
// Value-based lenses (MatchLenses, NamedMatchLenses):
240+
// - Create new structures on each Set operation
241+
// - Safe for concurrent use (immutable)
242+
// - Suitable for functional programming patterns
243+
//
244+
// Reference-based lenses (MatchRefLenses, NamedMatchRefLenses):
245+
// - Mutate existing structures
246+
// - More efficient for repeated updates
247+
// - Require careful handling in concurrent contexts
248+
//
249+
// # Related Packages
250+
//
251+
// - github.com/IBM/fp-go/v2/optics/lens: Core lens functionality
252+
// - github.com/IBM/fp-go/v2/optics/prism: Core prism functionality
253+
// - github.com/IBM/fp-go/v2/optics/iso: Isomorphisms for type conversions
254+
// - github.com/IBM/fp-go/v2/option: Option type for optional values
255+
//
256+
// # See Also
257+
//
258+
// For more information on functional optics:
259+
// - Lens laws: https://github.com/IBM/fp-go/blob/main/optics/lens/README.md
260+
// - Prism laws: https://github.com/IBM/fp-go/blob/main/optics/prism/README.md
261+
// - Optics tutorial: https://github.com/IBM/fp-go/blob/main/docs/optics.md
262+
package lenses
263+
264+
//go:generate go run ../../main.go lens --dir . --filename gen_lens.go

0 commit comments

Comments
 (0)