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:
@@ -368,5 +368,3 @@ func TestToNonEmptyArrayUseCases(t *testing.T) {
|
|||||||
assert.Equal(t, "default", result2)
|
assert.Equal(t, "default", result2)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Made with Bob
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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]
|
||||||
)
|
)
|
||||||
|
|||||||
780
v2/idiomatic/context/readerresult/README.md
Normal file
780
v2/idiomatic/context/readerresult/README.md
Normal 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
|
||||||
@@ -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](
|
||||||
|
|||||||
627
v2/idiomatic/context/readerresult/bind_test.go
Normal file
627
v2/idiomatic/context/readerresult/bind_test.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -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])
|
||||||
}
|
}
|
||||||
|
|||||||
630
v2/idiomatic/context/readerresult/bracket_test.go
Normal file
630
v2/idiomatic/context/readerresult/bracket_test.go
Normal 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())
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
171
v2/samples/lens/prism_test.go
Normal file
171
v2/samples/lens/prism_test.go
Normal 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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user