1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-12-19 23:42:05 +02:00

doc: improve docs

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
Dr. Carsten Leue
2025-12-17 10:11:58 +01:00
parent 4ebfcadabe
commit fceda15701
22 changed files with 3950 additions and 345 deletions

View File

@@ -368,5 +368,3 @@ func TestToNonEmptyArrayUseCases(t *testing.T) {
assert.Equal(t, "default", result2) assert.Equal(t, "default", result2)
}) })
} }
// Made with Bob

View File

@@ -76,6 +76,7 @@ type fieldInfo struct {
BaseType string // TypeName without leading * for pointer types BaseType string // TypeName without leading * for pointer types
IsOptional bool // true if field is a pointer or has json omitempty tag IsOptional bool // true if field is a pointer or has json omitempty tag
IsComparable bool // true if the type is comparable (can use ==) IsComparable bool // true if the type is comparable (can use ==)
IsEmbedded bool // true if this field comes from an embedded struct
} }
// templateData holds data for template rendering // templateData holds data for template rendering
@@ -110,6 +111,17 @@ type {{.Name}}RefLenses{{.TypeParams}} struct {
{{- if .IsComparable}} {{- if .IsComparable}}
{{.Name}}O __lens_option.LensO[*{{$.Name}}{{$.TypeParamNames}}, {{.TypeName}}] {{.Name}}O __lens_option.LensO[*{{$.Name}}{{$.TypeParamNames}}, {{.TypeName}}]
{{- end}} {{- end}}
{{- end}}
// prisms
{{- range .Fields}}
{{.Name}}P __prism.Prism[*{{$.Name}}{{$.TypeParamNames}}, {{.TypeName}}]
{{- end}}
}
// {{.Name}}Prisms provides prisms for accessing fields of {{.Name}}
type {{.Name}}Prisms{{.TypeParams}} struct {
{{- range .Fields}}
{{.Name}} __prism.Prism[{{$.Name}}{{$.TypeParamNames}}, {{.TypeName}}]
{{- end}} {{- end}}
} }
` `
@@ -182,6 +194,47 @@ func Make{{.Name}}RefLenses{{.TypeParams}}() {{.Name}}RefLenses{{.TypeParamNames
{{- end}} {{- end}}
} }
} }
// Make{{.Name}}Prisms creates a new {{.Name}}Prisms with prisms for all fields
func Make{{.Name}}Prisms{{.TypeParams}}() {{.Name}}Prisms{{.TypeParamNames}} {
{{- range .Fields}}
{{- if .IsComparable}}
_fromNonZero{{.Name}} := __option.FromNonZero[{{.TypeName}}]()
_prism{{.Name}} := __prism.MakePrismWithName(
func(s {{$.Name}}{{$.TypeParamNames}}) __option.Option[{{.TypeName}}] { return _fromNonZero{{.Name}}(s.{{.Name}}) },
func(v {{.TypeName}}) {{$.Name}}{{$.TypeParamNames}} {
{{- if .IsEmbedded}}
var result {{$.Name}}{{$.TypeParamNames}}
result.{{.Name}} = v
return result
{{- else}}
return {{$.Name}}{{$.TypeParamNames}}{ {{.Name}}: v }
{{- end}}
},
"{{$.Name}}{{$.TypeParamNames}}.{{.Name}}",
)
{{- else}}
_prism{{.Name}} := __prism.MakePrismWithName(
func(s {{$.Name}}{{$.TypeParamNames}}) __option.Option[{{.TypeName}}] { return __option.Some(s.{{.Name}}) },
func(v {{.TypeName}}) {{$.Name}}{{$.TypeParamNames}} {
{{- if .IsEmbedded}}
var result {{$.Name}}{{$.TypeParamNames}}
result.{{.Name}} = v
return result
{{- else}}
return {{$.Name}}{{$.TypeParamNames}}{ {{.Name}}: v }
{{- end}}
},
"{{$.Name}}{{$.TypeParamNames}}.{{.Name}}",
)
{{- end}}
{{- end}}
return {{.Name}}Prisms{{.TypeParamNames}} {
{{- range .Fields}}
{{.Name}}: _prism{{.Name}},
{{- end}}
}
}
` `
var ( var (
@@ -506,6 +559,7 @@ func extractEmbeddedFields(embedType ast.Expr, fileImports map[string]string, fi
BaseType: baseType, BaseType: baseType,
IsOptional: isOptional, IsOptional: isOptional,
IsComparable: isComparable, IsComparable: isComparable,
IsEmbedded: true,
}, },
fieldType: field.Type, fieldType: field.Type,
}) })
@@ -833,6 +887,8 @@ func generateLensFile(absDir, filename, packageName string, structs []structInfo
f.WriteString("import (\n") f.WriteString("import (\n")
// Standard fp-go imports always needed // Standard fp-go imports always needed
f.WriteString("\t__lens \"github.com/IBM/fp-go/v2/optics/lens\"\n") f.WriteString("\t__lens \"github.com/IBM/fp-go/v2/optics/lens\"\n")
f.WriteString("\t__option \"github.com/IBM/fp-go/v2/option\"\n")
f.WriteString("\t__prism \"github.com/IBM/fp-go/v2/optics/prism\"\n")
f.WriteString("\t__lens_option \"github.com/IBM/fp-go/v2/optics/lens/option\"\n") f.WriteString("\t__lens_option \"github.com/IBM/fp-go/v2/optics/lens/option\"\n")
f.WriteString("\t__iso_option \"github.com/IBM/fp-go/v2/optics/iso/option\"\n") f.WriteString("\t__iso_option \"github.com/IBM/fp-go/v2/optics/iso/option\"\n")

View File

@@ -24,7 +24,6 @@ import (
"github.com/IBM/fp-go/v2/io" "github.com/IBM/fp-go/v2/io"
"github.com/IBM/fp-go/v2/ioeither" "github.com/IBM/fp-go/v2/ioeither"
"github.com/IBM/fp-go/v2/ioresult" "github.com/IBM/fp-go/v2/ioresult"
L "github.com/IBM/fp-go/v2/optics/lens"
"github.com/IBM/fp-go/v2/reader" "github.com/IBM/fp-go/v2/reader"
RIOR "github.com/IBM/fp-go/v2/readerioresult" RIOR "github.com/IBM/fp-go/v2/readerioresult"
"github.com/IBM/fp-go/v2/result" "github.com/IBM/fp-go/v2/result"
@@ -128,6 +127,13 @@ func BindTo[S1, T any](
return RIOR.BindTo[context.Context](setter) return RIOR.BindTo[context.Context](setter)
} }
//go:inline
func BindToP[S1, T any](
setter Prism[S1, T],
) Operator[T, S1] {
return BindTo(setter.ReverseGet)
}
// ApS attaches a value to a context [S1] to produce a context [S2] by considering // ApS attaches a value to a context [S1] to produce a context [S2] by considering
// the context and the value concurrently (using Applicative rather than Monad). // the context and the value concurrently (using Applicative rather than Monad).
// This allows independent computations to be combined without one depending on the result of the other. // This allows independent computations to be combined without one depending on the result of the other.
@@ -214,7 +220,7 @@ func ApS[S1, S2, T any](
// //
//go:inline //go:inline
func ApSL[S, T any]( func ApSL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
fa ReaderIOResult[T], fa ReaderIOResult[T],
) Operator[S, S] { ) Operator[S, S] {
return ApS(lens.Set, fa) return ApS(lens.Set, fa)
@@ -253,7 +259,7 @@ func ApSL[S, T any](
// //
//go:inline //go:inline
func BindL[S, T any]( func BindL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
f Kleisli[T, T], f Kleisli[T, T],
) Operator[S, S] { ) Operator[S, S] {
return RIOR.BindL(lens, F.Flow2(f, WithContext)) return RIOR.BindL(lens, F.Flow2(f, WithContext))
@@ -289,7 +295,7 @@ func BindL[S, T any](
// //
//go:inline //go:inline
func LetL[S, T any]( func LetL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
f Endomorphism[T], f Endomorphism[T],
) Operator[S, S] { ) Operator[S, S] {
return RIOR.LetL[context.Context](lens, f) return RIOR.LetL[context.Context](lens, f)
@@ -322,7 +328,7 @@ func LetL[S, T any](
// //
//go:inline //go:inline
func LetToL[S, T any]( func LetToL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
b T, b T,
) Operator[S, S] { ) Operator[S, S] {
return RIOR.LetToL[context.Context](lens, b) return RIOR.LetToL[context.Context](lens, b)
@@ -443,7 +449,7 @@ func BindResultK[S1, S2, T any](
// //
//go:inline //go:inline
func BindIOEitherKL[S, T any]( func BindIOEitherKL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
f ioresult.Kleisli[T, T], f ioresult.Kleisli[T, T],
) Operator[S, S] { ) Operator[S, S] {
return BindL(lens, F.Flow2(f, FromIOEither[T])) return BindL(lens, F.Flow2(f, FromIOEither[T]))
@@ -458,7 +464,7 @@ func BindIOEitherKL[S, T any](
// //
//go:inline //go:inline
func BindIOResultKL[S, T any]( func BindIOResultKL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
f ioresult.Kleisli[T, T], f ioresult.Kleisli[T, T],
) Operator[S, S] { ) Operator[S, S] {
return BindL(lens, F.Flow2(f, FromIOEither[T])) return BindL(lens, F.Flow2(f, FromIOEither[T]))
@@ -474,7 +480,7 @@ func BindIOResultKL[S, T any](
// //
//go:inline //go:inline
func BindIOKL[S, T any]( func BindIOKL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
f io.Kleisli[T, T], f io.Kleisli[T, T],
) Operator[S, S] { ) Operator[S, S] {
return BindL(lens, F.Flow2(f, FromIO[T])) return BindL(lens, F.Flow2(f, FromIO[T]))
@@ -490,7 +496,7 @@ func BindIOKL[S, T any](
// //
//go:inline //go:inline
func BindReaderKL[S, T any]( func BindReaderKL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
f reader.Kleisli[context.Context, T, T], f reader.Kleisli[context.Context, T, T],
) Operator[S, S] { ) Operator[S, S] {
return BindL(lens, F.Flow2(f, FromReader[T])) return BindL(lens, F.Flow2(f, FromReader[T]))
@@ -506,7 +512,7 @@ func BindReaderKL[S, T any](
// //
//go:inline //go:inline
func BindReaderIOKL[S, T any]( func BindReaderIOKL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
f readerio.Kleisli[T, T], f readerio.Kleisli[T, T],
) Operator[S, S] { ) Operator[S, S] {
return BindL(lens, F.Flow2(f, FromReaderIO[T])) return BindL(lens, F.Flow2(f, FromReaderIO[T]))
@@ -627,7 +633,7 @@ func ApResultS[S1, S2, T any](
// //
//go:inline //go:inline
func ApIOEitherSL[S, T any]( func ApIOEitherSL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
fa IOResult[T], fa IOResult[T],
) Operator[S, S] { ) Operator[S, S] {
return F.Bind2nd(F.Flow2[ReaderIOResult[S], ioresult.Operator[S, S]], ioresult.ApSL(lens, fa)) return F.Bind2nd(F.Flow2[ReaderIOResult[S], ioresult.Operator[S, S]], ioresult.ApSL(lens, fa))
@@ -642,7 +648,7 @@ func ApIOEitherSL[S, T any](
// //
//go:inline //go:inline
func ApIOResultSL[S, T any]( func ApIOResultSL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
fa IOResult[T], fa IOResult[T],
) Operator[S, S] { ) Operator[S, S] {
return F.Bind2nd(F.Flow2[ReaderIOResult[S], ioresult.Operator[S, S]], ioresult.ApSL(lens, fa)) return F.Bind2nd(F.Flow2[ReaderIOResult[S], ioresult.Operator[S, S]], ioresult.ApSL(lens, fa))
@@ -657,7 +663,7 @@ func ApIOResultSL[S, T any](
// //
//go:inline //go:inline
func ApIOSL[S, T any]( func ApIOSL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
fa IO[T], fa IO[T],
) Operator[S, S] { ) Operator[S, S] {
return ApSL(lens, FromIO(fa)) return ApSL(lens, FromIO(fa))
@@ -672,7 +678,7 @@ func ApIOSL[S, T any](
// //
//go:inline //go:inline
func ApReaderSL[S, T any]( func ApReaderSL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
fa Reader[context.Context, T], fa Reader[context.Context, T],
) Operator[S, S] { ) Operator[S, S] {
return ApSL(lens, FromReader(fa)) return ApSL(lens, FromReader(fa))
@@ -687,7 +693,7 @@ func ApReaderSL[S, T any](
// //
//go:inline //go:inline
func ApReaderIOSL[S, T any]( func ApReaderIOSL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
fa ReaderIO[T], fa ReaderIO[T],
) Operator[S, S] { ) Operator[S, S] {
return ApSL(lens, FromReaderIO(fa)) return ApSL(lens, FromReaderIO(fa))
@@ -702,7 +708,7 @@ func ApReaderIOSL[S, T any](
// //
//go:inline //go:inline
func ApEitherSL[S, T any]( func ApEitherSL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
fa Result[T], fa Result[T],
) Operator[S, S] { ) Operator[S, S] {
return ApSL(lens, FromEither(fa)) return ApSL(lens, FromEither(fa))
@@ -717,7 +723,7 @@ func ApEitherSL[S, T any](
// //
//go:inline //go:inline
func ApResultSL[S, T any]( func ApResultSL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
fa Result[T], fa Result[T],
) Operator[S, S] { ) Operator[S, S] {
return ApSL(lens, FromResult(fa)) return ApSL(lens, FromResult(fa))

View File

@@ -26,6 +26,8 @@ import (
"github.com/IBM/fp-go/v2/io" "github.com/IBM/fp-go/v2/io"
"github.com/IBM/fp-go/v2/ioeither" "github.com/IBM/fp-go/v2/ioeither"
"github.com/IBM/fp-go/v2/lazy" "github.com/IBM/fp-go/v2/lazy"
"github.com/IBM/fp-go/v2/optics/lens"
"github.com/IBM/fp-go/v2/optics/prism"
"github.com/IBM/fp-go/v2/option" "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/reader" "github.com/IBM/fp-go/v2/reader"
"github.com/IBM/fp-go/v2/readereither" "github.com/IBM/fp-go/v2/readereither"
@@ -132,4 +134,7 @@ type (
Endomorphism[A any] = endomorphism.Endomorphism[A] Endomorphism[A any] = endomorphism.Endomorphism[A]
Consumer[A any] = consumer.Consumer[A] Consumer[A any] = consumer.Consumer[A]
Prism[S, T any] = prism.Prism[S, T]
Lens[S, T any] = lens.Lens[S, T]
) )

View File

@@ -17,7 +17,6 @@ package readerresult
import ( import (
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
L "github.com/IBM/fp-go/v2/optics/lens"
G "github.com/IBM/fp-go/v2/readereither/generic" G "github.com/IBM/fp-go/v2/readereither/generic"
) )
@@ -39,10 +38,18 @@ func Do[S any](
return G.Do[ReaderResult[S]](empty) return G.Do[ReaderResult[S]](empty)
} }
// Bind attaches the result of a computation to a context [S1] to produce a context [S2]. // Bind attaches the result of an EFFECTFUL computation to a context [S1] to produce a context [S2].
// This enables sequential composition where each step can depend on the results of previous steps // This enables sequential composition where each step can depend on the results of previous steps
// and access the context.Context from the environment. // and access the context.Context from the environment.
// //
// IMPORTANT: Bind is for EFFECTFUL FUNCTIONS that depend on context.Context.
// The function parameter takes state and returns a ReaderResult[T], which is effectful because
// it depends on context.Context (can be cancelled, has deadlines, carries values).
//
// For PURE FUNCTIONS (side-effect free), use:
// - BindResultK: For pure functions with errors (State -> (Value, error))
// - Let: For pure functions without errors (State -> Value)
//
// The setter function takes the result of the computation and returns a function that // The setter function takes the result of the computation and returns a function that
// updates the context from S1 to S2. // updates the context from S1 to S2.
// //
@@ -89,7 +96,16 @@ func Bind[S1, S2, T any](
return G.Bind[ReaderResult[S1], ReaderResult[S2]](setter, F.Flow2(f, WithContext)) return G.Bind[ReaderResult[S1], ReaderResult[S2]](setter, F.Flow2(f, WithContext))
} }
// Let attaches the result of a computation to a context [S1] to produce a context [S2] // Let attaches the result of a PURE computation to a context [S1] to produce a context [S2].
//
// IMPORTANT: Let is for PURE FUNCTIONS (side-effect free) that don't depend on context.Context.
// The function parameter takes state and returns a value directly, with no errors or effects.
//
// For EFFECTFUL FUNCTIONS (that need context.Context), use:
// - Bind: For effectful ReaderResult computations (State -> ReaderResult[Value])
//
// For PURE FUNCTIONS with error handling, use:
// - BindResultK: For pure functions with errors (State -> (Value, error))
// //
//go:inline //go:inline
func Let[S1, S2, T any]( func Let[S1, S2, T any](
@@ -99,7 +115,8 @@ func Let[S1, S2, T any](
return G.Let[ReaderResult[S1], ReaderResult[S2]](setter, f) return G.Let[ReaderResult[S1], ReaderResult[S2]](setter, f)
} }
// LetTo attaches the a value to a context [S1] to produce a context [S2] // LetTo attaches a constant value to a context [S1] to produce a context [S2].
// This is a PURE operation (side-effect free) that simply sets a field to a constant value.
// //
//go:inline //go:inline
func LetTo[S1, S2, T any]( func LetTo[S1, S2, T any](
@@ -114,13 +131,23 @@ func LetTo[S1, S2, T any](
//go:inline //go:inline
func BindTo[S1, T any]( func BindTo[S1, T any](
setter func(T) S1, setter func(T) S1,
) Kleisli[ReaderResult[T], S1] { ) Operator[T, S1] {
return G.BindTo[ReaderResult[S1], ReaderResult[T]](setter) return G.BindTo[ReaderResult[S1], ReaderResult[T]](setter)
} }
//go:inline
func BindToP[S1, T any](
setter Prism[S1, T],
) Operator[T, S1] {
return BindTo(setter.ReverseGet)
}
// ApS attaches a value to a context [S1] to produce a context [S2] by considering // ApS attaches a value to a context [S1] to produce a context [S2] by considering
// the context and the value concurrently (using Applicative rather than Monad). // the context and the value concurrently (using Applicative rather than Monad).
// This allows independent computations to be combined without one depending on the result of the other. // This allows independent EFFECTFUL computations to be combined without one depending on the result of the other.
//
// IMPORTANT: ApS is for EFFECTFUL FUNCTIONS that depend on context.Context.
// The ReaderResult parameter is effectful because it depends on context.Context.
// //
// Unlike Bind, which sequences operations, ApS can be used when operations are independent // Unlike Bind, which sequences operations, ApS can be used when operations are independent
// and can conceptually run in parallel. // and can conceptually run in parallel.
@@ -198,16 +225,21 @@ func ApS[S1, S2, T any](
// //
//go:inline //go:inline
func ApSL[S, T any]( func ApSL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
fa ReaderResult[T], fa ReaderResult[T],
) Kleisli[ReaderResult[S], S] { ) Kleisli[ReaderResult[S], S] {
return ApS(lens.Set, fa) return ApS(lens.Set, fa)
} }
// BindL is a variant of Bind that uses a lens to focus on a specific field in the state. // BindL is a variant of Bind that uses a lens to focus on a specific field in the state.
// It combines the lens-based field access with monadic composition, allowing you to: // It combines the lens-based field access with monadic composition for EFFECTFUL computations.
//
// IMPORTANT: BindL is for EFFECTFUL FUNCTIONS that depend on context.Context.
// The function parameter returns a ReaderResult, which is effectful.
//
// It allows you to:
// 1. Extract a field value using the lens // 1. Extract a field value using the lens
// 2. Use that value in a computation that may fail // 2. Use that value in an effectful computation that may fail
// 3. Update the field with the result // 3. Update the field with the result
// //
// Parameters: // Parameters:
@@ -244,14 +276,17 @@ func ApSL[S, T any](
// //
//go:inline //go:inline
func BindL[S, T any]( func BindL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
f Kleisli[T, T], f Kleisli[T, T],
) Kleisli[ReaderResult[S], S] { ) Kleisli[ReaderResult[S], S] {
return Bind(lens.Set, F.Flow2(lens.Get, F.Flow2(f, WithContext))) return Bind(lens.Set, F.Flow2(lens.Get, F.Flow2(f, WithContext)))
} }
// LetL is a variant of Let that uses a lens to focus on a specific field in the state. // LetL is a variant of Let that uses a lens to focus on a specific field in the state.
// It applies a pure transformation to the focused field without any effects. // It applies a PURE transformation to the focused field without any effects.
//
// IMPORTANT: LetL is for PURE FUNCTIONS (side-effect free) that don't depend on context.Context.
// The function parameter is a pure endomorphism (T -> T) with no errors or effects.
// //
// Parameters: // Parameters:
// - lens: A lens that focuses on a field of type T within state S // - lens: A lens that focuses on a field of type T within state S
@@ -281,14 +316,14 @@ func BindL[S, T any](
// //
//go:inline //go:inline
func LetL[S, T any]( func LetL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
f Endomorphism[T], f Endomorphism[T],
) Kleisli[ReaderResult[S], S] { ) Kleisli[ReaderResult[S], S] {
return Let(lens.Set, F.Flow2(lens.Get, f)) return Let(lens.Set, F.Flow2(lens.Get, f))
} }
// LetToL is a variant of LetTo that uses a lens to focus on a specific field in the state. // LetToL is a variant of LetTo that uses a lens to focus on a specific field in the state.
// It sets the focused field to a constant value. // It sets the focused field to a constant value. This is a PURE operation (side-effect free).
// //
// Parameters: // Parameters:
// - lens: A lens that focuses on a field of type T within state S // - lens: A lens that focuses on a field of type T within state S
@@ -317,7 +352,7 @@ func LetL[S, T any](
// //
//go:inline //go:inline
func LetToL[S, T any]( func LetToL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
b T, b T,
) Kleisli[ReaderResult[S], S] { ) Kleisli[ReaderResult[S], S] {
return LetTo(lens.Set, b) return LetTo(lens.Set, b)

View File

@@ -13,7 +13,31 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// package readerresult implements a specialization of the Reader monad assuming a golang context as the context of the monad and a standard golang error // Package readerresult implements a specialization of the Reader monad assuming a golang context as the context of the monad and a standard golang error.
//
// # Pure vs Effectful Functions
//
// This package distinguishes between pure (side-effect free) and effectful (side-effectful) functions:
//
// EFFECTFUL FUNCTIONS (depend on context.Context):
// - ReaderResult[A]: func(context.Context) (A, error) - Effectful computation that needs context
// - These functions are effectful because context.Context is effectful (can be cancelled, has deadlines, carries values)
// - Use for: operations that need cancellation, timeouts, context values, or any context-dependent behavior
// - Examples: database queries, HTTP requests, operations that respect cancellation
//
// PURE FUNCTIONS (side-effect free):
// - func(State) (Value, error) - Pure computation that only depends on state, not context
// - func(State) Value - Pure transformation without errors
// - These functions are pure because they only read from their input state and don't depend on external context
// - Use for: parsing, validation, calculations, data transformations that don't need context
// - Examples: JSON parsing, input validation, mathematical computations
//
// The package provides different bind operations for each:
// - Bind: For effectful ReaderResult computations (State -> ReaderResult[Value])
// - BindResultK: For pure functions with errors (State -> (Value, error))
// - Let: For pure functions without errors (State -> Value)
// - BindReaderK: For context-dependent pure functions (State -> Reader[Context, Value])
// - BindEitherK: For pure Result/Either values (State -> Result[Value])
package readerresult package readerresult
import ( import (
@@ -21,6 +45,8 @@ import (
"github.com/IBM/fp-go/v2/either" "github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/endomorphism" "github.com/IBM/fp-go/v2/endomorphism"
"github.com/IBM/fp-go/v2/optics/lens"
"github.com/IBM/fp-go/v2/optics/prism"
"github.com/IBM/fp-go/v2/option" "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/reader" "github.com/IBM/fp-go/v2/reader"
"github.com/IBM/fp-go/v2/readereither" "github.com/IBM/fp-go/v2/readereither"
@@ -38,4 +64,6 @@ type (
Kleisli[A, B any] = reader.Reader[A, ReaderResult[B]] Kleisli[A, B any] = reader.Reader[A, ReaderResult[B]]
Operator[A, B any] = Kleisli[ReaderResult[A], B] Operator[A, B any] = Kleisli[ReaderResult[A], B]
Endomorphism[A any] = endomorphism.Endomorphism[A] Endomorphism[A any] = endomorphism.Endomorphism[A]
Prism[S, T any] = prism.Prism[S, T]
Lens[S, T any] = lens.Lens[S, T]
) )

View File

@@ -0,0 +1,780 @@
# 🎯 ReaderResult: Context-Aware Functional Composition
## 📖 Overview
The `ReaderResult` monad is a specialized implementation of the Reader monad pattern for Go, designed specifically for functions that:
- Depend on `context.Context` (for cancellation, deadlines, or context values)
- May fail with an error
- Need to be composed in a functional, declarative style
```go
type ReaderResult[A any] func(context.Context) (A, error)
```
This is equivalent to the common Go pattern `func(ctx context.Context) (A, error)`, but wrapped in a way that enables powerful functional composition.
## 🔄 ReaderResult as an Effectful Operation
**Important:** `ReaderResult` represents an **effectful operation** because it depends on `context.Context`, which is inherently mutable and can change during execution.
### Why Context Makes ReaderResult Effectful
The `context.Context` type in Go is designed to be mutable in the following ways:
1. **Cancellation State**: A context can transition from active to cancelled at any time
2. **Deadline Changes**: Timeouts and deadlines can expire during execution
3. **Value Storage**: Context values can be added or modified through `context.WithValue`
4. **Parent-Child Relationships**: Derived contexts inherit and can override parent behavior
This mutability means that:
- **Same ReaderResult, Different Results**: Executing the same `ReaderResult` with different contexts (or the same context at different times) can produce different outcomes
- **Non-Deterministic Behavior**: Context cancellation or timeout can interrupt execution at any point
- **Side Effects**: The context carries runtime state that affects computation behavior
### Use Case Examples
#### 1. **HTTP Request Handling with Timeout**
```go
// The context carries a deadline that can expire during execution
func FetchUserProfile(userID int) ReaderResult[UserProfile] {
return func(ctx context.Context) (UserProfile, error) {
// Context deadline affects when this operation fails
req, _ := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("/users/%d", userID), nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return UserProfile{}, err // May fail due to context timeout
}
defer resp.Body.Close()
var profile UserProfile
json.NewDecoder(resp.Body).Decode(&profile)
return profile, nil
}
}
// Same function, different contexts = different behavior
ctx1, cancel1 := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel1()
profile1, _ := FetchUserProfile(123)(ctx1) // Has 5 seconds to complete
ctx2, cancel2 := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel2()
profile2, _ := FetchUserProfile(123)(ctx2) // Has only 100ms - likely to timeout
```
#### 2. **Database Transactions with Cancellation**
```go
// Context cancellation can abort the transaction at any point
func TransferFunds(from, to int, amount float64) ReaderResult[Transaction] {
return func(ctx context.Context) (Transaction, error) {
tx, err := db.BeginTx(ctx, nil) // Context controls transaction lifetime
if err != nil {
return Transaction{}, err
}
defer tx.Rollback()
// If context is cancelled here, the debit fails
if err := debitAccount(ctx, tx, from, amount); err != nil {
return Transaction{}, err
}
// Or cancellation could happen here, before credit
if err := creditAccount(ctx, tx, to, amount); err != nil {
return Transaction{}, err
}
if err := tx.Commit(); err != nil {
return Transaction{}, err
}
return Transaction{From: from, To: to, Amount: amount}, nil
}
}
// User cancels the request mid-transaction
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(50 * time.Millisecond)
cancel() // Cancellation affects the running operation
}()
result, err := TransferFunds(100, 200, 50.0)(ctx) // May be interrupted
```
#### 3. **Context Values for Request Tracing**
```go
// Context values affect logging and tracing behavior
func ProcessOrder(orderID string) ReaderResult[Order] {
return func(ctx context.Context) (Order, error) {
// Extract trace ID from context (mutable state)
traceID := ctx.Value("trace-id")
log.Printf("[%v] Processing order %s", traceID, orderID)
// The same function behaves differently based on context values
if ctx.Value("debug") == true {
log.Printf("[%v] Debug mode: detailed order processing", traceID)
}
return fetchOrder(ctx, orderID)
}
}
// Different contexts = different tracing behavior
ctx1 := context.WithValue(context.Background(), "trace-id", "req-001")
order1, _ := ProcessOrder("ORD-123")(ctx1) // Logs with trace-id: req-001
ctx2 := context.WithValue(context.Background(), "trace-id", "req-002")
ctx2 = context.WithValue(ctx2, "debug", true)
order2, _ := ProcessOrder("ORD-123")(ctx2) // Logs with trace-id: req-002 + debug info
```
#### 4. **Parallel Operations with Shared Cancellation**
```go
// Multiple operations share the same cancellable context
func FetchDashboardData(userID int) ReaderResult[Dashboard] {
return func(ctx context.Context) (Dashboard, error) {
// All these operations can be cancelled together
userCh := make(chan User)
postsCh := make(chan []Post)
statsCh := make(chan Stats)
errCh := make(chan error, 3)
go func() {
user, err := FetchUser(userID)(ctx) // Shares cancellation
if err != nil {
errCh <- err
return
}
userCh <- user
}()
go func() {
posts, err := FetchPosts(userID)(ctx) // Shares cancellation
if err != nil {
errCh <- err
return
}
postsCh <- posts
}()
go func() {
stats, err := FetchStats(userID)(ctx) // Shares cancellation
if err != nil {
errCh <- err
return
}
statsCh <- stats
}()
// If context is cancelled, all goroutines stop
select {
case err := <-errCh:
return Dashboard{}, err
case <-ctx.Done():
return Dashboard{}, ctx.Err() // Context cancellation is an effect
case user := <-userCh:
// ... collect results
}
}
}
```
#### 5. **Retry Logic with Context Awareness**
```go
// Context state affects retry behavior
func FetchWithRetry[A any](operation ReaderResult[A], maxRetries int) ReaderResult[A] {
return func(ctx context.Context) (A, error) {
var lastErr error
for i := 0; i < maxRetries; i++ {
// Check if context is cancelled before each retry
if ctx.Err() != nil {
return *new(A), ctx.Err() // Context state is mutable
}
result, err := operation(ctx)
if err == nil {
return result, nil
}
lastErr = err
// Wait before retry, but respect context cancellation
select {
case <-time.After(time.Second * time.Duration(i+1)):
continue
case <-ctx.Done():
return *new(A), ctx.Err() // Context can be cancelled during wait
}
}
return *new(A), fmt.Errorf("failed after %d retries: %w", maxRetries, lastErr)
}
}
```
### Key Takeaway
Because `ReaderResult` depends on the mutable `context.Context`, it represents **effectful computations** where:
- Execution behavior can change based on context state
- The same operation can produce different results with different contexts
- External factors (timeouts, cancellations, context values) influence outcomes
- Side effects are inherent due to context's runtime nature
This makes `ReaderResult` ideal for modeling real-world operations that interact with external systems, respect cancellation, and need to be composed in a functional style while acknowledging their effectful nature.
## 🤔 Why Use ReaderResult Instead of Traditional Go Methods?
### 1. ✨ **Simplified API Design**
**Traditional Go approach:**
```go
func GetUser(ctx context.Context, id int) (User, error)
func GetPosts(ctx context.Context, userID int) ([]Post, error)
func FormatUser(ctx context.Context, user User) (string, error)
```
Every function must explicitly accept and thread `context.Context` through the call chain, leading to repetitive boilerplate.
**ReaderResult approach:**
```go
func GetUser(id int) ReaderResult[User]
func GetPosts(userID int) ReaderResult[[]Post]
func FormatUser(user User) ReaderResult[string]
```
The context dependency is implicit in the return type. Functions are cleaner and focus on their core logic.
### 2. 🔗 **Composability Through Monadic Operations**
**Traditional Go approach:**
```go
func GetUserWithPosts(ctx context.Context, userID int) (UserWithPosts, error) {
user, err := GetUser(ctx, userID)
if err != nil {
return UserWithPosts{}, err
}
posts, err := GetPosts(ctx, user.ID)
if err != nil {
return UserWithPosts{}, err
}
formatted, err := FormatUser(ctx, user)
if err != nil {
return UserWithPosts{}, err
}
return UserWithPosts{
User: user,
Posts: posts,
Formatted: formatted,
}, nil
}
```
Manual error handling at every step, repetitive context threading, and imperative style.
**ReaderResult approach:**
```go
func GetUserWithPosts(userID int) ReaderResult[UserWithPosts] {
return F.Pipe3(
Do(UserWithPosts{}),
Bind(setUser, func(s UserWithPosts) ReaderResult[User] {
return GetUser(userID)
}),
Bind(setPosts, func(s UserWithPosts) ReaderResult[[]Post] {
return GetPosts(s.User.ID)
}),
Bind(setFormatted, func(s UserWithPosts) ReaderResult[string] {
return FormatUser(s.User)
}),
)
}
```
Declarative pipeline, automatic error propagation, and clear data flow.
### 3. 🎨 **Pure Composition - Side Effects Deferred**
**💡 Key Insight:** ReaderResult separates *building* computations from *executing* them.
```go
// Building the computation (pure, no side effects)
getUserPipeline := F.Pipe2(
GetUser(123),
Chain(func(user User) ReaderResult[[]Post] {
return GetPosts(user.ID)
}),
Map(len[[]Post]),
)
// Execution happens later, at the edge of your system
postCount, err := getUserPipeline(ctx)
```
**Benefits:**
- Computations can be built, tested, and reasoned about without executing side effects
- Easy to mock and test individual components
- Clear separation between business logic and execution
- Computations are reusable with different contexts
### 4. 🧪 **Improved Testability**
**Traditional approach:**
```go
func TestGetUserWithPosts(t *testing.T) {
// Need to mock database, HTTP clients, etc.
// Tests are tightly coupled to implementation
ctx := context.Background()
result, err := GetUserWithPosts(ctx, 123)
// ...
}
```
**ReaderResult approach:**
```go
func TestGetUserWithPosts(t *testing.T) {
// Test the composition logic without executing side effects
pipeline := GetUserWithPosts(123)
// Can test with a mock context that provides test data
testCtx := context.WithValue(context.Background(), "test", true)
result, err := pipeline(testCtx)
// Or test individual components in isolation
mockGetUser := func(id int) ReaderResult[User] {
return Of(User{ID: id, Name: "Test User"})
}
}
```
You can test the composition logic separately from the actual I/O operations.
### 5. 📝 **Better Error Context Accumulation**
ReaderResult makes it easy to add context to errors as they propagate:
```go
getUserWithContext := F.Pipe2(
GetUser(userID),
MapError(func(err error) error {
return fmt.Errorf("failed to get user %d: %w", userID, err)
}),
Chain(func(user User) ReaderResult[UserWithPosts] {
return F.Pipe1(
GetPosts(user.ID),
MapError(func(err error) error {
return fmt.Errorf("failed to get posts for user %s: %w", user.Name, err)
}),
Map(func(posts []Post) UserWithPosts {
return UserWithPosts{User: user, Posts: posts}
}),
)
}),
)
```
Errors automatically accumulate context as they bubble up through the composition.
### 6. ⚡ **Natural Parallel Execution**
With applicative functors, independent operations can be expressed naturally:
```go
// These operations don't depend on each other
getUserData := F.Pipe2(
Do(UserData{}),
ApS(setUser, GetUser(userID)), // Can run in parallel
ApS(setSettings, GetSettings(userID)), // Can run in parallel
ApS(setPreferences, GetPreferences(userID)), // Can run in parallel
)
```
The structure makes it clear which operations are independent, enabling potential optimization.
### 7. 🔄 **Retry and Recovery Patterns**
ReaderResult makes retry logic composable:
```go
func WithRetry[A any](maxAttempts int, operation ReaderResult[A]) ReaderResult[A] {
return func(ctx context.Context) (A, error) {
var lastErr error
for i := 0; i < maxAttempts; i++ {
result, err := operation(ctx)
if err == nil {
return result, nil
}
lastErr = err
time.Sleep(time.Second * time.Duration(i+1))
}
return *new(A), fmt.Errorf("failed after %d attempts: %w", maxAttempts, lastErr)
}
}
// Use it:
reliableGetUser := WithRetry(3, GetUser(userID))
```
### 8. 🎭 **Middleware/Aspect-Oriented Programming**
Cross-cutting concerns can be added as higher-order functions:
```go
// Logging middleware
func WithLogging[A any](name string, operation ReaderResult[A]) ReaderResult[A] {
return func(ctx context.Context) (A, error) {
log.Printf("Starting %s", name)
start := time.Now()
result, err := operation(ctx)
log.Printf("Finished %s in %v (error: %v)", name, time.Since(start), err)
return result, err
}
}
// Timeout middleware
func WithTimeout[A any](timeout time.Duration, operation ReaderResult[A]) ReaderResult[A] {
return func(ctx context.Context) (A, error) {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
return operation(ctx)
}
}
// Compose middleware:
robustGetUser := F.Pipe1(
GetUser(userID),
WithLogging("GetUser"),
WithTimeout(5 * time.Second),
WithRetry(3),
)
```
### 9. 🛡️ **Resource Management with Bracket**
Safe resource handling with guaranteed cleanup:
```go
readFile := Bracket(
// Acquire
func() ReaderResult[*os.File] {
return func(ctx context.Context) (*os.File, error) {
return os.Open("data.txt")
}
},
// Use
func(file *os.File) ReaderResult[string] {
return func(ctx context.Context) (string, error) {
data, err := io.ReadAll(file)
return string(data), err
}
},
// Release (always called)
func(file *os.File, content string, err error) ReaderResult[any] {
return func(ctx context.Context) (any, error) {
return nil, file.Close()
}
},
)
```
The bracket pattern ensures resources are always cleaned up, even on errors.
### 10. 🔒 **Type-Safe State Threading**
Do-notation provides type-safe accumulation of state:
```go
type UserProfile struct {
User User
Posts []Post
Comments []Comment
Stats Statistics
}
buildProfile := F.Pipe4(
Do(UserProfile{}),
Bind(setUser, func(s UserProfile) ReaderResult[User] {
return GetUser(userID)
}),
Bind(setPosts, func(s UserProfile) ReaderResult[[]Post] {
return GetPosts(s.User.ID) // Can access previous results
}),
Bind(setComments, func(s UserProfile) ReaderResult[[]Comment] {
return GetComments(s.User.ID)
}),
Bind(setStats, func(s UserProfile) ReaderResult[Statistics] {
return CalculateStats(s.Posts, s.Comments) // Can use multiple previous results
}),
)
```
The compiler ensures you can't access fields that haven't been set yet.
### 11. ⏱️ **Automatic Context Cancellation Checks**
ReaderResult automatically checks for context cancellation at composition boundaries through `WithContextK`, ensuring fail-fast behavior without manual checks:
**Traditional approach:**
```go
func ProcessData(ctx context.Context, data Data) (Result, error) {
// Manual cancellation check
if ctx.Err() != nil {
return Result{}, ctx.Err()
}
step1, err := Step1(ctx, data)
if err != nil {
return Result{}, err
}
// Manual cancellation check again
if ctx.Err() != nil {
return Result{}, ctx.Err()
}
step2, err := Step2(ctx, step1)
if err != nil {
return Result{}, err
}
// And again...
if ctx.Err() != nil {
return Result{}, ctx.Err()
}
return Step3(ctx, step2)
}
```
**ReaderResult approach:**
```go
func ProcessData(data Data) ReaderResult[Result] {
return F.Pipe3(
Step1(data),
Chain(Step2), // Automatic cancellation check before Step2
Chain(Step3), // Automatic cancellation check before Step3
)
}
```
**How it works:**
- All `Bind` operations use `WithContextK` internally
- `WithContextK` wraps each Kleisli arrow with a cancellation check
- Before executing each step, it checks `ctx.Err()` and fails fast if cancelled
- No manual cancellation checks needed in your business logic
- Ensures long-running pipelines respect context cancellation at every step
**Example with timeout:**
```go
pipeline := F.Pipe3(
FetchUser(userID), // Step 1
Chain(FetchPosts), // Cancellation checked before Step 2
Chain(EnrichWithMetadata), // Cancellation checked before Step 3
)
// Set a timeout
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
// If Step 1 takes too long, Steps 2 and 3 won't execute
result, err := pipeline(ctx)
```
This makes ReaderResult ideal for:
- Long-running pipelines that should respect timeouts
- Operations that need to be cancellable at any point
- Composing third-party functions that don't check context themselves
- Building responsive services that handle request cancellation properly
## 🎯 When to Use ReaderResult
**✅ Use ReaderResult when:**
- You have complex composition of context-dependent operations
- You want to separate business logic from execution
- You need better testability and mockability
- You want declarative, pipeline-style code
- You need to add cross-cutting concerns (logging, retry, timeout)
- You want type-safe state accumulation
- You need automatic context cancellation checks at composition boundaries
- You're building long-running pipelines that should respect timeouts
**❌ Stick with traditional Go when:**
- You have simple, one-off operations
- The team is unfamiliar with functional patterns
- You're writing library code that needs to be idiomatic Go
- Performance is absolutely critical (though the overhead is minimal)
## 🚀 Quick Start
```go
import (
"context"
F "github.com/IBM/fp-go/v2/function"
RR "github.com/IBM/fp-go/v2/idiomatic/context/readerresult"
)
// Define your operations
func GetUser(id int) RR.ReaderResult[User] {
return func(ctx context.Context) (User, error) {
// Your implementation
}
}
// Compose them
pipeline := F.Pipe2(
GetUser(123),
RR.Chain(func(user User) RR.ReaderResult[[]Post] {
return GetPosts(user.ID)
}),
RR.Map(CreateSummary),
)
// Execute at the edge
summary, err := pipeline(context.Background())
```
## 🔄 Converting Traditional Go Functions
ReaderResult provides convenient functions to convert traditional Go functions (that take `context.Context` as their first parameter) into functional ReaderResult operations. This makes it easy to integrate existing code into functional pipelines.
### Using `FromXXX` Functions (Uncurried)
The `FromXXX` functions convert traditional Go functions into ReaderResult-returning functions that take all parameters at once. This is the most straightforward conversion for direct use.
```go
// Traditional Go function
func getUser(ctx context.Context, id int) (User, error) {
// ... database query
return User{ID: id, Name: "Alice"}, nil
}
func updateUser(ctx context.Context, id int, name string) (User, error) {
// ... database update
return User{ID: id, Name: name}, nil
}
// Convert using From1 (1 parameter besides context)
getUserRR := RR.From1(getUser)
// Convert using From2 (2 parameters besides context)
updateUserRR := RR.From2(updateUser)
// Use in a pipeline
pipeline := F.Pipe2(
getUserRR(123), // Returns ReaderResult[User]
RR.Chain(func(user User) RR.ReaderResult[User] {
return updateUserRR(user.ID, "Bob") // All params at once
}),
)
result, err := pipeline(ctx)
```
**Available From functions:**
- `From0`: Converts `func(context.Context) (A, error)``func() ReaderResult[A]`
- `From1`: Converts `func(context.Context, T1) (A, error)``func(T1) ReaderResult[A]`
- `From2`: Converts `func(context.Context, T1, T2) (A, error)``func(T1, T2) ReaderResult[A]`
- `From3`: Converts `func(context.Context, T1, T2, T3) (A, error)``func(T1, T2, T3) ReaderResult[A]`
### Using `CurryXXX` Functions (Curried)
The `CurryXXX` functions convert traditional Go functions into curried ReaderResult-returning functions. This enables partial application, which is useful for building reusable function pipelines.
```go
// Traditional Go function
func createPost(ctx context.Context, userID int, title string, body string) (Post, error) {
return Post{UserID: userID, Title: title, Body: body}, nil
}
// Convert using Curry3 (3 parameters besides context)
createPostRR := RR.Curry3(createPost)
// Partial application - build specialized functions
createPostForUser42 := createPostRR(42)
createPostWithTitle := createPostForUser42("My Title")
// Complete the application
rr := createPostWithTitle("Post body content")
post, err := rr(ctx)
// Or apply all at once
post2, err := createPostRR(42)("Another Title")("Another body")(ctx)
```
**Available Curry functions:**
- `Curry0`: Converts `func(context.Context) (A, error)``ReaderResult[A]`
- `Curry1`: Converts `func(context.Context, T1) (A, error)``func(T1) ReaderResult[A]`
- `Curry2`: Converts `func(context.Context, T1, T2) (A, error)``func(T1) func(T2) ReaderResult[A]`
- `Curry3`: Converts `func(context.Context, T1, T2, T3) (A, error)``func(T1) func(T2) func(T3) ReaderResult[A]`
### Practical Example: Integrating Existing Code
```go
// Existing traditional Go functions (e.g., from a database package)
func fetchUser(ctx context.Context, id int) (User, error) { /* ... */ }
func fetchPosts(ctx context.Context, userID int) ([]Post, error) { /* ... */ }
func fetchComments(ctx context.Context, postID int) ([]Comment, error) { /* ... */ }
// Convert them all to ReaderResult
var (
GetUser = RR.From1(fetchUser)
GetPosts = RR.From1(fetchPosts)
GetComments = RR.From1(fetchComments)
)
// Now compose them functionally
func GetUserWithData(userID int) RR.ReaderResult[UserData] {
return F.Pipe3(
GetUser(userID),
RR.Chain(func(user User) RR.ReaderResult[[]Post] {
return GetPosts(user.ID)
}),
RR.Map(func(posts []Post) UserData {
return UserData{
User: user,
Posts: posts,
}
}),
)
}
// Execute
userData, err := GetUserWithData(123)(ctx)
```
### When to Use From vs Curry
**Use `FromXXX` when:**
- You want straightforward conversion for immediate use
- You're calling functions with all parameters at once
- You prefer a more familiar, uncurried style
- You're converting functions for one-time use in a pipeline
**Use `CurryXXX` when:**
- You want to partially apply parameters
- You're building reusable, specialized functions
- You need maximum composability
- You're working with higher-order functions that expect curried inputs
### Converting Back: Uncurry Functions
If you need to convert a ReaderResult function back to traditional Go style (e.g., for interfacing with non-functional code), use the `UncurryXXX` functions:
```go
// Functional style
getUserRR := func(id int) RR.ReaderResult[User] {
return func(ctx context.Context) (User, error) {
return User{ID: id}, nil
}
}
// Convert back to traditional Go
getUser := RR.Uncurry1(getUserRR)
// Now callable in traditional style
user, err := getUser(ctx, 123)
```
## 📚 See Also
- [bind.go](bind.go) - Do-notation and composition operators
- [bracket.go](bracket.go) - Resource management patterns
- [examples_bind_test.go](examples_bind_test.go) - Comprehensive examples

View File

@@ -22,7 +22,6 @@ import (
"github.com/IBM/fp-go/v2/idiomatic/result" "github.com/IBM/fp-go/v2/idiomatic/result"
AP "github.com/IBM/fp-go/v2/internal/apply" AP "github.com/IBM/fp-go/v2/internal/apply"
C "github.com/IBM/fp-go/v2/internal/chain" C "github.com/IBM/fp-go/v2/internal/chain"
L "github.com/IBM/fp-go/v2/optics/lens"
"github.com/IBM/fp-go/v2/reader" "github.com/IBM/fp-go/v2/reader"
RES "github.com/IBM/fp-go/v2/result" RES "github.com/IBM/fp-go/v2/result"
) )
@@ -49,7 +48,15 @@ func Do[S any](
return RR.Do[context.Context](empty) return RR.Do[context.Context](empty)
} }
// Bind sequences a ReaderResult computation and updates the state with its result. // Bind sequences an EFFECTFUL ReaderResult computation and updates the state with its result.
//
// IMPORTANT: Bind is for EFFECTFUL FUNCTIONS that depend on context.Context.
// The Kleisli parameter (State -> ReaderResult[T]) is effectful because ReaderResult
// depends on context.Context (can be cancelled, has deadlines, carries values).
//
// For PURE FUNCTIONS (side-effect free), use:
// - BindResultK: For pure functions with errors (State -> (Value, error))
// - Let: For pure functions without errors (State -> Value)
// //
// This is the core operation for do-notation, allowing you to chain computations // This is the core operation for do-notation, allowing you to chain computations
// where each step can depend on the accumulated state and update it with new values. // where each step can depend on the accumulated state and update it with new values.
@@ -61,7 +68,7 @@ func Do[S any](
// //
// Parameters: // Parameters:
// - setter: A function that takes the computation result and returns a state updater // - setter: A function that takes the computation result and returns a state updater
// - f: A Kleisli arrow that produces the next computation based on current state // - f: A Kleisli arrow that produces the next effectful computation based on current state
// //
// Returns: // Returns:
// - An Operator that transforms ReaderResult[S1] to ReaderResult[S2] // - An Operator that transforms ReaderResult[S1] to ReaderResult[S2]
@@ -79,7 +86,16 @@ func Bind[S1, S2, T any](
) )
} }
// Let attaches the result of a pure computation to a state. // Let attaches the result of a PURE computation to a state.
//
// IMPORTANT: Let is for PURE FUNCTIONS (side-effect free) that don't depend on context.Context.
// The function parameter (State -> Value) is pure - it only reads from state with no effects.
//
// For EFFECTFUL FUNCTIONS (that need context.Context), use:
// - Bind: For effectful ReaderResult computations (State -> ReaderResult[Value])
//
// For PURE FUNCTIONS with error handling, use:
// - BindResultK: For pure functions with errors (State -> (Value, error))
// //
// Unlike Bind, Let works with pure functions (not ReaderResult computations). // Unlike Bind, Let works with pure functions (not ReaderResult computations).
// This is useful for deriving values from the current state without performing // This is useful for deriving values from the current state without performing
@@ -106,6 +122,7 @@ func Let[S1, S2, T any](
} }
// LetTo attaches a constant value to a state. // LetTo attaches a constant value to a state.
// This is a PURE operation (side-effect free).
// //
// This is a simplified version of Let for when you want to add a constant // This is a simplified version of Let for when you want to add a constant
// value to the state without computing it. // value to the state without computing it.
@@ -152,6 +169,48 @@ func BindTo[S1, T any](
return RR.BindTo[context.Context](setter) return RR.BindTo[context.Context](setter)
} }
// BindToP initializes do-notation by binding a value to a state using a Prism.
//
// This is a variant of BindTo that uses a prism instead of a setter function.
// Prisms are useful for working with sum types and optional values.
//
// Type Parameters:
// - S1: The state type to create
// - T: The type of the initial value
//
// Parameters:
// - setter: A prism that can construct the state from a value
//
// Returns:
// - An Operator that transforms ReaderResult[T] to ReaderResult[S1]
//
//go:inline
func BindToP[S1, T any](
setter Prism[S1, T],
) Operator[T, S1] {
return BindTo(setter.ReverseGet)
}
// ApS attaches a value to a context using applicative style.
//
// IMPORTANT: ApS is for EFFECTFUL FUNCTIONS that depend on context.Context.
// The ReaderResult parameter is effectful because it depends on context.Context.
//
// Unlike Bind (which sequences operations), ApS can be used when operations are
// independent and can conceptually run in parallel.
//
// Type Parameters:
// - S1: The input state type
// - S2: The output state type
// - T: The type of value produced by the computation
//
// Parameters:
// - setter: A function that takes the computation result and returns a state updater
// - fa: An effectful ReaderResult computation
//
// Returns:
// - An Operator that transforms ReaderResult[S1] to ReaderResult[S2]
//
//go:inline //go:inline
func ApS[S1, S2, T any]( func ApS[S1, S2, T any](
setter func(T) func(S1) S2, setter func(T) func(S1) S2,
@@ -165,38 +224,119 @@ func ApS[S1, S2, T any](
) )
} }
// ApSL is a variant of ApS that uses a lens to focus on a specific field in the state.
//
// IMPORTANT: ApSL is for EFFECTFUL FUNCTIONS that depend on context.Context.
// The ReaderResult parameter is effectful because it depends on context.Context.
//
// Instead of providing a setter function, you provide a lens that knows how to get and set
// the field. This is more convenient when working with nested structures.
//
// Type Parameters:
// - S: The state type
// - T: The type of the field to update
//
// Parameters:
// - lens: A lens that focuses on a field of type T within state S
// - fa: An effectful ReaderResult computation that produces a value of type T
//
// Returns:
// - An Operator that transforms ReaderResult[S] to ReaderResult[S]
//
//go:inline //go:inline
func ApSL[S, T any]( func ApSL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
fa ReaderResult[T], fa ReaderResult[T],
) Operator[S, S] { ) Operator[S, S] {
return ApS(lens.Set, fa) return ApS(lens.Set, fa)
} }
// BindL is a variant of Bind that uses a lens to focus on a specific field in the state.
//
// IMPORTANT: BindL is for EFFECTFUL FUNCTIONS that depend on context.Context.
// The Kleisli parameter returns a ReaderResult, which is effectful.
//
// It combines lens-based field access with monadic composition, allowing you to:
// 1. Extract a field value using the lens
// 2. Use that value in an effectful computation that may fail
// 3. Update the field with the result
//
// Type Parameters:
// - S: The state type
// - T: The type of the field to update
//
// Parameters:
// - lens: A lens that focuses on a field of type T within state S
// - f: An effectful Kleisli arrow that transforms the field value
//
// Returns:
// - An Operator that transforms ReaderResult[S] to ReaderResult[S]
//
//go:inline //go:inline
func BindL[S, T any]( func BindL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
f Kleisli[T, T], f Kleisli[T, T],
) Operator[S, S] { ) Operator[S, S] {
return RR.BindL(lens, WithContextK(f)) return RR.BindL(lens, WithContextK(f))
} }
// LetL is a variant of Let that uses a lens to focus on a specific field in the state.
//
// IMPORTANT: LetL is for PURE FUNCTIONS (side-effect free) that don't depend on context.Context.
// The endomorphism parameter is a pure function (T -> T) with no errors or effects.
//
// It applies a pure transformation to the focused field without any effects.
//
// Type Parameters:
// - S: The state type
// - T: The type of the field to update
//
// Parameters:
// - lens: A lens that focuses on a field of type T within state S
// - f: A pure endomorphism that transforms the field value
//
// Returns:
// - An Operator that transforms ReaderResult[S] to ReaderResult[S]
//
//go:inline //go:inline
func LetL[S, T any]( func LetL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
f Endomorphism[T], f Endomorphism[T],
) Operator[S, S] { ) Operator[S, S] {
return RR.LetL[context.Context](lens, f) return RR.LetL[context.Context](lens, f)
} }
// LetToL is a variant of LetTo that uses a lens to focus on a specific field in the state.
//
// IMPORTANT: LetToL is for setting constant values. This is a PURE operation (side-effect free).
//
// It sets the focused field to a constant value.
//
// Type Parameters:
// - S: The state type
// - T: The type of the field to update
//
// Parameters:
// - lens: A lens that focuses on a field of type T within state S
// - b: The constant value to set
//
// Returns:
// - An Operator that transforms ReaderResult[S] to ReaderResult[S]
//
//go:inline //go:inline
func LetToL[S, T any]( func LetToL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
b T, b T,
) Operator[S, S] { ) Operator[S, S] {
return RR.LetToL[context.Context](lens, b) return RR.LetToL[context.Context](lens, b)
} }
// BindReaderK binds a Reader computation (context-dependent but error-free) into the do-notation chain.
//
// IMPORTANT: This is for functions that depend on context.Context but don't return errors.
// The Reader[Context, T] is effectful because it depends on context.Context.
// Use this when you need context values but the operation cannot fail.
//
//go:inline //go:inline
func BindReaderK[S1, S2, T any]( func BindReaderK[S1, S2, T any](
setter func(T) func(S1) S2, setter func(T) func(S1) S2,
@@ -205,6 +345,12 @@ func BindReaderK[S1, S2, T any](
return RR.BindReaderK(setter, f) return RR.BindReaderK(setter, f)
} }
// BindEitherK binds a Result (Either) computation into the do-notation chain.
//
// IMPORTANT: This is for PURE FUNCTIONS (side-effect free) that return Result[T].
// The function (State -> Result[T]) is pure - it only depends on state, not context.
// Use this for pure error-handling logic that doesn't need context.
//
//go:inline //go:inline
func BindEitherK[S1, S2, T any]( func BindEitherK[S1, S2, T any](
setter func(T) func(S1) S2, setter func(T) func(S1) S2,
@@ -213,6 +359,14 @@ func BindEitherK[S1, S2, T any](
return RR.BindEitherK[context.Context](setter, f) return RR.BindEitherK[context.Context](setter, f)
} }
// BindResultK binds an idiomatic Go function (returning value and error) into the do-notation chain.
//
// IMPORTANT: This is for PURE FUNCTIONS (side-effect free) that return (Value, error).
// The function (State -> (Value, error)) is pure - it only depends on state, not context.
// Use this for pure computations with error handling that don't need context.
//
// For EFFECTFUL FUNCTIONS (that need context.Context), use Bind instead.
//
//go:inline //go:inline
func BindResultK[S1, S2, T any]( func BindResultK[S1, S2, T any](
setter func(T) func(S1) S2, setter func(T) func(S1) S2,
@@ -221,6 +375,11 @@ func BindResultK[S1, S2, T any](
return RR.BindResultK[context.Context](setter, f) return RR.BindResultK[context.Context](setter, f)
} }
// BindToReader converts a Reader computation into a ReaderResult and binds it to create an initial state.
//
// IMPORTANT: Reader[Context, T] is EFFECTFUL because it depends on context.Context.
// Use this when you have a context-dependent computation that cannot fail.
//
//go:inline //go:inline
func BindToReader[ func BindToReader[
S1, T any]( S1, T any](
@@ -229,6 +388,11 @@ func BindToReader[
return RR.BindToReader[context.Context](setter) return RR.BindToReader[context.Context](setter)
} }
// BindToEither converts a Result (Either) into a ReaderResult and binds it to create an initial state.
//
// IMPORTANT: Result[T] is PURE (side-effect free) - it doesn't depend on context.
// Use this to lift pure error-handling values into the ReaderResult context.
//
//go:inline //go:inline
func BindToEither[ func BindToEither[
S1, T any]( S1, T any](
@@ -237,6 +401,11 @@ func BindToEither[
return RR.BindToEither[context.Context](setter) return RR.BindToEither[context.Context](setter)
} }
// BindToResult converts an idiomatic Go tuple (value, error) into a ReaderResult and binds it to create an initial state.
//
// IMPORTANT: The (Value, error) tuple is PURE (side-effect free) - it doesn't depend on context.
// Use this to lift pure Go error-handling results into the ReaderResult context.
//
//go:inline //go:inline
func BindToResult[ func BindToResult[
S1, T any]( S1, T any](
@@ -245,6 +414,11 @@ func BindToResult[
return RR.BindToResult[context.Context](setter) return RR.BindToResult[context.Context](setter)
} }
// ApReaderS applies a Reader computation in applicative style, combining it with the current state.
//
// IMPORTANT: Reader[Context, T] is EFFECTFUL because it depends on context.Context.
// Use this for context-dependent operations that cannot fail.
//
//go:inline //go:inline
func ApReaderS[ func ApReaderS[
S1, S2, T any]( S1, S2, T any](
@@ -254,6 +428,11 @@ func ApReaderS[
return RR.ApReaderS(setter, fa) return RR.ApReaderS(setter, fa)
} }
// ApResultS applies an idiomatic Go tuple (value, error) in applicative style.
//
// IMPORTANT: The (Value, error) tuple is PURE (side-effect free) - it doesn't depend on context.
// Use this for pure Go error-handling results.
//
//go:inline //go:inline
func ApResultS[ func ApResultS[
S1, S2, T any]( S1, S2, T any](
@@ -262,6 +441,11 @@ func ApResultS[
return RR.ApResultS[context.Context](setter) return RR.ApResultS[context.Context](setter)
} }
// ApEitherS applies a Result (Either) in applicative style, combining it with the current state.
//
// IMPORTANT: Result[T] is PURE (side-effect free) - it doesn't depend on context.
// Use this for pure error-handling values.
//
//go:inline //go:inline
func ApEitherS[ func ApEitherS[
S1, S2, T any]( S1, S2, T any](

View File

@@ -0,0 +1,627 @@
// 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 readerresult
import (
"context"
"errors"
"testing"
F "github.com/IBM/fp-go/v2/function"
N "github.com/IBM/fp-go/v2/number"
"github.com/IBM/fp-go/v2/reader"
RES "github.com/IBM/fp-go/v2/result"
"github.com/stretchr/testify/assert"
)
func TestDoInit(t *testing.T) {
initial := SimpleState{Value: 42}
result := Do(initial)
state, err := result(context.Background())
assert.NoError(t, err)
assert.Equal(t, initial, state)
}
func TestBind(t *testing.T) {
t.Run("successful bind", func(t *testing.T) {
// Effectful function that depends on context
fetchValue := func(s SimpleState) ReaderResult[int] {
return func(ctx context.Context) (int, error) {
return s.Value * 2, nil
}
}
result := F.Pipe1(
Do(SimpleState{Value: 21}),
Bind(
func(v int) func(SimpleState) SimpleState {
return func(s SimpleState) SimpleState {
s.Value = v
return s
}
},
fetchValue,
),
)
state, err := result(context.Background())
assert.NoError(t, err)
assert.Equal(t, 42, state.Value)
})
t.Run("bind with error", func(t *testing.T) {
fetchValue := func(s SimpleState) ReaderResult[int] {
return func(ctx context.Context) (int, error) {
return 0, errors.New("fetch failed")
}
}
result := F.Pipe1(
Do(SimpleState{Value: 21}),
Bind(
func(v int) func(SimpleState) SimpleState {
return func(s SimpleState) SimpleState {
s.Value = v
return s
}
},
fetchValue,
),
)
_, err := result(context.Background())
assert.Error(t, err)
assert.Equal(t, "fetch failed", err.Error())
})
}
func TestLet(t *testing.T) {
// Pure function that doesn't depend on context
double := func(s SimpleState) int {
return s.Value * 2
}
result := F.Pipe1(
Do(SimpleState{Value: 21}),
Let(
func(v int) func(SimpleState) SimpleState {
return func(s SimpleState) SimpleState {
s.Value = v
return s
}
},
double,
),
)
state, err := result(context.Background())
assert.NoError(t, err)
assert.Equal(t, 42, state.Value)
}
func TestLetTo(t *testing.T) {
result := F.Pipe1(
Do(SimpleState{}),
LetTo(
func(v int) func(SimpleState) SimpleState {
return func(s SimpleState) SimpleState {
s.Value = v
return s
}
},
100,
),
)
state, err := result(context.Background())
assert.NoError(t, err)
assert.Equal(t, 100, state.Value)
}
func TestBindToInit(t *testing.T) {
getValue := func(ctx context.Context) (int, error) {
return 42, nil
}
result := F.Pipe1(
getValue,
BindTo(func(v int) SimpleState {
return SimpleState{Value: v}
}),
)
state, err := result(context.Background())
assert.NoError(t, err)
assert.Equal(t, 42, state.Value)
}
func TestApS(t *testing.T) {
t.Run("successful ApS", func(t *testing.T) {
getValue := func(ctx context.Context) (int, error) {
return 100, nil
}
result := F.Pipe1(
Do(SimpleState{Value: 42}),
ApS(
func(v int) func(SimpleState) SimpleState {
return func(s SimpleState) SimpleState {
s.Value = v
return s
}
},
getValue,
),
)
state, err := result(context.Background())
assert.NoError(t, err)
assert.Equal(t, 100, state.Value)
})
t.Run("ApS with error", func(t *testing.T) {
getValue := func(ctx context.Context) (int, error) {
return 0, errors.New("failed")
}
result := F.Pipe1(
Do(SimpleState{Value: 42}),
ApS(
func(v int) func(SimpleState) SimpleState {
return func(s SimpleState) SimpleState {
s.Value = v
return s
}
},
getValue,
),
)
_, err := result(context.Background())
assert.Error(t, err)
})
}
func TestApSL(t *testing.T) {
lenses := MakeSimpleStateLenses()
getValue := func(ctx context.Context) (int, error) {
return 100, nil
}
result := F.Pipe1(
Do(SimpleState{Value: 42}),
ApSL(lenses.Value, getValue),
)
state, err := result(context.Background())
assert.NoError(t, err)
assert.Equal(t, 100, state.Value)
}
func TestBindL(t *testing.T) {
lenses := MakeSimpleStateLenses()
// Effectful function
increment := func(v int) ReaderResult[int] {
return func(ctx context.Context) (int, error) {
return v + 1, nil
}
}
result := F.Pipe1(
Do(SimpleState{Value: 41}),
BindL(lenses.Value, increment),
)
state, err := result(context.Background())
assert.NoError(t, err)
assert.Equal(t, 42, state.Value)
}
func TestLetL(t *testing.T) {
lenses := MakeSimpleStateLenses()
result := F.Pipe1(
Do(SimpleState{Value: 21}),
LetL(lenses.Value, N.Mul(2)),
)
state, err := result(context.Background())
assert.NoError(t, err)
assert.Equal(t, 42, state.Value)
}
func TestLetToL(t *testing.T) {
lenses := MakeSimpleStateLenses()
result := F.Pipe1(
Do(SimpleState{}),
LetToL(lenses.Value, 42),
)
state, err := result(context.Background())
assert.NoError(t, err)
assert.Equal(t, 42, state.Value)
}
func TestBindReaderK(t *testing.T) {
t.Run("successful BindReaderK", func(t *testing.T) {
// Context-dependent function that doesn't return error
getFromContext := func(s SimpleState) reader.Reader[context.Context, int] {
return func(ctx context.Context) int {
if val := ctx.Value("multiplier"); val != nil {
return s.Value * val.(int)
}
return s.Value
}
}
result := F.Pipe1(
Do(SimpleState{Value: 21}),
BindReaderK(
func(v int) func(SimpleState) SimpleState {
return func(s SimpleState) SimpleState {
s.Value = v
return s
}
},
getFromContext,
),
)
ctx := context.WithValue(context.Background(), "multiplier", 2)
state, err := result(ctx)
assert.NoError(t, err)
assert.Equal(t, 42, state.Value)
})
}
func TestBindEitherK(t *testing.T) {
t.Run("successful BindEitherK", func(t *testing.T) {
// Pure function returning Result
validate := func(s SimpleState) RES.Result[int] {
if s.Value > 0 {
return RES.Of(s.Value * 2)
}
return RES.Left[int](errors.New("value must be positive"))
}
result := F.Pipe1(
Do(SimpleState{Value: 21}),
BindEitherK(
func(v int) func(SimpleState) SimpleState {
return func(s SimpleState) SimpleState {
s.Value = v
return s
}
},
validate,
),
)
state, err := result(context.Background())
assert.NoError(t, err)
assert.Equal(t, 42, state.Value)
})
t.Run("BindEitherK with error", func(t *testing.T) {
validate := func(s SimpleState) RES.Result[int] {
return RES.Left[int](errors.New("validation failed"))
}
result := F.Pipe1(
Do(SimpleState{Value: 21}),
BindEitherK(
func(v int) func(SimpleState) SimpleState {
return func(s SimpleState) SimpleState {
s.Value = v
return s
}
},
validate,
),
)
_, err := result(context.Background())
assert.Error(t, err)
assert.Equal(t, "validation failed", err.Error())
})
}
func TestBindResultK(t *testing.T) {
t.Run("successful BindResultK", func(t *testing.T) {
// Pure function returning (value, error)
parse := func(s SimpleState) (int, error) {
return s.Value * 2, nil
}
result := F.Pipe1(
Do(SimpleState{Value: 21}),
BindResultK(
func(v int) func(SimpleState) SimpleState {
return func(s SimpleState) SimpleState {
s.Value = v
return s
}
},
parse,
),
)
state, err := result(context.Background())
assert.NoError(t, err)
assert.Equal(t, 42, state.Value)
})
t.Run("BindResultK with error", func(t *testing.T) {
parse := func(s SimpleState) (int, error) {
return 0, errors.New("parse failed")
}
result := F.Pipe1(
Do(SimpleState{Value: 21}),
BindResultK(
func(v int) func(SimpleState) SimpleState {
return func(s SimpleState) SimpleState {
s.Value = v
return s
}
},
parse,
),
)
_, err := result(context.Background())
assert.Error(t, err)
assert.Equal(t, "parse failed", err.Error())
})
}
func TestBindToReader(t *testing.T) {
getFromContext := func(ctx context.Context) int {
if val := ctx.Value("value"); val != nil {
return val.(int)
}
return 0
}
result := F.Pipe1(
getFromContext,
BindToReader(func(v int) SimpleState {
return SimpleState{Value: v}
}),
)
ctx := context.WithValue(context.Background(), "value", 42)
state, err := result(ctx)
assert.NoError(t, err)
assert.Equal(t, 42, state.Value)
}
func TestBindToEither(t *testing.T) {
t.Run("successful BindToEither", func(t *testing.T) {
resultValue := RES.Of(42)
result := F.Pipe1(
resultValue,
BindToEither(func(v int) SimpleState {
return SimpleState{Value: v}
}),
)
state, err := result(context.Background())
assert.NoError(t, err)
assert.Equal(t, 42, state.Value)
})
t.Run("BindToEither with error", func(t *testing.T) {
resultValue := RES.Left[int](errors.New("failed"))
result := F.Pipe1(
resultValue,
BindToEither(func(v int) SimpleState {
return SimpleState{Value: v}
}),
)
_, err := result(context.Background())
assert.Error(t, err)
})
}
func TestBindToResult(t *testing.T) {
t.Run("successful BindToResult", func(t *testing.T) {
value, err := 42, error(nil)
result := F.Pipe1(
BindToResult(func(v int) SimpleState {
return SimpleState{Value: v}
}),
func(f func(int, error) ReaderResult[SimpleState]) ReaderResult[SimpleState] {
return f(value, err)
},
)
state, resultErr := result(context.Background())
assert.NoError(t, resultErr)
assert.Equal(t, 42, state.Value)
})
t.Run("BindToResult with error", func(t *testing.T) {
value, err := 0, errors.New("failed")
result := F.Pipe1(
BindToResult(func(v int) SimpleState {
return SimpleState{Value: v}
}),
func(f func(int, error) ReaderResult[SimpleState]) ReaderResult[SimpleState] {
return f(value, err)
},
)
_, resultErr := result(context.Background())
assert.Error(t, resultErr)
})
}
func TestApReaderS(t *testing.T) {
getFromContext := func(ctx context.Context) int {
if val := ctx.Value("value"); val != nil {
return val.(int)
}
return 0
}
result := F.Pipe1(
Do(SimpleState{}),
ApReaderS(
func(v int) func(SimpleState) SimpleState {
return func(s SimpleState) SimpleState {
s.Value = v
return s
}
},
getFromContext,
),
)
ctx := context.WithValue(context.Background(), "value", 42)
state, err := result(ctx)
assert.NoError(t, err)
assert.Equal(t, 42, state.Value)
}
func TestApResultS(t *testing.T) {
t.Run("successful ApResultS", func(t *testing.T) {
value, err := 42, error(nil)
result := F.Pipe1(
Do(SimpleState{}),
func(rr ReaderResult[SimpleState]) ReaderResult[SimpleState] {
return F.Pipe1(
rr,
ApResultS(
func(v int) func(SimpleState) SimpleState {
return func(s SimpleState) SimpleState {
s.Value = v
return s
}
},
)(value, err),
)
},
)
state, resultErr := result(context.Background())
assert.NoError(t, resultErr)
assert.Equal(t, 42, state.Value)
})
t.Run("ApResultS with error", func(t *testing.T) {
value, err := 0, errors.New("failed")
result := F.Pipe1(
Do(SimpleState{}),
func(rr ReaderResult[SimpleState]) ReaderResult[SimpleState] {
return F.Pipe1(
rr,
ApResultS(
func(v int) func(SimpleState) SimpleState {
return func(s SimpleState) SimpleState {
s.Value = v
return s
}
},
)(value, err),
)
},
)
_, resultErr := result(context.Background())
assert.Error(t, resultErr)
})
}
func TestApEitherS(t *testing.T) {
t.Run("successful ApEitherS", func(t *testing.T) {
resultValue := RES.Of(42)
result := F.Pipe1(
Do(SimpleState{}),
ApEitherS(
func(v int) func(SimpleState) SimpleState {
return func(s SimpleState) SimpleState {
s.Value = v
return s
}
},
resultValue,
),
)
state, err := result(context.Background())
assert.NoError(t, err)
assert.Equal(t, 42, state.Value)
})
t.Run("ApEitherS with error", func(t *testing.T) {
resultValue := RES.Left[int](errors.New("failed"))
result := F.Pipe1(
Do(SimpleState{}),
ApEitherS(
func(v int) func(SimpleState) SimpleState {
return func(s SimpleState) SimpleState {
s.Value = v
return s
}
},
resultValue,
),
)
_, err := result(context.Background())
assert.Error(t, err)
})
}
func TestComplexPipeline(t *testing.T) {
lenses := MakeSimpleStateLenses()
// Complex pipeline combining multiple operations
result := F.Pipe3(
Do(SimpleState{}),
LetToL(lenses.Value, 10),
LetL(lenses.Value, N.Mul(2)),
BindResultK(
func(v int) func(SimpleState) SimpleState {
return func(s SimpleState) SimpleState {
s.Value = v
return s
}
},
func(s SimpleState) (int, error) {
return s.Value + 22, nil
},
),
)
state, err := result(context.Background())
assert.NoError(t, err)
assert.Equal(t, 42, state.Value)
}

View File

@@ -130,6 +130,8 @@ import (
// } // }
// }, // },
// ) // )
//
//go:inline
func Bracket[ func Bracket[
A, B, ANY any]( A, B, ANY any](
@@ -251,6 +253,8 @@ func Bracket[
// }), // }),
// readerresult.Map(formatResult), // readerresult.Map(formatResult),
// ) // )
//
//go:inline
func WithResource[B, A, ANY any]( func WithResource[B, A, ANY any](
onCreate Lazy[ReaderResult[A]], onCreate Lazy[ReaderResult[A]],
onRelease Kleisli[A, ANY], onRelease Kleisli[A, ANY],
@@ -261,9 +265,9 @@ func WithResource[B, A, ANY any](
// onClose is a helper function that creates a ReaderResult that closes an io.Closer. // onClose is a helper function that creates a ReaderResult that closes an io.Closer.
// This is used internally by WithCloser to provide automatic cleanup for resources // This is used internally by WithCloser to provide automatic cleanup for resources
// that implement the io.Closer interface. // that implement the io.Closer interface.
func onClose[A io.Closer](a A) ReaderResult[any] { func onClose[A io.Closer](a A) ReaderResult[struct{}] {
return func(_ context.Context) (any, error) { return func(_ context.Context) (struct{}, error) {
return nil, a.Close() return struct{}{}, a.Close()
} }
} }
@@ -398,6 +402,8 @@ func onClose[A io.Closer](a A) ReaderResult[any] {
// Note: WithCloser is a convenience wrapper around WithResource that automatically // Note: WithCloser is a convenience wrapper around WithResource that automatically
// provides the Close() cleanup function. For resources that don't implement io.Closer // provides the Close() cleanup function. For resources that don't implement io.Closer
// or require custom cleanup logic, use WithResource or Bracket instead. // or require custom cleanup logic, use WithResource or Bracket instead.
//
//go:inline
func WithCloser[B any, A io.Closer](onCreate Lazy[ReaderResult[A]]) Kleisli[Kleisli[A, B], B] { func WithCloser[B any, A io.Closer](onCreate Lazy[ReaderResult[A]]) Kleisli[Kleisli[A, B], B] {
return WithResource[B](onCreate, onClose[A]) return WithResource[B](onCreate, onClose[A])
} }

View File

@@ -0,0 +1,630 @@
// 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 readerresult
import (
"context"
"errors"
"io"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
// mockResource simulates a resource that needs cleanup
type mockResource struct {
id int
closed bool
closeMu sync.Mutex
closeErr error
}
func (m *mockResource) Close() error {
m.closeMu.Lock()
defer m.closeMu.Unlock()
m.closed = true
return m.closeErr
}
func (m *mockResource) IsClosed() bool {
m.closeMu.Lock()
defer m.closeMu.Unlock()
return m.closed
}
// mockCloser implements io.Closer for testing WithCloser
type mockCloser struct {
*mockResource
}
func TestBracketExtended(t *testing.T) {
t.Run("successful acquire, use, and release with real resource", func(t *testing.T) {
resource := &mockResource{id: 1}
released := false
result := Bracket(
// Acquire
func() ReaderResult[*mockResource] {
return func(ctx context.Context) (*mockResource, error) {
return resource, nil
}
},
// Use
func(r *mockResource) ReaderResult[int] {
return func(ctx context.Context) (int, error) {
return r.id * 2, nil
}
},
// Release
func(r *mockResource, result int, err error) ReaderResult[any] {
return func(ctx context.Context) (any, error) {
released = true
assert.Equal(t, 2, result)
assert.NoError(t, err)
return nil, r.Close()
}
},
)
value, err := result(context.Background())
assert.NoError(t, err)
assert.Equal(t, 2, value)
assert.True(t, released)
assert.True(t, resource.IsClosed())
})
t.Run("acquire fails - release not called", func(t *testing.T) {
acquireErr := errors.New("acquire failed")
released := false
result := Bracket(
// Acquire fails
func() ReaderResult[*mockResource] {
return func(ctx context.Context) (*mockResource, error) {
return nil, acquireErr
}
},
// Use (should not be called)
func(r *mockResource) ReaderResult[int] {
t.Fatal("use should not be called when acquire fails")
return func(ctx context.Context) (int, error) {
return 0, nil
}
},
// Release (should not be called)
func(r *mockResource, result int, err error) ReaderResult[any] {
released = true
return func(ctx context.Context) (any, error) {
return nil, nil
}
},
)
_, err := result(context.Background())
assert.Error(t, err)
assert.Equal(t, acquireErr, err)
assert.False(t, released)
})
t.Run("use fails - release still called", func(t *testing.T) {
resource := &mockResource{id: 1}
useErr := errors.New("use failed")
released := false
result := Bracket(
// Acquire
func() ReaderResult[*mockResource] {
return func(ctx context.Context) (*mockResource, error) {
return resource, nil
}
},
// Use fails
func(r *mockResource) ReaderResult[int] {
return func(ctx context.Context) (int, error) {
return 0, useErr
}
},
// Release (should still be called)
func(r *mockResource, result int, err error) ReaderResult[any] {
return func(ctx context.Context) (any, error) {
released = true
assert.Equal(t, 0, result)
assert.Equal(t, useErr, err)
return nil, r.Close()
}
},
)
_, err := result(context.Background())
assert.Error(t, err)
assert.Equal(t, useErr, err)
assert.True(t, released)
assert.True(t, resource.IsClosed())
})
t.Run("release fails - error propagated", func(t *testing.T) {
resource := &mockResource{id: 1, closeErr: errors.New("close failed")}
released := false
result := Bracket(
// Acquire
func() ReaderResult[*mockResource] {
return func(ctx context.Context) (*mockResource, error) {
return resource, nil
}
},
// Use succeeds
func(r *mockResource) ReaderResult[int] {
return func(ctx context.Context) (int, error) {
return r.id * 2, nil
}
},
// Release fails
func(r *mockResource, result int, err error) ReaderResult[any] {
return func(ctx context.Context) (any, error) {
released = true
return nil, r.Close()
}
},
)
_, err := result(context.Background())
assert.Error(t, err)
assert.Equal(t, "close failed", err.Error())
assert.True(t, released)
assert.True(t, resource.IsClosed())
})
t.Run("both use and release fail - use error takes precedence", func(t *testing.T) {
resource := &mockResource{id: 1, closeErr: errors.New("close failed")}
useErr := errors.New("use failed")
released := false
result := Bracket(
// Acquire
func() ReaderResult[*mockResource] {
return func(ctx context.Context) (*mockResource, error) {
return resource, nil
}
},
// Use fails
func(r *mockResource) ReaderResult[int] {
return func(ctx context.Context) (int, error) {
return 0, useErr
}
},
// Release also fails
func(r *mockResource, result int, err error) ReaderResult[any] {
return func(ctx context.Context) (any, error) {
released = true
assert.Equal(t, useErr, err)
return nil, r.Close()
}
},
)
_, err := result(context.Background())
assert.Error(t, err)
// The use error should be returned
assert.Equal(t, useErr, err)
assert.True(t, released)
assert.True(t, resource.IsClosed())
})
t.Run("context cancellation during use", func(t *testing.T) {
resource := &mockResource{id: 1}
released := false
result := Bracket(
// Acquire
func() ReaderResult[*mockResource] {
return func(ctx context.Context) (*mockResource, error) {
return resource, nil
}
},
// Use checks context
func(r *mockResource) ReaderResult[int] {
return func(ctx context.Context) (int, error) {
select {
case <-ctx.Done():
return 0, ctx.Err()
default:
return r.id * 2, nil
}
}
},
// Release
func(r *mockResource, result int, err error) ReaderResult[any] {
return func(ctx context.Context) (any, error) {
released = true
return nil, r.Close()
}
},
)
ctx, cancel := context.WithCancel(context.Background())
cancel() // Cancel immediately
_, err := result(ctx)
assert.Error(t, err)
assert.Equal(t, context.Canceled, err)
assert.True(t, released)
assert.True(t, resource.IsClosed())
})
}
func TestWithResource(t *testing.T) {
t.Run("reusable resource manager - successful operations", func(t *testing.T) {
resource := &mockResource{id: 42}
createCount := 0
releaseCount := 0
withResource := WithResource[int](
// onCreate
func() ReaderResult[*mockResource] {
return func(ctx context.Context) (*mockResource, error) {
createCount++
return resource, nil
}
},
// onRelease
func(r *mockResource) ReaderResult[any] {
return func(ctx context.Context) (any, error) {
releaseCount++
return nil, r.Close()
}
},
)
// First operation
operation1 := withResource(func(r *mockResource) ReaderResult[int] {
return func(ctx context.Context) (int, error) {
return r.id * 2, nil
}
})
result1, err1 := operation1(context.Background())
assert.NoError(t, err1)
assert.Equal(t, 84, result1)
assert.Equal(t, 1, createCount)
assert.Equal(t, 1, releaseCount)
// Reset for second operation
resource.closed = false
// Second operation with same resource manager
operation2 := withResource(func(r *mockResource) ReaderResult[int] {
return func(ctx context.Context) (int, error) {
return r.id + 10, nil
}
})
result2, err2 := operation2(context.Background())
assert.NoError(t, err2)
assert.Equal(t, 52, result2)
assert.Equal(t, 2, createCount)
assert.Equal(t, 2, releaseCount)
})
t.Run("resource manager with failing operation", func(t *testing.T) {
resource := &mockResource{id: 42}
releaseCount := 0
opErr := errors.New("operation failed")
withResource := WithResource[int](
func() ReaderResult[*mockResource] {
return func(ctx context.Context) (*mockResource, error) {
return resource, nil
}
},
func(r *mockResource) ReaderResult[any] {
return func(ctx context.Context) (any, error) {
releaseCount++
return nil, r.Close()
}
},
)
operation := withResource(func(r *mockResource) ReaderResult[int] {
return func(ctx context.Context) (int, error) {
return 0, opErr
}
})
_, err := operation(context.Background())
assert.Error(t, err)
assert.Equal(t, opErr, err)
assert.Equal(t, 1, releaseCount)
assert.True(t, resource.IsClosed())
})
t.Run("nested resource managers", func(t *testing.T) {
resource1 := &mockResource{id: 1}
resource2 := &mockResource{id: 2}
withResource1 := WithResource[int](
func() ReaderResult[*mockResource] {
return func(ctx context.Context) (*mockResource, error) {
return resource1, nil
}
},
func(r *mockResource) ReaderResult[any] {
return func(ctx context.Context) (any, error) {
return nil, r.Close()
}
},
)
withResource2 := WithResource[int](
func() ReaderResult[*mockResource] {
return func(ctx context.Context) (*mockResource, error) {
return resource2, nil
}
},
func(r *mockResource) ReaderResult[any] {
return func(ctx context.Context) (any, error) {
return nil, r.Close()
}
},
)
// Nest the resource managers
operation := withResource1(func(r1 *mockResource) ReaderResult[int] {
return withResource2(func(r2 *mockResource) ReaderResult[int] {
return func(ctx context.Context) (int, error) {
return r1.id + r2.id, nil
}
})
})
result, err := operation(context.Background())
assert.NoError(t, err)
assert.Equal(t, 3, result)
assert.True(t, resource1.IsClosed())
assert.True(t, resource2.IsClosed())
})
}
func TestWithCloser(t *testing.T) {
t.Run("successful operation with io.Closer", func(t *testing.T) {
resource := &mockCloser{mockResource: &mockResource{id: 100}}
withCloser := WithCloser[string](
func() ReaderResult[*mockCloser] {
return func(ctx context.Context) (*mockCloser, error) {
return resource, nil
}
},
)
operation := withCloser(func(r *mockCloser) ReaderResult[string] {
return func(ctx context.Context) (string, error) {
return "success", nil
}
})
result, err := operation(context.Background())
assert.NoError(t, err)
assert.Equal(t, "success", result)
assert.True(t, resource.IsClosed())
})
t.Run("operation fails but closer still called", func(t *testing.T) {
resource := &mockCloser{mockResource: &mockResource{id: 100}}
opErr := errors.New("operation failed")
withCloser := WithCloser[string](
func() ReaderResult[*mockCloser] {
return func(ctx context.Context) (*mockCloser, error) {
return resource, nil
}
},
)
operation := withCloser(func(r *mockCloser) ReaderResult[string] {
return func(ctx context.Context) (string, error) {
return "", opErr
}
})
_, err := operation(context.Background())
assert.Error(t, err)
assert.Equal(t, opErr, err)
assert.True(t, resource.IsClosed())
})
t.Run("closer fails", func(t *testing.T) {
closeErr := errors.New("close failed")
resource := &mockCloser{mockResource: &mockResource{id: 100, closeErr: closeErr}}
withCloser := WithCloser[string](
func() ReaderResult[*mockCloser] {
return func(ctx context.Context) (*mockCloser, error) {
return resource, nil
}
},
)
operation := withCloser(func(r *mockCloser) ReaderResult[string] {
return func(ctx context.Context) (string, error) {
return "success", nil
}
})
_, err := operation(context.Background())
assert.Error(t, err)
assert.Equal(t, closeErr, err)
assert.True(t, resource.IsClosed())
})
t.Run("with strings.Reader (real io.Closer)", func(t *testing.T) {
content := "Hello, World!"
withReader := WithCloser[string](
func() ReaderResult[io.ReadCloser] {
return func(ctx context.Context) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader(content)), nil
}
},
)
operation := withReader(func(r io.ReadCloser) ReaderResult[string] {
return func(ctx context.Context) (string, error) {
data, err := io.ReadAll(r)
return string(data), err
}
})
result, err := operation(context.Background())
assert.NoError(t, err)
assert.Equal(t, content, result)
})
t.Run("multiple operations with same closer", func(t *testing.T) {
createCount := 0
withCloser := WithCloser[int](
func() ReaderResult[*mockCloser] {
return func(ctx context.Context) (*mockCloser, error) {
createCount++
return &mockCloser{mockResource: &mockResource{id: createCount}}, nil
}
},
)
// First operation
op1 := withCloser(func(r *mockCloser) ReaderResult[int] {
return func(ctx context.Context) (int, error) {
return r.id * 10, nil
}
})
result1, err1 := op1(context.Background())
assert.NoError(t, err1)
assert.Equal(t, 10, result1)
// Second operation
op2 := withCloser(func(r *mockCloser) ReaderResult[int] {
return func(ctx context.Context) (int, error) {
return r.id * 20, nil
}
})
result2, err2 := op2(context.Background())
assert.NoError(t, err2)
assert.Equal(t, 40, result2)
assert.Equal(t, 2, createCount)
})
}
func TestOnClose(t *testing.T) {
t.Run("onClose helper function", func(t *testing.T) {
resource := &mockCloser{mockResource: &mockResource{id: 1}}
closeFunc := onClose(resource)
_, err := closeFunc(context.Background())
assert.NoError(t, err)
assert.True(t, resource.IsClosed())
})
t.Run("onClose with error", func(t *testing.T) {
closeErr := errors.New("close error")
resource := &mockCloser{mockResource: &mockResource{id: 1, closeErr: closeErr}}
closeFunc := onClose(resource)
_, err := closeFunc(context.Background())
assert.Error(t, err)
assert.Equal(t, closeErr, err)
assert.True(t, resource.IsClosed())
})
}
// Integration test combining multiple bracket patterns
func TestBracketIntegration(t *testing.T) {
t.Run("complex resource management scenario", func(t *testing.T) {
// Simulate a scenario with multiple resources
db := &mockResource{id: 1}
cache := &mockResource{id: 2}
logger := &mockResource{id: 3}
result := Bracket(
// Acquire DB
func() ReaderResult[*mockResource] {
return func(ctx context.Context) (*mockResource, error) {
return db, nil
}
},
// Use DB to get cache and logger
func(dbRes *mockResource) ReaderResult[int] {
return Bracket(
// Acquire cache
func() ReaderResult[*mockResource] {
return func(ctx context.Context) (*mockResource, error) {
return cache, nil
}
},
// Use cache to get logger
func(cacheRes *mockResource) ReaderResult[int] {
return Bracket(
// Acquire logger
func() ReaderResult[*mockResource] {
return func(ctx context.Context) (*mockResource, error) {
return logger, nil
}
},
// Use all resources
func(logRes *mockResource) ReaderResult[int] {
return func(ctx context.Context) (int, error) {
return dbRes.id + cacheRes.id + logRes.id, nil
}
},
// Release logger
func(logRes *mockResource, result int, err error) ReaderResult[any] {
return func(ctx context.Context) (any, error) {
return nil, logRes.Close()
}
},
)
},
// Release cache
func(cacheRes *mockResource, result int, err error) ReaderResult[any] {
return func(ctx context.Context) (any, error) {
return nil, cacheRes.Close()
}
},
)
},
// Release DB
func(dbRes *mockResource, result int, err error) ReaderResult[any] {
return func(ctx context.Context) (any, error) {
return nil, dbRes.Close()
}
},
)
value, err := result(context.Background())
assert.NoError(t, err)
assert.Equal(t, 6, value) // 1 + 2 + 3
assert.True(t, db.IsClosed())
assert.True(t, cache.IsClosed())
assert.True(t, logger.IsClosed())
})
}

View File

@@ -364,11 +364,12 @@ type UserState struct {
// This is typically used as the first operation after a computation to // This is typically used as the first operation after a computation to
// start building up a state structure. // start building up a state structure.
func ExampleBindTo() { func ExampleBindTo() {
userStatePrisms := MakeUserStatePrisms()
result := F.Pipe1( result := F.Pipe1(
getUser(42), getUser(42),
BindTo(func(u User) UserState { BindToP(userStatePrisms.User),
return UserState{User: u}
}),
) )
state, err := result(context.Background()) state, err := result(context.Background())
@@ -385,6 +386,8 @@ type ConfigState struct {
// but error-free) into a ReaderResult do-notation chain. // but error-free) into a ReaderResult do-notation chain.
func ExampleBindReaderK() { func ExampleBindReaderK() {
configStateLenses := MakeConfigStateLenses()
// A Reader that extracts a value from context // A Reader that extracts a value from context
getConfig := func(ctx context.Context) string { getConfig := func(ctx context.Context) string {
if val := ctx.Value("config"); val != nil { if val := ctx.Value("config"); val != nil {
@@ -395,14 +398,8 @@ func ExampleBindReaderK() {
result := F.Pipe1( result := F.Pipe1(
Do(ConfigState{}), Do(ConfigState{}),
BindReaderK( BindReaderK(configStateLenses.Config.Set,
func(cfg string) Endomorphism[ConfigState] { func(s ConfigState) Reader[context.Context, string] {
return func(s ConfigState) ConfigState {
s.Config = cfg
return s
}
},
func(s ConfigState) func(context.Context) string {
return getConfig return getConfig
}, },
), ),
@@ -423,6 +420,9 @@ type NumberState struct {
// a ReaderResult do-notation chain. This is useful for integrating pure // a ReaderResult do-notation chain. This is useful for integrating pure
// error-handling logic that doesn't need context. // error-handling logic that doesn't need context.
func ExampleBindEitherK() { func ExampleBindEitherK() {
numberStateLenses := MakeNumberStateLenses()
// A pure function that returns a Result // A pure function that returns a Result
parseNumber := func(s NumberState) RES.Result[int] { parseNumber := func(s NumberState) RES.Result[int] {
return RES.Of(42) return RES.Of(42)
@@ -431,12 +431,7 @@ func ExampleBindEitherK() {
result := F.Pipe1( result := F.Pipe1(
Do(NumberState{}), Do(NumberState{}),
BindEitherK( BindEitherK(
func(n int) Endomorphism[NumberState] { numberStateLenses.Number.Set,
return func(s NumberState) NumberState {
s.Number = n
return s
}
},
parseNumber, parseNumber,
), ),
) )
@@ -452,8 +447,102 @@ type DataState struct {
} }
// ExampleBindResultK demonstrates binding an idiomatic Go function (returning // ExampleBindResultK demonstrates binding an idiomatic Go function (returning
// value and error) into a ReaderResult do-notation chain. // value and error) into a ReaderResult do-notation chain. This is particularly
// useful for integrating existing Go code that follows the standard (value, error)
// return pattern into functional pipelines.
//
// Step-by-step breakdown:
//
// 1. dataStateLenses := MakeDataStateLenses() - Create lenses for accessing
// DataState fields. This provides functional accessors (getters and setters)
// for the Data field, enabling type-safe, immutable field updates.
//
// 2. fetchData := func(s DataState) (string, error) - Define an idiomatic Go
// function that takes the current state and returns a tuple of (value, error).
//
// IMPORTANT: This function represents a PURE READER COMPOSITION - it reads from
// the state and performs computations that don't require a context.Context.
// This is suitable for:
// - Pure computations that may fail (parsing, validation, calculations)
// - Operations that only depend on the state, not external context
// - Stateless transformations with error handling
// - Synchronous operations that don't need cancellation or timeouts
//
// For EFFECTFUL COMPOSITION (operations that need context), use the full
// ReaderResult type instead: func(context.Context) (Value, error)
// Use ReaderResult when you need:
// - Context cancellation or timeouts
// - Context values (request IDs, trace IDs, etc.)
// - Operations that depend on external context state
// - Async operations that should respect context lifecycle
//
// In this example, fetchData always succeeds with "fetched data", but in real
// code it might perform pure operations like:
// - Parsing or validating data from the state
// - Performing calculations that could fail
// - Calling pure functions from external libraries
// - Data transformations that don't require context
//
// 3. Do(DataState{}) - Initialize the do-notation chain with an empty DataState.
// This creates the initial ReaderResult that will accumulate data through
// subsequent operations.
// Initial state: {Data: ""}
//
// 4. BindResultK(dataStateLenses.Data.Set, fetchData) - Bind the idiomatic Go
// function into the ReaderResult chain.
//
// BindResultK takes two parameters:
//
// a) First parameter: dataStateLenses.Data.Set
// This is a setter function from the lens that will update the Data field
// with the result of the computation. The lens ensures immutable updates.
//
// b) Second parameter: fetchData
// This is the idiomatic Go function (State -> (Value, error)) that will be
// lifted into the ReaderResult context.
//
// The BindResultK operation flow:
// - Takes the current state: {Data: ""}
// - Calls fetchData with the state: fetchData(DataState{})
// - Gets the result tuple: ("fetched data", nil)
// - If error is not nil, short-circuits the chain and returns the error
// - If error is nil, uses the setter to update state.Data with "fetched data"
// - Returns the updated state: {Data: "fetched data"}
// After this step: {Data: "fetched data"}
//
// 5. result(context.Background()) - Execute the computation chain with a context.
// Even though fetchData doesn't use the context, the ReaderResult still needs
// one to maintain the uniform interface. This runs all operations in sequence
// and returns the final state and any error.
//
// Key concepts demonstrated:
// - Integration of idiomatic Go code: BindResultK bridges functional and imperative styles
// - Error propagation: Errors from the Go function automatically propagate through the chain
// - State transformation: The result updates the state using lens-based setters
// - Context independence: The function doesn't need context but still works in ReaderResult
//
// Comparison with other bind operations:
// - BindResultK: For idiomatic Go functions (State -> (Value, error))
// - Bind: For full ReaderResult computations (State -> ReaderResult[Value])
// - BindEitherK: For pure Result/Either values (State -> Result[Value])
// - BindReaderK: For context-dependent functions (State -> Reader[Context, Value])
//
// Use BindResultK when you need to:
// - Integrate existing Go code that returns (value, error)
// - Call functions that may fail but don't need context
// - Perform stateful computations with standard Go error handling
// - Bridge between functional pipelines and imperative Go code
// - Work with libraries that follow Go conventions
//
// Real-world example scenarios:
// - Parsing JSON from a state field: func(s State) (ParsedData, error)
// - Validating user input: func(s State) (ValidatedInput, error)
// - Performing calculations: func(s State) (Result, error)
// - Calling third-party libraries: func(s State) (APIResponse, error)
func ExampleBindResultK() { func ExampleBindResultK() {
dataStateLenses := MakeDataStateLenses()
// An idiomatic Go function returning (value, error) // An idiomatic Go function returning (value, error)
fetchData := func(s DataState) (string, error) { fetchData := func(s DataState) (string, error) {
return "fetched data", nil return "fetched data", nil
@@ -462,12 +551,7 @@ func ExampleBindResultK() {
result := F.Pipe1( result := F.Pipe1(
Do(DataState{}), Do(DataState{}),
BindResultK( BindResultK(
func(data string) Endomorphism[DataState] { dataStateLenses.Data.Set,
return func(s DataState) DataState {
s.Data = data
return s
}
},
fetchData, fetchData,
), ),
) )

View File

@@ -2,36 +2,49 @@ package readerresult
// 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-12-16 13:45:59.7460125 +0100 CET m=+0.011815501 // 2025-12-16 15:20:56.2461527 +0100 CET m=+0.014539301
import ( import (
__iso_option "github.com/IBM/fp-go/v2/optics/iso/option"
__lens "github.com/IBM/fp-go/v2/optics/lens" __lens "github.com/IBM/fp-go/v2/optics/lens"
__option "github.com/IBM/fp-go/v2/option"
__prism "github.com/IBM/fp-go/v2/optics/prism"
__lens_option "github.com/IBM/fp-go/v2/optics/lens/option" __lens_option "github.com/IBM/fp-go/v2/optics/lens/option"
__iso_option "github.com/IBM/fp-go/v2/optics/iso/option"
) )
// PostLenses provides lenses for accessing fields of Post // PostLenses provides lenses for accessing fields of Post
type PostLenses struct { type PostLenses struct {
// mandatory fields // mandatory fields
ID __lens.Lens[Post, int] ID __lens.Lens[Post, int]
UserID __lens.Lens[Post, int] UserID __lens.Lens[Post, int]
Title __lens.Lens[Post, string] Title __lens.Lens[Post, string]
// optional fields // optional fields
IDO __lens_option.LensO[Post, int] IDO __lens_option.LensO[Post, int]
UserIDO __lens_option.LensO[Post, int] UserIDO __lens_option.LensO[Post, int]
TitleO __lens_option.LensO[Post, string] TitleO __lens_option.LensO[Post, string]
} }
// PostRefLenses provides lenses for accessing fields of Post via a reference to Post // PostRefLenses provides lenses for accessing fields of Post via a reference to Post
type PostRefLenses struct { type PostRefLenses struct {
// mandatory fields // mandatory fields
ID __lens.Lens[*Post, int] ID __lens.Lens[*Post, int]
UserID __lens.Lens[*Post, int] UserID __lens.Lens[*Post, int]
Title __lens.Lens[*Post, string] Title __lens.Lens[*Post, string]
// optional fields // optional fields
IDO __lens_option.LensO[*Post, int] IDO __lens_option.LensO[*Post, int]
UserIDO __lens_option.LensO[*Post, int] UserIDO __lens_option.LensO[*Post, int]
TitleO __lens_option.LensO[*Post, string] TitleO __lens_option.LensO[*Post, string]
// prisms
IDP __prism.Prism[*Post, int]
UserIDP __prism.Prism[*Post, int]
TitleP __prism.Prism[*Post, string]
}
// PostPrisms provides prisms for accessing fields of Post
type PostPrisms struct {
ID __prism.Prism[Post, int]
UserID __prism.Prism[Post, int]
Title __prism.Prism[Post, string]
} }
// MakePostLenses creates a new PostLenses with lenses for all fields // MakePostLenses creates a new PostLenses with lenses for all fields
@@ -58,13 +71,13 @@ func MakePostLenses() PostLenses {
lensTitleO := __lens_option.FromIso[Post](__iso_option.FromZero[string]())(lensTitle) lensTitleO := __lens_option.FromIso[Post](__iso_option.FromZero[string]())(lensTitle)
return PostLenses{ return PostLenses{
// mandatory lenses // mandatory lenses
ID: lensID, ID: lensID,
UserID: lensUserID, UserID: lensUserID,
Title: lensTitle, Title: lensTitle,
// optional lenses // optional lenses
IDO: lensIDO, IDO: lensIDO,
UserIDO: lensUserIDO, UserIDO: lensUserIDO,
TitleO: lensTitleO, TitleO: lensTitleO,
} }
} }
@@ -92,40 +105,80 @@ func MakePostRefLenses() PostRefLenses {
lensTitleO := __lens_option.FromIso[*Post](__iso_option.FromZero[string]())(lensTitle) lensTitleO := __lens_option.FromIso[*Post](__iso_option.FromZero[string]())(lensTitle)
return PostRefLenses{ return PostRefLenses{
// mandatory lenses // mandatory lenses
ID: lensID, ID: lensID,
UserID: lensUserID, UserID: lensUserID,
Title: lensTitle, Title: lensTitle,
// optional lenses // optional lenses
IDO: lensIDO, IDO: lensIDO,
UserIDO: lensUserIDO, UserIDO: lensUserIDO,
TitleO: lensTitleO, TitleO: lensTitleO,
}
}
// MakePostPrisms creates a new PostPrisms with prisms for all fields
func MakePostPrisms() PostPrisms {
_fromNonZeroID := __option.FromNonZero[int]()
_prismID := __prism.MakePrismWithName(
func(s Post) __option.Option[int] { return _fromNonZeroID(s.ID) },
func(v int) Post { return Post{ ID: v } },
"Post.ID",
)
_fromNonZeroUserID := __option.FromNonZero[int]()
_prismUserID := __prism.MakePrismWithName(
func(s Post) __option.Option[int] { return _fromNonZeroUserID(s.UserID) },
func(v int) Post { return Post{ UserID: v } },
"Post.UserID",
)
_fromNonZeroTitle := __option.FromNonZero[string]()
_prismTitle := __prism.MakePrismWithName(
func(s Post) __option.Option[string] { return _fromNonZeroTitle(s.Title) },
func(v string) Post { return Post{ Title: v } },
"Post.Title",
)
return PostPrisms {
ID: _prismID,
UserID: _prismUserID,
Title: _prismTitle,
} }
} }
// StateLenses provides lenses for accessing fields of State // StateLenses provides lenses for accessing fields of State
type StateLenses struct { type StateLenses struct {
// mandatory fields // mandatory fields
User __lens.Lens[State, User] User __lens.Lens[State, User]
Posts __lens.Lens[State, []Post] Posts __lens.Lens[State, []Post]
FullName __lens.Lens[State, string] FullName __lens.Lens[State, string]
Status __lens.Lens[State, string] Status __lens.Lens[State, string]
// optional fields // optional fields
UserO __lens_option.LensO[State, User] UserO __lens_option.LensO[State, User]
FullNameO __lens_option.LensO[State, string] FullNameO __lens_option.LensO[State, string]
StatusO __lens_option.LensO[State, string] StatusO __lens_option.LensO[State, string]
} }
// StateRefLenses provides lenses for accessing fields of State via a reference to State // StateRefLenses provides lenses for accessing fields of State via a reference to State
type StateRefLenses struct { type StateRefLenses struct {
// mandatory fields // mandatory fields
User __lens.Lens[*State, User] User __lens.Lens[*State, User]
Posts __lens.Lens[*State, []Post] Posts __lens.Lens[*State, []Post]
FullName __lens.Lens[*State, string] FullName __lens.Lens[*State, string]
Status __lens.Lens[*State, string] Status __lens.Lens[*State, string]
// optional fields // optional fields
UserO __lens_option.LensO[*State, User] UserO __lens_option.LensO[*State, User]
FullNameO __lens_option.LensO[*State, string] FullNameO __lens_option.LensO[*State, string]
StatusO __lens_option.LensO[*State, string] StatusO __lens_option.LensO[*State, string]
// prisms
UserP __prism.Prism[*State, User]
PostsP __prism.Prism[*State, []Post]
FullNameP __prism.Prism[*State, string]
StatusP __prism.Prism[*State, string]
}
// StatePrisms provides prisms for accessing fields of State
type StatePrisms struct {
User __prism.Prism[State, User]
Posts __prism.Prism[State, []Post]
FullName __prism.Prism[State, string]
Status __prism.Prism[State, string]
} }
// MakeStateLenses creates a new StateLenses with lenses for all fields // MakeStateLenses creates a new StateLenses with lenses for all fields
@@ -157,14 +210,14 @@ func MakeStateLenses() StateLenses {
lensStatusO := __lens_option.FromIso[State](__iso_option.FromZero[string]())(lensStatus) lensStatusO := __lens_option.FromIso[State](__iso_option.FromZero[string]())(lensStatus)
return StateLenses{ return StateLenses{
// mandatory lenses // mandatory lenses
User: lensUser, User: lensUser,
Posts: lensPosts, Posts: lensPosts,
FullName: lensFullName, FullName: lensFullName,
Status: lensStatus, Status: lensStatus,
// optional lenses // optional lenses
UserO: lensUserO, UserO: lensUserO,
FullNameO: lensFullNameO, FullNameO: lensFullNameO,
StatusO: lensStatusO, StatusO: lensStatusO,
} }
} }
@@ -197,14 +250,47 @@ func MakeStateRefLenses() StateRefLenses {
lensStatusO := __lens_option.FromIso[*State](__iso_option.FromZero[string]())(lensStatus) lensStatusO := __lens_option.FromIso[*State](__iso_option.FromZero[string]())(lensStatus)
return StateRefLenses{ return StateRefLenses{
// mandatory lenses // mandatory lenses
User: lensUser, User: lensUser,
Posts: lensPosts, Posts: lensPosts,
FullName: lensFullName, FullName: lensFullName,
Status: lensStatus, Status: lensStatus,
// optional lenses // optional lenses
UserO: lensUserO, UserO: lensUserO,
FullNameO: lensFullNameO, FullNameO: lensFullNameO,
StatusO: lensStatusO, StatusO: lensStatusO,
}
}
// MakeStatePrisms creates a new StatePrisms with prisms for all fields
func MakeStatePrisms() StatePrisms {
_fromNonZeroUser := __option.FromNonZero[User]()
_prismUser := __prism.MakePrismWithName(
func(s State) __option.Option[User] { return _fromNonZeroUser(s.User) },
func(v User) State { return State{ User: v } },
"State.User",
)
_prismPosts := __prism.MakePrismWithName(
func(s State) __option.Option[[]Post] { return __option.Some(s.Posts) },
func(v []Post) State { return State{ Posts: v } },
"State.Posts",
)
_fromNonZeroFullName := __option.FromNonZero[string]()
_prismFullName := __prism.MakePrismWithName(
func(s State) __option.Option[string] { return _fromNonZeroFullName(s.FullName) },
func(v string) State { return State{ FullName: v } },
"State.FullName",
)
_fromNonZeroStatus := __option.FromNonZero[string]()
_prismStatus := __prism.MakePrismWithName(
func(s State) __option.Option[string] { return _fromNonZeroStatus(s.Status) },
func(v string) State { return State{ Status: v } },
"State.Status",
)
return StatePrisms {
User: _prismUser,
Posts: _prismPosts,
FullName: _prismFullName,
Status: _prismStatus,
} }
} }
@@ -222,6 +308,13 @@ type SimpleStateRefLenses struct {
Value __lens.Lens[*SimpleState, int] Value __lens.Lens[*SimpleState, int]
// optional fields // optional fields
ValueO __lens_option.LensO[*SimpleState, int] ValueO __lens_option.LensO[*SimpleState, int]
// prisms
ValueP __prism.Prism[*SimpleState, int]
}
// SimpleStatePrisms provides prisms for accessing fields of SimpleState
type SimpleStatePrisms struct {
Value __prism.Prism[SimpleState, int]
} }
// MakeSimpleStateLenses creates a new SimpleStateLenses with lenses for all fields // MakeSimpleStateLenses creates a new SimpleStateLenses with lenses for all fields
@@ -260,28 +353,52 @@ func MakeSimpleStateRefLenses() SimpleStateRefLenses {
} }
} }
// MakeSimpleStatePrisms creates a new SimpleStatePrisms with prisms for all fields
func MakeSimpleStatePrisms() SimpleStatePrisms {
_fromNonZeroValue := __option.FromNonZero[int]()
_prismValue := __prism.MakePrismWithName(
func(s SimpleState) __option.Option[int] { return _fromNonZeroValue(s.Value) },
func(v int) SimpleState { return SimpleState{ Value: v } },
"SimpleState.Value",
)
return SimpleStatePrisms {
Value: _prismValue,
}
}
// NameStateLenses provides lenses for accessing fields of NameState // NameStateLenses provides lenses for accessing fields of NameState
type NameStateLenses struct { type NameStateLenses struct {
// mandatory fields // mandatory fields
FirstName __lens.Lens[NameState, string] FirstName __lens.Lens[NameState, string]
LastName __lens.Lens[NameState, string] LastName __lens.Lens[NameState, string]
FullName __lens.Lens[NameState, string] FullName __lens.Lens[NameState, string]
// optional fields // optional fields
FirstNameO __lens_option.LensO[NameState, string] FirstNameO __lens_option.LensO[NameState, string]
LastNameO __lens_option.LensO[NameState, string] LastNameO __lens_option.LensO[NameState, string]
FullNameO __lens_option.LensO[NameState, string] FullNameO __lens_option.LensO[NameState, string]
} }
// NameStateRefLenses provides lenses for accessing fields of NameState via a reference to NameState // NameStateRefLenses provides lenses for accessing fields of NameState via a reference to NameState
type NameStateRefLenses struct { type NameStateRefLenses struct {
// mandatory fields // mandatory fields
FirstName __lens.Lens[*NameState, string] FirstName __lens.Lens[*NameState, string]
LastName __lens.Lens[*NameState, string] LastName __lens.Lens[*NameState, string]
FullName __lens.Lens[*NameState, string] FullName __lens.Lens[*NameState, string]
// optional fields // optional fields
FirstNameO __lens_option.LensO[*NameState, string] FirstNameO __lens_option.LensO[*NameState, string]
LastNameO __lens_option.LensO[*NameState, string] LastNameO __lens_option.LensO[*NameState, string]
FullNameO __lens_option.LensO[*NameState, string] FullNameO __lens_option.LensO[*NameState, string]
// prisms
FirstNameP __prism.Prism[*NameState, string]
LastNameP __prism.Prism[*NameState, string]
FullNameP __prism.Prism[*NameState, string]
}
// NameStatePrisms provides prisms for accessing fields of NameState
type NameStatePrisms struct {
FirstName __prism.Prism[NameState, string]
LastName __prism.Prism[NameState, string]
FullName __prism.Prism[NameState, string]
} }
// MakeNameStateLenses creates a new NameStateLenses with lenses for all fields // MakeNameStateLenses creates a new NameStateLenses with lenses for all fields
@@ -309,12 +426,12 @@ func MakeNameStateLenses() NameStateLenses {
return NameStateLenses{ return NameStateLenses{
// mandatory lenses // mandatory lenses
FirstName: lensFirstName, FirstName: lensFirstName,
LastName: lensLastName, LastName: lensLastName,
FullName: lensFullName, FullName: lensFullName,
// optional lenses // optional lenses
FirstNameO: lensFirstNameO, FirstNameO: lensFirstNameO,
LastNameO: lensLastNameO, LastNameO: lensLastNameO,
FullNameO: lensFullNameO, FullNameO: lensFullNameO,
} }
} }
@@ -343,12 +460,39 @@ func MakeNameStateRefLenses() NameStateRefLenses {
return NameStateRefLenses{ return NameStateRefLenses{
// mandatory lenses // mandatory lenses
FirstName: lensFirstName, FirstName: lensFirstName,
LastName: lensLastName, LastName: lensLastName,
FullName: lensFullName, FullName: lensFullName,
// optional lenses // optional lenses
FirstNameO: lensFirstNameO, FirstNameO: lensFirstNameO,
LastNameO: lensLastNameO, LastNameO: lensLastNameO,
FullNameO: lensFullNameO, FullNameO: lensFullNameO,
}
}
// MakeNameStatePrisms creates a new NameStatePrisms with prisms for all fields
func MakeNameStatePrisms() NameStatePrisms {
_fromNonZeroFirstName := __option.FromNonZero[string]()
_prismFirstName := __prism.MakePrismWithName(
func(s NameState) __option.Option[string] { return _fromNonZeroFirstName(s.FirstName) },
func(v string) NameState { return NameState{ FirstName: v } },
"NameState.FirstName",
)
_fromNonZeroLastName := __option.FromNonZero[string]()
_prismLastName := __prism.MakePrismWithName(
func(s NameState) __option.Option[string] { return _fromNonZeroLastName(s.LastName) },
func(v string) NameState { return NameState{ LastName: v } },
"NameState.LastName",
)
_fromNonZeroFullName := __option.FromNonZero[string]()
_prismFullName := __prism.MakePrismWithName(
func(s NameState) __option.Option[string] { return _fromNonZeroFullName(s.FullName) },
func(v string) NameState { return NameState{ FullName: v } },
"NameState.FullName",
)
return NameStatePrisms {
FirstName: _prismFirstName,
LastName: _prismLastName,
FullName: _prismFullName,
} }
} }
@@ -366,6 +510,13 @@ type StatusStateRefLenses struct {
Status __lens.Lens[*StatusState, string] Status __lens.Lens[*StatusState, string]
// optional fields // optional fields
StatusO __lens_option.LensO[*StatusState, string] StatusO __lens_option.LensO[*StatusState, string]
// prisms
StatusP __prism.Prism[*StatusState, string]
}
// StatusStatePrisms provides prisms for accessing fields of StatusState
type StatusStatePrisms struct {
Status __prism.Prism[StatusState, string]
} }
// MakeStatusStateLenses creates a new StatusStateLenses with lenses for all fields // MakeStatusStateLenses creates a new StatusStateLenses with lenses for all fields
@@ -404,6 +555,19 @@ func MakeStatusStateRefLenses() StatusStateRefLenses {
} }
} }
// MakeStatusStatePrisms creates a new StatusStatePrisms with prisms for all fields
func MakeStatusStatePrisms() StatusStatePrisms {
_fromNonZeroStatus := __option.FromNonZero[string]()
_prismStatus := __prism.MakePrismWithName(
func(s StatusState) __option.Option[string] { return _fromNonZeroStatus(s.Status) },
func(v string) StatusState { return StatusState{ Status: v } },
"StatusState.Status",
)
return StatusStatePrisms {
Status: _prismStatus,
}
}
// UserStateLenses provides lenses for accessing fields of UserState // UserStateLenses provides lenses for accessing fields of UserState
type UserStateLenses struct { type UserStateLenses struct {
// mandatory fields // mandatory fields
@@ -418,6 +582,13 @@ type UserStateRefLenses struct {
User __lens.Lens[*UserState, User] User __lens.Lens[*UserState, User]
// optional fields // optional fields
UserO __lens_option.LensO[*UserState, User] UserO __lens_option.LensO[*UserState, User]
// prisms
UserP __prism.Prism[*UserState, User]
}
// UserStatePrisms provides prisms for accessing fields of UserState
type UserStatePrisms struct {
User __prism.Prism[UserState, User]
} }
// MakeUserStateLenses creates a new UserStateLenses with lenses for all fields // MakeUserStateLenses creates a new UserStateLenses with lenses for all fields
@@ -456,6 +627,19 @@ func MakeUserStateRefLenses() UserStateRefLenses {
} }
} }
// MakeUserStatePrisms creates a new UserStatePrisms with prisms for all fields
func MakeUserStatePrisms() UserStatePrisms {
_fromNonZeroUser := __option.FromNonZero[User]()
_prismUser := __prism.MakePrismWithName(
func(s UserState) __option.Option[User] { return _fromNonZeroUser(s.User) },
func(v User) UserState { return UserState{ User: v } },
"UserState.User",
)
return UserStatePrisms {
User: _prismUser,
}
}
// ConfigStateLenses provides lenses for accessing fields of ConfigState // ConfigStateLenses provides lenses for accessing fields of ConfigState
type ConfigStateLenses struct { type ConfigStateLenses struct {
// mandatory fields // mandatory fields
@@ -470,6 +654,13 @@ type ConfigStateRefLenses struct {
Config __lens.Lens[*ConfigState, string] Config __lens.Lens[*ConfigState, string]
// optional fields // optional fields
ConfigO __lens_option.LensO[*ConfigState, string] ConfigO __lens_option.LensO[*ConfigState, string]
// prisms
ConfigP __prism.Prism[*ConfigState, string]
}
// ConfigStatePrisms provides prisms for accessing fields of ConfigState
type ConfigStatePrisms struct {
Config __prism.Prism[ConfigState, string]
} }
// MakeConfigStateLenses creates a new ConfigStateLenses with lenses for all fields // MakeConfigStateLenses creates a new ConfigStateLenses with lenses for all fields
@@ -508,6 +699,19 @@ func MakeConfigStateRefLenses() ConfigStateRefLenses {
} }
} }
// MakeConfigStatePrisms creates a new ConfigStatePrisms with prisms for all fields
func MakeConfigStatePrisms() ConfigStatePrisms {
_fromNonZeroConfig := __option.FromNonZero[string]()
_prismConfig := __prism.MakePrismWithName(
func(s ConfigState) __option.Option[string] { return _fromNonZeroConfig(s.Config) },
func(v string) ConfigState { return ConfigState{ Config: v } },
"ConfigState.Config",
)
return ConfigStatePrisms {
Config: _prismConfig,
}
}
// NumberStateLenses provides lenses for accessing fields of NumberState // NumberStateLenses provides lenses for accessing fields of NumberState
type NumberStateLenses struct { type NumberStateLenses struct {
// mandatory fields // mandatory fields
@@ -522,6 +726,13 @@ type NumberStateRefLenses struct {
Number __lens.Lens[*NumberState, int] Number __lens.Lens[*NumberState, int]
// optional fields // optional fields
NumberO __lens_option.LensO[*NumberState, int] NumberO __lens_option.LensO[*NumberState, int]
// prisms
NumberP __prism.Prism[*NumberState, int]
}
// NumberStatePrisms provides prisms for accessing fields of NumberState
type NumberStatePrisms struct {
Number __prism.Prism[NumberState, int]
} }
// MakeNumberStateLenses creates a new NumberStateLenses with lenses for all fields // MakeNumberStateLenses creates a new NumberStateLenses with lenses for all fields
@@ -560,6 +771,19 @@ func MakeNumberStateRefLenses() NumberStateRefLenses {
} }
} }
// MakeNumberStatePrisms creates a new NumberStatePrisms with prisms for all fields
func MakeNumberStatePrisms() NumberStatePrisms {
_fromNonZeroNumber := __option.FromNonZero[int]()
_prismNumber := __prism.MakePrismWithName(
func(s NumberState) __option.Option[int] { return _fromNonZeroNumber(s.Number) },
func(v int) NumberState { return NumberState{ Number: v } },
"NumberState.Number",
)
return NumberStatePrisms {
Number: _prismNumber,
}
}
// DataStateLenses provides lenses for accessing fields of DataState // DataStateLenses provides lenses for accessing fields of DataState
type DataStateLenses struct { type DataStateLenses struct {
// mandatory fields // mandatory fields
@@ -574,6 +798,13 @@ type DataStateRefLenses struct {
Data __lens.Lens[*DataState, string] Data __lens.Lens[*DataState, string]
// optional fields // optional fields
DataO __lens_option.LensO[*DataState, string] DataO __lens_option.LensO[*DataState, string]
// prisms
DataP __prism.Prism[*DataState, string]
}
// DataStatePrisms provides prisms for accessing fields of DataState
type DataStatePrisms struct {
Data __prism.Prism[DataState, string]
} }
// MakeDataStateLenses creates a new DataStateLenses with lenses for all fields // MakeDataStateLenses creates a new DataStateLenses with lenses for all fields
@@ -612,6 +843,19 @@ func MakeDataStateRefLenses() DataStateRefLenses {
} }
} }
// MakeDataStatePrisms creates a new DataStatePrisms with prisms for all fields
func MakeDataStatePrisms() DataStatePrisms {
_fromNonZeroData := __option.FromNonZero[string]()
_prismData := __prism.MakePrismWithName(
func(s DataState) __option.Option[string] { return _fromNonZeroData(s.Data) },
func(v string) DataState { return DataState{ Data: v } },
"DataState.Data",
)
return DataStatePrisms {
Data: _prismData,
}
}
// RequestStateLenses provides lenses for accessing fields of RequestState // RequestStateLenses provides lenses for accessing fields of RequestState
type RequestStateLenses struct { type RequestStateLenses struct {
// mandatory fields // mandatory fields
@@ -626,6 +870,13 @@ type RequestStateRefLenses struct {
RequestID __lens.Lens[*RequestState, string] RequestID __lens.Lens[*RequestState, string]
// optional fields // optional fields
RequestIDO __lens_option.LensO[*RequestState, string] RequestIDO __lens_option.LensO[*RequestState, string]
// prisms
RequestIDP __prism.Prism[*RequestState, string]
}
// RequestStatePrisms provides prisms for accessing fields of RequestState
type RequestStatePrisms struct {
RequestID __prism.Prism[RequestState, string]
} }
// MakeRequestStateLenses creates a new RequestStateLenses with lenses for all fields // MakeRequestStateLenses creates a new RequestStateLenses with lenses for all fields
@@ -664,6 +915,19 @@ func MakeRequestStateRefLenses() RequestStateRefLenses {
} }
} }
// MakeRequestStatePrisms creates a new RequestStatePrisms with prisms for all fields
func MakeRequestStatePrisms() RequestStatePrisms {
_fromNonZeroRequestID := __option.FromNonZero[string]()
_prismRequestID := __prism.MakePrismWithName(
func(s RequestState) __option.Option[string] { return _fromNonZeroRequestID(s.RequestID) },
func(v string) RequestState { return RequestState{ RequestID: v } },
"RequestState.RequestID",
)
return RequestStatePrisms {
RequestID: _prismRequestID,
}
}
// ValueStateLenses provides lenses for accessing fields of ValueState // ValueStateLenses provides lenses for accessing fields of ValueState
type ValueStateLenses struct { type ValueStateLenses struct {
// mandatory fields // mandatory fields
@@ -678,6 +942,13 @@ type ValueStateRefLenses struct {
Value __lens.Lens[*ValueState, int] Value __lens.Lens[*ValueState, int]
// optional fields // optional fields
ValueO __lens_option.LensO[*ValueState, int] ValueO __lens_option.LensO[*ValueState, int]
// prisms
ValueP __prism.Prism[*ValueState, int]
}
// ValueStatePrisms provides prisms for accessing fields of ValueState
type ValueStatePrisms struct {
Value __prism.Prism[ValueState, int]
} }
// MakeValueStateLenses creates a new ValueStateLenses with lenses for all fields // MakeValueStateLenses creates a new ValueStateLenses with lenses for all fields
@@ -716,6 +987,19 @@ func MakeValueStateRefLenses() ValueStateRefLenses {
} }
} }
// MakeValueStatePrisms creates a new ValueStatePrisms with prisms for all fields
func MakeValueStatePrisms() ValueStatePrisms {
_fromNonZeroValue := __option.FromNonZero[int]()
_prismValue := __prism.MakePrismWithName(
func(s ValueState) __option.Option[int] { return _fromNonZeroValue(s.Value) },
func(v int) ValueState { return ValueState{ Value: v } },
"ValueState.Value",
)
return ValueStatePrisms {
Value: _prismValue,
}
}
// ResultStateLenses provides lenses for accessing fields of ResultState // ResultStateLenses provides lenses for accessing fields of ResultState
type ResultStateLenses struct { type ResultStateLenses struct {
// mandatory fields // mandatory fields
@@ -730,6 +1014,13 @@ type ResultStateRefLenses struct {
Result __lens.Lens[*ResultState, string] Result __lens.Lens[*ResultState, string]
// optional fields // optional fields
ResultO __lens_option.LensO[*ResultState, string] ResultO __lens_option.LensO[*ResultState, string]
// prisms
ResultP __prism.Prism[*ResultState, string]
}
// ResultStatePrisms provides prisms for accessing fields of ResultState
type ResultStatePrisms struct {
Result __prism.Prism[ResultState, string]
} }
// MakeResultStateLenses creates a new ResultStateLenses with lenses for all fields // MakeResultStateLenses creates a new ResultStateLenses with lenses for all fields
@@ -768,6 +1059,19 @@ func MakeResultStateRefLenses() ResultStateRefLenses {
} }
} }
// MakeResultStatePrisms creates a new ResultStatePrisms with prisms for all fields
func MakeResultStatePrisms() ResultStatePrisms {
_fromNonZeroResult := __option.FromNonZero[string]()
_prismResult := __prism.MakePrismWithName(
func(s ResultState) __option.Option[string] { return _fromNonZeroResult(s.Result) },
func(v string) ResultState { return ResultState{ Result: v } },
"ResultState.Result",
)
return ResultStatePrisms {
Result: _prismResult,
}
}
// EnvStateLenses provides lenses for accessing fields of EnvState // EnvStateLenses provides lenses for accessing fields of EnvState
type EnvStateLenses struct { type EnvStateLenses struct {
// mandatory fields // mandatory fields
@@ -782,6 +1086,13 @@ type EnvStateRefLenses struct {
Environment __lens.Lens[*EnvState, string] Environment __lens.Lens[*EnvState, string]
// optional fields // optional fields
EnvironmentO __lens_option.LensO[*EnvState, string] EnvironmentO __lens_option.LensO[*EnvState, string]
// prisms
EnvironmentP __prism.Prism[*EnvState, string]
}
// EnvStatePrisms provides prisms for accessing fields of EnvState
type EnvStatePrisms struct {
Environment __prism.Prism[EnvState, string]
} }
// MakeEnvStateLenses creates a new EnvStateLenses with lenses for all fields // MakeEnvStateLenses creates a new EnvStateLenses with lenses for all fields
@@ -820,6 +1131,19 @@ func MakeEnvStateRefLenses() EnvStateRefLenses {
} }
} }
// MakeEnvStatePrisms creates a new EnvStatePrisms with prisms for all fields
func MakeEnvStatePrisms() EnvStatePrisms {
_fromNonZeroEnvironment := __option.FromNonZero[string]()
_prismEnvironment := __prism.MakePrismWithName(
func(s EnvState) __option.Option[string] { return _fromNonZeroEnvironment(s.Environment) },
func(v string) EnvState { return EnvState{ Environment: v } },
"EnvState.Environment",
)
return EnvStatePrisms {
Environment: _prismEnvironment,
}
}
// ScoreStateLenses provides lenses for accessing fields of ScoreState // ScoreStateLenses provides lenses for accessing fields of ScoreState
type ScoreStateLenses struct { type ScoreStateLenses struct {
// mandatory fields // mandatory fields
@@ -834,6 +1158,13 @@ type ScoreStateRefLenses struct {
Score __lens.Lens[*ScoreState, int] Score __lens.Lens[*ScoreState, int]
// optional fields // optional fields
ScoreO __lens_option.LensO[*ScoreState, int] ScoreO __lens_option.LensO[*ScoreState, int]
// prisms
ScoreP __prism.Prism[*ScoreState, int]
}
// ScoreStatePrisms provides prisms for accessing fields of ScoreState
type ScoreStatePrisms struct {
Score __prism.Prism[ScoreState, int]
} }
// MakeScoreStateLenses creates a new ScoreStateLenses with lenses for all fields // MakeScoreStateLenses creates a new ScoreStateLenses with lenses for all fields
@@ -872,6 +1203,19 @@ func MakeScoreStateRefLenses() ScoreStateRefLenses {
} }
} }
// MakeScoreStatePrisms creates a new ScoreStatePrisms with prisms for all fields
func MakeScoreStatePrisms() ScoreStatePrisms {
_fromNonZeroScore := __option.FromNonZero[int]()
_prismScore := __prism.MakePrismWithName(
func(s ScoreState) __option.Option[int] { return _fromNonZeroScore(s.Score) },
func(v int) ScoreState { return ScoreState{ Score: v } },
"ScoreState.Score",
)
return ScoreStatePrisms {
Score: _prismScore,
}
}
// MessageStateLenses provides lenses for accessing fields of MessageState // MessageStateLenses provides lenses for accessing fields of MessageState
type MessageStateLenses struct { type MessageStateLenses struct {
// mandatory fields // mandatory fields
@@ -886,6 +1230,13 @@ type MessageStateRefLenses struct {
Message __lens.Lens[*MessageState, string] Message __lens.Lens[*MessageState, string]
// optional fields // optional fields
MessageO __lens_option.LensO[*MessageState, string] MessageO __lens_option.LensO[*MessageState, string]
// prisms
MessageP __prism.Prism[*MessageState, string]
}
// MessageStatePrisms provides prisms for accessing fields of MessageState
type MessageStatePrisms struct {
Message __prism.Prism[MessageState, string]
} }
// MakeMessageStateLenses creates a new MessageStateLenses with lenses for all fields // MakeMessageStateLenses creates a new MessageStateLenses with lenses for all fields
@@ -924,24 +1275,46 @@ func MakeMessageStateRefLenses() MessageStateRefLenses {
} }
} }
// MakeMessageStatePrisms creates a new MessageStatePrisms with prisms for all fields
func MakeMessageStatePrisms() MessageStatePrisms {
_fromNonZeroMessage := __option.FromNonZero[string]()
_prismMessage := __prism.MakePrismWithName(
func(s MessageState) __option.Option[string] { return _fromNonZeroMessage(s.Message) },
func(v string) MessageState { return MessageState{ Message: v } },
"MessageState.Message",
)
return MessageStatePrisms {
Message: _prismMessage,
}
}
// UserLenses provides lenses for accessing fields of User // UserLenses provides lenses for accessing fields of User
type UserLenses struct { type UserLenses struct {
// mandatory fields // mandatory fields
ID __lens.Lens[User, int] ID __lens.Lens[User, int]
Name __lens.Lens[User, string] Name __lens.Lens[User, string]
// optional fields // optional fields
IDO __lens_option.LensO[User, int] IDO __lens_option.LensO[User, int]
NameO __lens_option.LensO[User, string] NameO __lens_option.LensO[User, string]
} }
// UserRefLenses provides lenses for accessing fields of User via a reference to User // UserRefLenses provides lenses for accessing fields of User via a reference to User
type UserRefLenses struct { type UserRefLenses struct {
// mandatory fields // mandatory fields
ID __lens.Lens[*User, int] ID __lens.Lens[*User, int]
Name __lens.Lens[*User, string] Name __lens.Lens[*User, string]
// optional fields // optional fields
IDO __lens_option.LensO[*User, int] IDO __lens_option.LensO[*User, int]
NameO __lens_option.LensO[*User, string] NameO __lens_option.LensO[*User, string]
// prisms
IDP __prism.Prism[*User, int]
NameP __prism.Prism[*User, string]
}
// UserPrisms provides prisms for accessing fields of User
type UserPrisms struct {
ID __prism.Prism[User, int]
Name __prism.Prism[User, string]
} }
// MakeUserLenses creates a new UserLenses with lenses for all fields // MakeUserLenses creates a new UserLenses with lenses for all fields
@@ -962,10 +1335,10 @@ func MakeUserLenses() UserLenses {
lensNameO := __lens_option.FromIso[User](__iso_option.FromZero[string]())(lensName) lensNameO := __lens_option.FromIso[User](__iso_option.FromZero[string]())(lensName)
return UserLenses{ return UserLenses{
// mandatory lenses // mandatory lenses
ID: lensID, ID: lensID,
Name: lensName, Name: lensName,
// optional lenses // optional lenses
IDO: lensIDO, IDO: lensIDO,
NameO: lensNameO, NameO: lensNameO,
} }
} }
@@ -988,32 +1361,61 @@ func MakeUserRefLenses() UserRefLenses {
lensNameO := __lens_option.FromIso[*User](__iso_option.FromZero[string]())(lensName) lensNameO := __lens_option.FromIso[*User](__iso_option.FromZero[string]())(lensName)
return UserRefLenses{ return UserRefLenses{
// mandatory lenses // mandatory lenses
ID: lensID, ID: lensID,
Name: lensName, Name: lensName,
// optional lenses // optional lenses
IDO: lensIDO, IDO: lensIDO,
NameO: lensNameO, NameO: lensNameO,
} }
} }
// MakeUserPrisms creates a new UserPrisms with prisms for all fields
func MakeUserPrisms() UserPrisms {
_fromNonZeroID := __option.FromNonZero[int]()
_prismID := __prism.MakePrismWithName(
func(s User) __option.Option[int] { return _fromNonZeroID(s.ID) },
func(v int) User { return User{ ID: v } },
"User.ID",
)
_fromNonZeroName := __option.FromNonZero[string]()
_prismName := __prism.MakePrismWithName(
func(s User) __option.Option[string] { return _fromNonZeroName(s.Name) },
func(v string) User { return User{ Name: v } },
"User.Name",
)
return UserPrisms {
ID: _prismID,
Name: _prismName,
}
}
// ConfigLenses provides lenses for accessing fields of Config // ConfigLenses provides lenses for accessing fields of Config
type ConfigLenses struct { type ConfigLenses struct {
// mandatory fields // mandatory fields
Port __lens.Lens[Config, int] Port __lens.Lens[Config, int]
DatabaseURL __lens.Lens[Config, string] DatabaseURL __lens.Lens[Config, string]
// optional fields // optional fields
PortO __lens_option.LensO[Config, int] PortO __lens_option.LensO[Config, int]
DatabaseURLO __lens_option.LensO[Config, string] DatabaseURLO __lens_option.LensO[Config, string]
} }
// ConfigRefLenses provides lenses for accessing fields of Config via a reference to Config // ConfigRefLenses provides lenses for accessing fields of Config via a reference to Config
type ConfigRefLenses struct { type ConfigRefLenses struct {
// mandatory fields // mandatory fields
Port __lens.Lens[*Config, int] Port __lens.Lens[*Config, int]
DatabaseURL __lens.Lens[*Config, string] DatabaseURL __lens.Lens[*Config, string]
// optional fields // optional fields
PortO __lens_option.LensO[*Config, int] PortO __lens_option.LensO[*Config, int]
DatabaseURLO __lens_option.LensO[*Config, string] DatabaseURLO __lens_option.LensO[*Config, string]
// prisms
PortP __prism.Prism[*Config, int]
DatabaseURLP __prism.Prism[*Config, string]
}
// ConfigPrisms provides prisms for accessing fields of Config
type ConfigPrisms struct {
Port __prism.Prism[Config, int]
DatabaseURL __prism.Prism[Config, string]
} }
// MakeConfigLenses creates a new ConfigLenses with lenses for all fields // MakeConfigLenses creates a new ConfigLenses with lenses for all fields
@@ -1034,10 +1436,10 @@ func MakeConfigLenses() ConfigLenses {
lensDatabaseURLO := __lens_option.FromIso[Config](__iso_option.FromZero[string]())(lensDatabaseURL) lensDatabaseURLO := __lens_option.FromIso[Config](__iso_option.FromZero[string]())(lensDatabaseURL)
return ConfigLenses{ return ConfigLenses{
// mandatory lenses // mandatory lenses
Port: lensPort, Port: lensPort,
DatabaseURL: lensDatabaseURL, DatabaseURL: lensDatabaseURL,
// optional lenses // optional lenses
PortO: lensPortO, PortO: lensPortO,
DatabaseURLO: lensDatabaseURLO, DatabaseURLO: lensDatabaseURLO,
} }
} }
@@ -1060,10 +1462,30 @@ func MakeConfigRefLenses() ConfigRefLenses {
lensDatabaseURLO := __lens_option.FromIso[*Config](__iso_option.FromZero[string]())(lensDatabaseURL) lensDatabaseURLO := __lens_option.FromIso[*Config](__iso_option.FromZero[string]())(lensDatabaseURL)
return ConfigRefLenses{ return ConfigRefLenses{
// mandatory lenses // mandatory lenses
Port: lensPort, Port: lensPort,
DatabaseURL: lensDatabaseURL, DatabaseURL: lensDatabaseURL,
// optional lenses // optional lenses
PortO: lensPortO, PortO: lensPortO,
DatabaseURLO: lensDatabaseURLO, DatabaseURLO: lensDatabaseURLO,
} }
} }
// MakeConfigPrisms creates a new ConfigPrisms with prisms for all fields
func MakeConfigPrisms() ConfigPrisms {
_fromNonZeroPort := __option.FromNonZero[int]()
_prismPort := __prism.MakePrismWithName(
func(s Config) __option.Option[int] { return _fromNonZeroPort(s.Port) },
func(v int) Config { return Config{ Port: v } },
"Config.Port",
)
_fromNonZeroDatabaseURL := __option.FromNonZero[string]()
_prismDatabaseURL := __prism.MakePrismWithName(
func(s Config) __option.Option[string] { return _fromNonZeroDatabaseURL(s.DatabaseURL) },
func(v string) Config { return Config{ DatabaseURL: v } },
"Config.DatabaseURL",
)
return ConfigPrisms {
Port: _prismPort,
DatabaseURL: _prismDatabaseURL,
}
}

View File

@@ -22,6 +22,8 @@ import (
"github.com/IBM/fp-go/v2/endomorphism" "github.com/IBM/fp-go/v2/endomorphism"
"github.com/IBM/fp-go/v2/lazy" "github.com/IBM/fp-go/v2/lazy"
"github.com/IBM/fp-go/v2/monoid" "github.com/IBM/fp-go/v2/monoid"
"github.com/IBM/fp-go/v2/optics/lens"
"github.com/IBM/fp-go/v2/optics/prism"
"github.com/IBM/fp-go/v2/option" "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/reader" "github.com/IBM/fp-go/v2/reader"
"github.com/IBM/fp-go/v2/result" "github.com/IBM/fp-go/v2/result"
@@ -46,12 +48,24 @@ type (
// Reader represents a computation that depends on a read-only environment of type R and produces a value of type A. // Reader represents a computation that depends on a read-only environment of type R and produces a value of type A.
Reader[R, A any] = reader.Reader[R, A] Reader[R, A any] = reader.Reader[R, A]
// ReaderResult represents a computation that depends on a context.Context and produces either a value of type A or an error.
// It combines the Reader pattern with Result (error handling), making it suitable for context-aware operations that may fail.
ReaderResult[A any] = func(context.Context) (A, error) ReaderResult[A any] = func(context.Context) (A, error)
// Monoid represents a monoid structure for ReaderResult values. // Monoid represents a monoid structure for ReaderResult values.
Monoid[A any] = monoid.Monoid[ReaderResult[A]] Monoid[A any] = monoid.Monoid[ReaderResult[A]]
// Kleisli represents a Kleisli arrow from A to ReaderResult[B].
// It's a function that takes a value of type A and returns a computation that produces B or an error in a context.
Kleisli[A, B any] = Reader[A, ReaderResult[B]] Kleisli[A, B any] = Reader[A, ReaderResult[B]]
// Operator represents a Kleisli arrow that operates on ReaderResult values.
// It transforms a ReaderResult[A] into a ReaderResult[B], useful for composing context-aware operations.
Operator[A, B any] = Kleisli[ReaderResult[A], B] Operator[A, B any] = Kleisli[ReaderResult[A], B]
// Lens represents an optic that focuses on a field of type A within a structure of type S.
Lens[S, A any] = lens.Lens[S, A]
// Prism represents an optic that focuses on a case of type A within a sum type S.
Prism[S, A any] = prism.Prism[S, A]
) )

View File

@@ -20,7 +20,6 @@ import (
"github.com/IBM/fp-go/v2/internal/apply" "github.com/IBM/fp-go/v2/internal/apply"
"github.com/IBM/fp-go/v2/internal/chain" "github.com/IBM/fp-go/v2/internal/chain"
"github.com/IBM/fp-go/v2/internal/functor" "github.com/IBM/fp-go/v2/internal/functor"
L "github.com/IBM/fp-go/v2/optics/lens"
) )
// Do creates an empty context of type [S] to be used with the [Bind] operation. // Do creates an empty context of type [S] to be used with the [Bind] operation.
@@ -75,7 +74,7 @@ func Do[S any](
func Bind[S1, S2, T any]( func Bind[S1, S2, T any](
setter func(T) func(S1) S2, setter func(T) func(S1) S2,
f Kleisli[S1, T], f Kleisli[S1, T],
) Kleisli[IOOption[S1], S2] { ) Operator[S1, S2] {
return chain.Bind( return chain.Bind(
Chain[S1, S2], Chain[S1, S2],
Map[T, S2], Map[T, S2],
@@ -88,7 +87,7 @@ func Bind[S1, S2, T any](
func Let[S1, S2, T any]( func Let[S1, S2, T any](
setter func(T) func(S1) S2, setter func(T) func(S1) S2,
f func(S1) T, f func(S1) T,
) Kleisli[IOOption[S1], S2] { ) Operator[S1, S2] {
return functor.Let( return functor.Let(
Map[S1, S2], Map[S1, S2],
setter, setter,
@@ -100,7 +99,7 @@ func Let[S1, S2, T any](
func LetTo[S1, S2, T any]( func LetTo[S1, S2, T any](
setter func(T) func(S1) S2, setter func(T) func(S1) S2,
b T, b T,
) Kleisli[IOOption[S1], S2] { ) Operator[S1, S2] {
return functor.LetTo( return functor.LetTo(
Map[S1, S2], Map[S1, S2],
setter, setter,
@@ -111,13 +110,20 @@ func LetTo[S1, S2, T any](
// BindTo initializes a new state [S1] from a value [T] // BindTo initializes a new state [S1] from a value [T]
func BindTo[S1, T any]( func BindTo[S1, T any](
setter func(T) S1, setter func(T) S1,
) Kleisli[IOOption[T], S1] { ) Operator[T, S1] {
return chain.BindTo( return chain.BindTo(
Map[T, S1], Map[T, S1],
setter, setter,
) )
} }
//go:inline
func BindToP[S1, T any](
setter Prism[S1, T],
) Operator[T, S1] {
return BindTo(setter.ReverseGet)
}
// ApS attaches a value to a context [S1] to produce a context [S2] by considering // ApS attaches a value to a context [S1] to produce a context [S2] by considering
// the context and the value concurrently (using Applicative rather than Monad). // the context and the value concurrently (using Applicative rather than Monad).
// This allows independent computations to be combined without one depending on the result of the other. // This allows independent computations to be combined without one depending on the result of the other.
@@ -154,7 +160,7 @@ func BindTo[S1, T any](
func ApS[S1, S2, T any]( func ApS[S1, S2, T any](
setter func(T) func(S1) S2, setter func(T) func(S1) S2,
fa IOOption[T], fa IOOption[T],
) Kleisli[IOOption[S1], S2] { ) Operator[S1, S2] {
return apply.ApS( return apply.ApS(
Ap[S2, T], Ap[S2, T],
Map[S1, func(T) S2], Map[S1, func(T) S2],
@@ -187,9 +193,9 @@ func ApS[S1, S2, T any](
// iooption.ApSL(ageLens, iooption.Some(30)), // iooption.ApSL(ageLens, iooption.Some(30)),
// ) // )
func ApSL[S, T any]( func ApSL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
fa IOOption[T], fa IOOption[T],
) Kleisli[IOOption[S], S] { ) Operator[S, S] {
return ApS(lens.Set, fa) return ApS(lens.Set, fa)
} }
@@ -222,9 +228,9 @@ func ApSL[S, T any](
// iooption.BindL(valueLens, increment), // iooption.BindL(valueLens, increment),
// ) // IOOption[Counter{Value: 43}] // ) // IOOption[Counter{Value: 43}]
func BindL[S, T any]( func BindL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
f Kleisli[T, T], f Kleisli[T, T],
) Kleisli[IOOption[S], S] { ) Operator[S, S] {
return Bind(lens.Set, F.Flow2(lens.Get, f)) return Bind(lens.Set, F.Flow2(lens.Get, f))
} }
@@ -255,9 +261,9 @@ func BindL[S, T any](
// iooption.LetL(valueLens, double), // iooption.LetL(valueLens, double),
// ) // IOOption[Counter{Value: 42}] // ) // IOOption[Counter{Value: 42}]
func LetL[S, T any]( func LetL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
f func(T) T, f func(T) T,
) Kleisli[IOOption[S], S] { ) Operator[S, S] {
return Let(lens.Set, F.Flow2(lens.Get, f)) return Let(lens.Set, F.Flow2(lens.Get, f))
} }
@@ -286,8 +292,8 @@ func LetL[S, T any](
// iooption.LetToL(debugLens, false), // iooption.LetToL(debugLens, false),
// ) // IOOption[Config{Debug: false, Timeout: 30}] // ) // IOOption[Config{Debug: false, Timeout: 30}]
func LetToL[S, T any]( func LetToL[S, T any](
lens L.Lens[S, T], lens Lens[S, T],
b T, b T,
) Kleisli[IOOption[S], S] { ) Operator[S, S] {
return LetTo(lens.Set, b) return LetTo(lens.Set, b)
} }

View File

@@ -20,6 +20,8 @@ import (
"github.com/IBM/fp-go/v2/either" "github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/io" "github.com/IBM/fp-go/v2/io"
"github.com/IBM/fp-go/v2/lazy" "github.com/IBM/fp-go/v2/lazy"
"github.com/IBM/fp-go/v2/optics/lens"
"github.com/IBM/fp-go/v2/optics/prism"
"github.com/IBM/fp-go/v2/option" "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/reader" "github.com/IBM/fp-go/v2/reader"
) )
@@ -37,4 +39,7 @@ type (
Kleisli[A, B any] = reader.Reader[A, IOOption[B]] Kleisli[A, B any] = reader.Reader[A, IOOption[B]]
Operator[A, B any] = Kleisli[IOOption[A], B] Operator[A, B any] = Kleisli[IOOption[A], B]
Consumer[A any] = consumer.Consumer[A] Consumer[A any] = consumer.Consumer[A]
Lens[S, T any] = lens.Lens[S, T]
Prism[S, T any] = prism.Prism[S, T]
) )

View File

@@ -1,12 +1,36 @@
# Optics # 🔍 Optics
Functional optics for composable data access and manipulation in Go. Functional optics for composable data access and manipulation in Go.
## Overview ## 📖 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. 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 ## ✨ Why Use Optics?
Optics bring powerful benefits to your Go code:
- **🎯 Composability**: Optics naturally compose with each other and with monadic operations, enabling elegant data transformations through function composition
- **🔒 Immutability**: Work with immutable data structures without manual copying and updating
- **🧩 Type Safety**: Leverage Go's type system to catch errors at compile time
- **📦 Reusability**: Define data access patterns once and reuse them throughout your codebase
- **🎨 Expressiveness**: Write declarative code that clearly expresses intent
- **🔄 Bidirectionality**: Read and write through the same abstraction
- **🚀 Productivity**: Eliminate boilerplate for nested data access and updates
- **🧪 Testability**: Optics are pure functions, making them easy to test and reason about
### 🔗 Composition with Monadic Operations
One of the most powerful features of optics is their natural composition with monadic operations. Optics integrate seamlessly with `fp-go`'s monadic types like `Option`, `Either`, `Result`, and `IO`, allowing you to:
- Chain optional field access with `Option` monads
- Handle errors gracefully with `Either` or `Result` monads
- Perform side effects with `IO` monads
- Combine multiple optics in a single pipeline using `Pipe`
This composability enables you to build complex data transformations from simple, reusable building blocks.
## 🚀 Quick Start
```go ```go
import ( import (
@@ -38,9 +62,9 @@ updated := nameLens.Set("Bob")(person)
// person.Name is still "Alice", updated.Name is "Bob" // person.Name is still "Alice", updated.Name is "Bob"
``` ```
## Core Optics Types ## 🛠️ Core Optics Types
### Lens - Product Types (Structs) ### 🔎 Lens - Product Types (Structs)
Focus on a single field within a struct. Provides get and set operations. Focus on a single field within a struct. Provides get and set operations.
**Use when:** Working with struct fields that always exist. **Use when:** Working with struct fields that always exist.
@@ -55,14 +79,19 @@ ageLens := lens.MakeLens(
) )
``` ```
### Prism - Sum Types (Variants) ### 🔀 Prism - Sum Types (Variants)
Focus on one variant of a sum type. Provides optional get and definite set. Focus on one variant of a sum type. Provides optional get and definite set.
**Use when:** Working with Either, Result, or custom sum types. **Use when:** Working with Either, Result, or custom sum types.
**💡 Important Use Case - Generalized Constructors for Do Notation:**
Prisms act as generalized constructors, making them invaluable for `Do` notation workflows. The prism's `ReverseGet` function serves as a constructor that creates a value of the sum type from a specific variant. This is particularly useful when building up complex data structures step-by-step in monadic contexts:
```go ```go
import "github.com/IBM/fp-go/v2/optics/prism" import "github.com/IBM/fp-go/v2/optics/prism"
// Prism for the Success variant
successPrism := prism.MakePrism( successPrism := prism.MakePrism(
func(r Result) option.Option[int] { func(r Result) option.Option[int] {
if s, ok := r.(Success); ok { if s, ok := r.(Success); ok {
@@ -70,11 +99,18 @@ successPrism := prism.MakePrism(
} }
return option.None[int]() return option.None[int]()
}, },
func(v int) Result { return Success{Value: v} }, func(v int) Result { return Success{Value: v} }, // Constructor!
)
// Use in Do notation to construct values
result := F.Pipe2(
computeValue(),
option.Map(func(v int) int { return v * 2 }),
option.Map(successPrism.ReverseGet), // Construct Result from int
) )
``` ```
### Iso - Isomorphisms ### 🔄 Iso - Isomorphisms
Bidirectional transformation between equivalent types with no information loss. Bidirectional transformation between equivalent types with no information loss.
**Use when:** Converting between equivalent representations (e.g., Celsius ↔ Fahrenheit). **Use when:** Converting between equivalent representations (e.g., Celsius ↔ Fahrenheit).
@@ -88,7 +124,7 @@ celsiusToFahrenheit := iso.MakeIso(
) )
``` ```
### Optional - Maybe Values ### Optional - Maybe Values
Focus on a value that may or may not exist. Focus on a value that may or may not exist.
**Use when:** Working with nullable fields or values that may be absent. **Use when:** Working with nullable fields or values that may be absent.
@@ -107,7 +143,7 @@ timeoutOptional := optional.MakeOptional(
) )
``` ```
### Traversal - Multiple Values ### 🔢 Traversal - Multiple Values
Focus on multiple values simultaneously, allowing batch operations. Focus on multiple values simultaneously, allowing batch operations.
**Use when:** Working with collections or updating multiple fields at once. **Use when:** Working with collections or updating multiple fields at once.
@@ -129,7 +165,7 @@ doubled := F.Pipe2(
// Result: [2, 4, 6, 8, 10] // Result: [2, 4, 6, 8, 10]
``` ```
## Composition ## 🔗 Composition
The real power of optics comes from composition: The real power of optics comes from composition:
@@ -176,7 +212,66 @@ city := companyCityLens.Get(company) // "NYC"
updated := companyCityLens.Set("Boston")(company) updated := companyCityLens.Set("Boston")(company)
``` ```
## Optics Hierarchy ## ⚙️ Auto-Generation with `go generate`
Lenses can be automatically generated using the `fp-go` CLI tool and a simple annotation. This eliminates boilerplate and ensures consistency.
### 📝 How to Use
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
// fp-go:Lens
type Person struct {
Name string
Age int
Email string
Phone *string // Optional field
}
```
2. **Run `go generate`**:
```bash
go generate ./...
```
3. **Use the generated lenses**:
```go
// Generated code creates PersonLenses, PersonRefLenses, and PersonPrisms
lenses := MakePersonLenses()
person := Person{Name: "Alice", Age: 30, Email: "alice@example.com"}
// Use the generated lenses
updatedPerson := lenses.Age.Set(31)(person)
name := lenses.Name.Get(person)
// Optional lenses for zero-value handling
personWithEmail := lenses.EmailO.Set(option.Some("new@example.com"))(person)
```
### 🎁 What Gets Generated
For each annotated struct, the generator creates:
- **`StructNameLenses`**: Lenses for value types with optional variants (`LensO`) for comparable fields
- **`StructNameRefLenses`**: Lenses for pointer types with prisms for constructing values
- **`StructNamePrisms`**: Prisms for all fields, useful for partial construction
- Constructor functions: `MakeStructNameLenses()`, `MakeStructNameRefLenses()`, `MakeStructNamePrisms()`
The generator supports:
- ✅ Generic types with type parameters
- ✅ Embedded structs (fields are promoted)
- ✅ Optional fields (pointers and `omitempty` tags)
- ✅ Custom package imports
See [samples/lens](../samples/lens) for complete examples.
## 📊 Optics Hierarchy
``` ```
Iso[S, A] Iso[S, A]
@@ -196,7 +291,7 @@ Traversal[S, A]
More specific optics can be converted to more general ones. More specific optics can be converted to more general ones.
## Package Structure ## 📦 Package Structure
- **optics/lens**: Lenses for product types (structs) - **optics/lens**: Lenses for product types (structs)
- **optics/prism**: Prisms for sum types (Either, Result, etc.) - **optics/prism**: Prisms for sum types (Either, Result, etc.)
@@ -210,7 +305,7 @@ Each package includes specialized sub-packages for common patterns:
- **option**: Optics for Option types - **option**: Optics for Option types
- **record**: Optics for maps - **record**: Optics for maps
## Documentation ## 📚 Documentation
For detailed documentation on each optic type, see: For detailed documentation on each optic type, see:
- [Main Package Documentation](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics) - [Main Package Documentation](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics)
@@ -220,15 +315,34 @@ For detailed documentation on each optic type, see:
- [Optional Documentation](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/optional) - [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) - [Traversal Documentation](https://pkg.go.dev/github.com/IBM/fp-go/v2/optics/traversal)
## Further Reading ## 🌐 Further Reading
For an introduction to functional optics concepts: ### Haskell Lens Library
- [Introduction to optics: lenses and prisms](https://medium.com/@gcanti/introduction-to-optics-lenses-and-prisms-3230e73bfcfe) by Giulio Canti The concepts in this library are inspired by the powerful [Haskell lens library](https://hackage.haskell.org/package/lens), which pioneered many of these abstractions.
## Examples ### Articles and Resources
- [Introduction to optics: lenses and prisms](https://medium.com/@gcanti/introduction-to-optics-lenses-and-prisms-3230e73bfcfe) by Giulio Canti - Excellent introduction to optics concepts
- [Lenses in Functional Programming](https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/a-little-lens-starter-tutorial) - Tutorial on lens fundamentals
- [Profunctor Optics: The Categorical View](https://bartoszmilewski.com/2017/07/07/profunctor-optics-the-categorical-view/) by Bartosz Milewski - Deep dive into the theory
- [Why Optics?](https://www.tweag.io/blog/2022-01-06-optics-vs-lenses/) - Discussion of benefits and use cases
See the [samples/lens](../samples/lens) directory for complete working examples. ### Why Functional Optics?
Functional optics solve real problems in software development:
- **Nested Updates**: Eliminate deeply nested field access patterns
- **Immutability**: Make working with immutable data practical and ergonomic
- **Abstraction**: Separate data access patterns from business logic
- **Composition**: Build complex operations from simple, reusable pieces
- **Type Safety**: Catch errors at compile time rather than runtime
## License ## 💡 Examples
See the [samples/lens](../samples/lens) directory for complete working examples, including:
- Basic lens usage
- Lens composition
- Auto-generated lenses
- Prism usage for sum types
- Integration with monadic operations
## 📄 License
Apache License 2.0 - See LICENSE file for details. Apache License 2.0 - See LICENSE file for details.

View File

@@ -305,6 +305,63 @@ Convenience Functions:
- Unwrap/To: Extract the target value (Get) - Unwrap/To: Extract the target value (Get)
- Wrap/From: Wrap into the source value (ReverseGet) - Wrap/From: Wrap into the source value (ReverseGet)
# Useful Iso Implementations
The package provides several ready-to-use isomorphisms for common transformations:
**String and Byte Conversions:**
- UTF8String: []byte ↔ string (UTF-8 encoding)
- Lines: []string ↔ string (newline-separated text)
**Time Conversions:**
- UnixMilli: int64 ↔ time.Time (Unix millisecond timestamps)
**Numeric Operations:**
- Add[T]: T ↔ T (shift by constant addition)
- Sub[T]: T ↔ T (shift by constant subtraction)
**Collection Operations:**
- ReverseArray[A]: []A ↔ []A (reverse slice order, self-inverse)
- Head[A]: A ↔ NonEmptyArray[A] (singleton array conversion)
**Pair and Either Operations:**
- SwapPair[A, B]: Pair[A, B] ↔ Pair[B, A] (swap pair elements, self-inverse)
- SwapEither[E, A]: Either[E, A] ↔ Either[A, E] (swap Either types, self-inverse)
**Option Conversions (optics/iso/option):**
- FromZero[T]: T ↔ Option[T] (zero value ↔ None, non-zero ↔ Some)
**Lens Conversions (optics/iso/lens):**
- IsoAsLens: Convert Iso[S, A] to Lens[S, A]
- IsoAsLensRef: Convert Iso[*S, A] to Lens[*S, A]
Example usage of built-in isomorphisms:
// String/byte conversion
utf8 := UTF8String()
str := utf8.Get([]byte("hello")) // "hello"
// Time conversion
unixTime := UnixMilli()
t := unixTime.Get(1609459200000) // 2021-01-01 00:00:00 UTC
// Numeric shift
addTen := Add(10)
result := addTen.Get(5) // 15
// Array reversal
reverse := ReverseArray[int]()
reversed := reverse.Get([]int{1, 2, 3}) // []int{3, 2, 1}
// Pair swap
swap := SwapPair[string, int]()
swapped := swap.Get(pair.MakePair("a", 1)) // Pair[int, string](1, "a")
// Option conversion
optIso := option.FromZero[int]()
opt := optIso.Get(0) // None
opt = optIso.Get(42) // Some(42)
# Related Packages # Related Packages
- github.com/IBM/fp-go/v2/optics/lens: Lenses for focusing on parts of structures - github.com/IBM/fp-go/v2/optics/lens: Lenses for focusing on parts of structures

View File

@@ -409,6 +409,62 @@ Prisms are fully type-safe:
- Generic type parameters ensure correctness - Generic type parameters ensure correctness
- Composition maintains type relationships - Composition maintains type relationships
# Built-in Prisms
The package provides many useful prisms for common transformations:
**Type Conversion & Parsing:**
- FromEncoding(enc): Base64 encoding/decoding - Prism[string, []byte]
- ParseURL(): URL parsing/formatting - Prism[string, *url.URL]
- ParseDate(layout): Date parsing with custom layouts - Prism[string, time.Time]
- ParseInt(): Integer string parsing - Prism[string, int]
- ParseInt64(): 64-bit integer parsing - Prism[string, int64]
- ParseBool(): Boolean string parsing - Prism[string, bool]
- ParseFloat32(): 32-bit float parsing - Prism[string, float32]
- ParseFloat64(): 64-bit float parsing - Prism[string, float64]
**Type Assertion & Extraction:**
- InstanceOf[T](): Safe type assertion from any - Prism[any, T]
- Deref[T](): Safe pointer dereferencing (filters nil) - Prism[*T, *T]
**Container/Wrapper Prisms:**
- FromEither[E, T](): Extract Right values - Prism[Either[E, T], T]
ReverseGet wraps into Right (acts as success constructor)
- FromResult[T](): Extract success from Result - Prism[Result[T], T]
ReverseGet wraps into success Result
- FromOption[T](): Extract Some values - Prism[Option[T], T]
ReverseGet wraps into Some (acts as Some constructor)
**Validation Prisms:**
- FromZero[T](): Match only zero/default values - Prism[T, T]
- FromNonZero[T](): Match only non-zero values - Prism[T, T]
**Pattern Matching:**
- RegexMatcher(re): Extract regex matches with groups - Prism[string, Match]
- RegexNamedMatcher(re): Extract named regex groups - Prism[string, NamedMatch]
Example using built-in prisms:
// Parse and validate an integer from a string
intPrism := prism.ParseInt()
value := intPrism.GetOption("42") // Some(42)
invalid := intPrism.GetOption("abc") // None[int]()
// Extract success values from Either
resultPrism := prism.FromEither[error, int]()
success := either.Right[error](100)
value = resultPrism.GetOption(success) // Some(100)
// ReverseGet acts as a constructor
wrapped := resultPrism.ReverseGet(42) // Right(42)
// Compose prisms for complex transformations
// Parse string to int, then wrap in Option
composed := F.Pipe1(
prism.ParseInt(),
prism.Compose[string](prism.FromOption[int]()),
)
# Function Reference # Function Reference
Core Functions: Core Functions:

View File

@@ -59,6 +59,11 @@ func FromEq[A any](pred eq.Eq[A]) func(A) Kleisli[A, A] {
return F.Flow2(P.IsEqual(pred), FromPredicate[A]) return F.Flow2(P.IsEqual(pred), FromPredicate[A])
} }
//go:inline
func FromStrictEq[A comparable]() func(A) Kleisli[A, A] {
return FromEq(eq.FromStrictEquals[A]())
}
// FromNillable converts a pointer to an Option. // FromNillable converts a pointer to an Option.
// Returns Some if the pointer is non-nil, None otherwise. // Returns Some if the pointer is non-nil, None otherwise.
// //

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,171 @@
// 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 lens
import (
"testing"
F "github.com/IBM/fp-go/v2/function"
O "github.com/IBM/fp-go/v2/option"
"github.com/stretchr/testify/assert"
)
// TestCompanyExtendedPrismWebsite tests that we can create a CompanyExtended
// with only the Website field set using prisms
func TestCompanyExtendedPrismWebsite(t *testing.T) {
prisms := MakeCompanyExtendedPrisms()
website := "https://example.com"
// Use the Website prism to create a CompanyExtended with only Website set
result := prisms.Website.ReverseGet(&website)
// Verify the Website field is set
assert.NotNil(t, result.Website)
assert.Equal(t, website, *result.Website)
// Verify other fields are zero values
assert.Equal(t, "", result.Name)
assert.Equal(t, "", result.Extended)
assert.Equal(t, Address{}, result.Address)
assert.Equal(t, Person{}, result.CEO)
}
// TestCompanyExtendedPrismName tests that we can create a CompanyExtended
// with only the Name field set (from embedded Company struct)
func TestCompanyExtendedPrismName(t *testing.T) {
prisms := MakeCompanyExtendedPrisms()
name := "Acme Corp"
// Use the Name prism to create a CompanyExtended with only Name set
result := prisms.Name.ReverseGet(name)
// Verify the Name field is set (from embedded Company)
assert.Equal(t, name, result.Name)
// Verify other fields are zero values
assert.Nil(t, result.Website)
assert.Equal(t, "", result.Extended)
assert.Equal(t, Address{}, result.Address)
assert.Equal(t, Person{}, result.CEO)
}
// TestCompanyExtendedPrismExtended tests that we can create a CompanyExtended
// with only the Extended field set
func TestCompanyExtendedPrismExtended(t *testing.T) {
prisms := MakeCompanyExtendedPrisms()
extended := "Extra Info"
// Use the Extended prism to create a CompanyExtended with only Extended set
result := prisms.Extended.ReverseGet(extended)
// Verify the Extended field is set
assert.Equal(t, extended, result.Extended)
// Verify other fields are zero values
assert.Nil(t, result.Website)
assert.Equal(t, "", result.Name)
assert.Equal(t, Address{}, result.Address)
assert.Equal(t, Person{}, result.CEO)
}
// TestCompanyExtendedPrismGetOption tests that GetOption works correctly
func TestCompanyExtendedPrismGetOption(t *testing.T) {
prisms := MakeCompanyExtendedPrisms()
t.Run("GetOption returns Some for non-zero Name", func(t *testing.T) {
company := CompanyExtended{
Company: Company{
Name: "Test Corp",
},
Extended: "Info",
}
result := prisms.Name.GetOption(company)
assert.True(t, O.IsSome(result))
assert.Equal(t, "Test Corp", O.GetOrElse(F.Constant(""))(result))
})
t.Run("GetOption returns None for zero Name", func(t *testing.T) {
company := CompanyExtended{
Extended: "Info",
}
result := prisms.Name.GetOption(company)
assert.True(t, O.IsNone(result))
})
t.Run("GetOption returns Some for non-nil Website", func(t *testing.T) {
website := "https://example.com"
company := CompanyExtended{
Company: Company{
Website: &website,
},
}
result := prisms.Website.GetOption(company)
assert.True(t, O.IsSome(result))
extracted := O.GetOrElse(F.Constant[*string](nil))(result)
assert.NotNil(t, extracted)
assert.Equal(t, website, *extracted)
})
t.Run("GetOption returns None for nil Website", func(t *testing.T) {
company := CompanyExtended{}
result := prisms.Website.GetOption(company)
assert.True(t, O.IsNone(result))
})
}
// TestCompanyPrismWebsite tests that Company prisms work correctly
func TestCompanyPrismWebsite(t *testing.T) {
prisms := MakeCompanyPrisms()
website := "https://company.com"
// Use the Website prism to create a Company with only Website set
result := prisms.Website.ReverseGet(&website)
// Verify the Website field is set
assert.NotNil(t, result.Website)
assert.Equal(t, website, *result.Website)
// Verify other fields are zero values
assert.Equal(t, "", result.Name)
assert.Equal(t, Address{}, result.Address)
assert.Equal(t, Person{}, result.CEO)
}
// TestPersonPrismName tests that Person prisms work correctly
func TestPersonPrismName(t *testing.T) {
prisms := MakePersonPrisms()
name := "John Doe"
// Use the Name prism to create a Person with only Name set
result := prisms.Name.ReverseGet(name)
// Verify the Name field is set
assert.Equal(t, name, result.Name)
// Verify other fields are zero values
assert.Equal(t, 0, result.Age)
assert.Equal(t, "", result.Email)
assert.Nil(t, result.Phone)
}