mirror of
https://github.com/IBM/fp-go.git
synced 2025-12-15 23:33:46 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cd575d95a |
@@ -50,6 +50,7 @@ import (
|
||||
|
||||
"github.com/IBM/fp-go/v2/boolean"
|
||||
"github.com/IBM/fp-go/v2/eq"
|
||||
"github.com/IBM/fp-go/v2/option"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -266,3 +267,197 @@ func RunAll(testcases map[string]Reader) Reader {
|
||||
return current
|
||||
}
|
||||
}
|
||||
|
||||
// Local transforms a Reader that works on type R1 into a Reader that works on type R2,
|
||||
// by providing a function that converts R2 to R1. This allows you to focus a test on a
|
||||
// specific property or subset of a larger data structure.
|
||||
//
|
||||
// This is particularly useful when you have an assertion that operates on a specific field
|
||||
// or property, and you want to apply it to a complete object. Instead of extracting the
|
||||
// property and then asserting on it, you can transform the assertion to work directly
|
||||
// on the whole object.
|
||||
//
|
||||
// Parameters:
|
||||
// - f: A function that extracts or transforms R2 into R1
|
||||
//
|
||||
// Returns:
|
||||
// - A function that transforms a Reader[R1, Reader] into a Reader[R2, Reader]
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type User struct {
|
||||
// Name string
|
||||
// Age int
|
||||
// }
|
||||
//
|
||||
// // Create an assertion that checks if age is positive
|
||||
// ageIsPositive := assert.That(func(age int) bool { return age > 0 })
|
||||
//
|
||||
// // Focus this assertion on the Age field of User
|
||||
// userAgeIsPositive := assert.Local(func(u User) int { return u.Age })(ageIsPositive)
|
||||
//
|
||||
// // Now we can test the whole User object
|
||||
// user := User{Name: "Alice", Age: 30}
|
||||
// userAgeIsPositive(user)(t)
|
||||
//
|
||||
//go:inline
|
||||
func Local[R1, R2 any](f func(R2) R1) func(Kleisli[R1]) Kleisli[R2] {
|
||||
return reader.Local[Reader](f)
|
||||
}
|
||||
|
||||
// LocalL is similar to Local but uses a Lens to focus on a specific property.
|
||||
// A Lens is a functional programming construct that provides a composable way to
|
||||
// focus on a part of a data structure.
|
||||
//
|
||||
// This function is particularly useful when you want to focus a test on a specific
|
||||
// field of a struct using a lens, making the code more declarative and composable.
|
||||
// Lenses are often code-generated or predefined for common data structures.
|
||||
//
|
||||
// Parameters:
|
||||
// - l: A Lens that focuses from type S to type T
|
||||
//
|
||||
// Returns:
|
||||
// - A function that transforms a Reader[T, Reader] into a Reader[S, Reader]
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Person struct {
|
||||
// Name string
|
||||
// Email string
|
||||
// }
|
||||
//
|
||||
// // Assume we have a lens that focuses on the Email field
|
||||
// var emailLens = lens.Prop[Person, string]("Email")
|
||||
//
|
||||
// // Create an assertion for email format
|
||||
// validEmail := assert.That(func(email string) bool {
|
||||
// return strings.Contains(email, "@")
|
||||
// })
|
||||
//
|
||||
// // Focus this assertion on the Email property using a lens
|
||||
// validPersonEmail := assert.LocalL(emailLens)(validEmail)
|
||||
//
|
||||
// // Test a Person object
|
||||
// person := Person{Name: "Bob", Email: "bob@example.com"}
|
||||
// validPersonEmail(person)(t)
|
||||
//
|
||||
//go:inline
|
||||
func LocalL[S, T any](l Lens[S, T]) func(Kleisli[T]) Kleisli[S] {
|
||||
return reader.Local[Reader](l.Get)
|
||||
}
|
||||
|
||||
// fromOptionalGetter is an internal helper that creates an assertion Reader from
|
||||
// an optional getter function. It asserts that the optional value is present (Some).
|
||||
func fromOptionalGetter[S, T any](getter func(S) option.Option[T], msgAndArgs ...any) Kleisli[S] {
|
||||
return func(s S) Reader {
|
||||
return func(t *testing.T) bool {
|
||||
return assert.True(t, option.IsSome(getter(s)), msgAndArgs...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FromOptional creates an assertion that checks if an Optional can successfully extract a value.
|
||||
// An Optional is an optic that represents an optional reference to a subpart of a data structure.
|
||||
//
|
||||
// This function is useful when you have an Optional optic and want to assert that the optional
|
||||
// value is present (Some) rather than absent (None). The assertion passes if the Optional's
|
||||
// GetOption returns Some, and fails if it returns None.
|
||||
//
|
||||
// This enables property-focused testing where you verify that a particular optional field or
|
||||
// sub-structure exists and is accessible.
|
||||
//
|
||||
// Parameters:
|
||||
// - opt: An Optional optic that focuses from type S to type T
|
||||
//
|
||||
// Returns:
|
||||
// - A Reader that asserts the optional value is present when applied to a value of type S
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Config struct {
|
||||
// Database *DatabaseConfig // Optional field
|
||||
// }
|
||||
//
|
||||
// type DatabaseConfig struct {
|
||||
// Host string
|
||||
// Port int
|
||||
// }
|
||||
//
|
||||
// // Create an Optional that focuses on the Database field
|
||||
// dbOptional := optional.MakeOptional(
|
||||
// func(c Config) option.Option[*DatabaseConfig] {
|
||||
// if c.Database != nil {
|
||||
// return option.Some(c.Database)
|
||||
// }
|
||||
// return option.None[*DatabaseConfig]()
|
||||
// },
|
||||
// func(c Config, db *DatabaseConfig) Config {
|
||||
// c.Database = db
|
||||
// return c
|
||||
// },
|
||||
// )
|
||||
//
|
||||
// // Assert that the database config is present
|
||||
// hasDatabaseConfig := assert.FromOptional(dbOptional)
|
||||
//
|
||||
// config := Config{Database: &DatabaseConfig{Host: "localhost", Port: 5432}}
|
||||
// hasDatabaseConfig(config)(t) // Passes
|
||||
//
|
||||
// emptyConfig := Config{Database: nil}
|
||||
// hasDatabaseConfig(emptyConfig)(t) // Fails
|
||||
//
|
||||
//go:inline
|
||||
func FromOptional[S, T any](opt Optional[S, T]) reader.Reader[S, Reader] {
|
||||
return fromOptionalGetter(opt.GetOption, "Optional: %s", opt)
|
||||
}
|
||||
|
||||
// FromPrism creates an assertion that checks if a Prism can successfully extract a value.
|
||||
// A Prism is an optic used to select part of a sum type (tagged union or variant).
|
||||
//
|
||||
// This function is useful when you have a Prism optic and want to assert that a value
|
||||
// matches a specific variant of a sum type. The assertion passes if the Prism's GetOption
|
||||
// returns Some (meaning the value is of the expected variant), and fails if it returns None
|
||||
// (meaning the value is a different variant).
|
||||
//
|
||||
// This enables variant-focused testing where you verify that a value is of a particular
|
||||
// type or matches a specific condition within a sum type.
|
||||
//
|
||||
// Parameters:
|
||||
// - p: A Prism optic that focuses from type S to type T
|
||||
//
|
||||
// Returns:
|
||||
// - A Reader that asserts the prism successfully extracts when applied to a value of type S
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// type Result interface{ isResult() }
|
||||
// type Success struct{ Value int }
|
||||
// type Failure struct{ Error string }
|
||||
//
|
||||
// func (Success) isResult() {}
|
||||
// func (Failure) isResult() {}
|
||||
//
|
||||
// // Create a Prism that focuses on Success variant
|
||||
// successPrism := prism.MakePrism(
|
||||
// func(r Result) option.Option[int] {
|
||||
// if s, ok := r.(Success); ok {
|
||||
// return option.Some(s.Value)
|
||||
// }
|
||||
// return option.None[int]()
|
||||
// },
|
||||
// func(v int) Result { return Success{Value: v} },
|
||||
// )
|
||||
//
|
||||
// // Assert that the result is a Success
|
||||
// isSuccess := assert.FromPrism(successPrism)
|
||||
//
|
||||
// result1 := Success{Value: 42}
|
||||
// isSuccess(result1)(t) // Passes
|
||||
//
|
||||
// result2 := Failure{Error: "something went wrong"}
|
||||
// isSuccess(result2)(t) // Fails
|
||||
//
|
||||
//go:inline
|
||||
func FromPrism[S, T any](p Prism[S, T]) reader.Reader[S, Reader] {
|
||||
return fromOptionalGetter(p.GetOption, "Prism: %s", p)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/IBM/fp-go/v2/option"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
)
|
||||
|
||||
@@ -212,7 +213,7 @@ func TestError(t *testing.T) {
|
||||
|
||||
func TestSuccess(t *testing.T) {
|
||||
t.Run("should pass for successful result", func(t *testing.T) {
|
||||
res := result.Of[int](42)
|
||||
res := result.Of(42)
|
||||
result := Success(res)(t)
|
||||
if !result {
|
||||
t.Error("Expected Success to pass for successful result")
|
||||
@@ -240,7 +241,7 @@ func TestFailure(t *testing.T) {
|
||||
|
||||
t.Run("should fail for successful result", func(t *testing.T) {
|
||||
mockT := &testing.T{}
|
||||
res := result.Of[int](42)
|
||||
res := result.Of(42)
|
||||
result := Failure(res)(mockT)
|
||||
if result {
|
||||
t.Error("Expected Failure to fail for successful result")
|
||||
@@ -445,41 +446,163 @@ func TestEq(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegration(t *testing.T) {
|
||||
t.Run("complex assertion composition", func(t *testing.T) {
|
||||
type User struct {
|
||||
Name string
|
||||
Age int
|
||||
Email string
|
||||
func TestLocal(t *testing.T) {
|
||||
type User struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
t.Run("should focus assertion on a property", func(t *testing.T) {
|
||||
// Create an assertion that checks if age is positive
|
||||
ageIsPositive := That(func(age int) bool { return age > 0 })
|
||||
|
||||
// Focus this assertion on the Age field of User
|
||||
userAgeIsPositive := Local(func(u User) int { return u.Age })(ageIsPositive)
|
||||
|
||||
// Test with a user who has a positive age
|
||||
user := User{Name: "Alice", Age: 30}
|
||||
result := userAgeIsPositive(user)(t)
|
||||
if !result {
|
||||
t.Error("Expected focused assertion to pass for positive age")
|
||||
}
|
||||
})
|
||||
|
||||
user := User{Name: "Alice", Age: 30, Email: "alice@example.com"}
|
||||
t.Run("should fail when focused property doesn't match", func(t *testing.T) {
|
||||
mockT := &testing.T{}
|
||||
ageIsPositive := That(func(age int) bool { return age > 0 })
|
||||
userAgeIsPositive := Local(func(u User) int { return u.Age })(ageIsPositive)
|
||||
|
||||
// Test with a user who has zero age
|
||||
user := User{Name: "Bob", Age: 0}
|
||||
result := userAgeIsPositive(user)(mockT)
|
||||
if result {
|
||||
t.Error("Expected focused assertion to fail for zero age")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should compose with other assertions", func(t *testing.T) {
|
||||
// Create multiple focused assertions
|
||||
nameNotEmpty := Local(func(u User) string { return u.Name })(
|
||||
That(func(name string) bool { return len(name) > 0 }),
|
||||
)
|
||||
ageInRange := Local(func(u User) int { return u.Age })(
|
||||
That(func(age int) bool { return age >= 18 && age <= 100 }),
|
||||
)
|
||||
|
||||
user := User{Name: "Charlie", Age: 25}
|
||||
assertions := AllOf([]Reader{
|
||||
Equal("Alice")(user.Name),
|
||||
Equal(30)(user.Age),
|
||||
That(func(s string) bool { return len(s) > 0 })(user.Email),
|
||||
nameNotEmpty(user),
|
||||
ageInRange(user),
|
||||
})
|
||||
|
||||
result := assertions(t)
|
||||
if !result {
|
||||
t.Error("Expected complex assertion composition to pass")
|
||||
t.Error("Expected composed focused assertions to pass")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test suite with RunAll", func(t *testing.T) {
|
||||
data := []int{1, 2, 3, 4, 5}
|
||||
t.Run("should work with Equal assertion", func(t *testing.T) {
|
||||
// Focus Equal assertion on Name field
|
||||
nameIsAlice := Local(func(u User) string { return u.Name })(Equal("Alice"))
|
||||
|
||||
suite := RunAll(map[string]Reader{
|
||||
"not_empty": ArrayNotEmpty(data),
|
||||
"correct_size": ArrayLength[int](5)(data),
|
||||
"contains_one": ArrayContains(1)(data),
|
||||
"contains_five": ArrayContains(5)(data),
|
||||
})
|
||||
|
||||
result := suite(t)
|
||||
user := User{Name: "Alice", Age: 30}
|
||||
result := nameIsAlice(user)(t)
|
||||
if !result {
|
||||
t.Error("Expected test suite to pass")
|
||||
t.Error("Expected focused Equal assertion to pass")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLocalL(t *testing.T) {
|
||||
// Note: LocalL requires lens package which provides lens operations.
|
||||
// This test demonstrates the concept, but actual usage would require
|
||||
// proper lens definitions.
|
||||
|
||||
t.Run("conceptual test for LocalL", func(t *testing.T) {
|
||||
// LocalL is similar to Local but uses lenses for focusing.
|
||||
// It would be used like:
|
||||
// validEmail := That(func(email string) bool { return strings.Contains(email, "@") })
|
||||
// validPersonEmail := LocalL(emailLens)(validEmail)
|
||||
//
|
||||
// The actual implementation would require lens definitions from the lens package.
|
||||
// This test serves as documentation for the intended usage.
|
||||
})
|
||||
}
|
||||
|
||||
func TestFromOptional(t *testing.T) {
|
||||
type DatabaseConfig struct {
|
||||
Host string
|
||||
Port int
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Database *DatabaseConfig
|
||||
}
|
||||
|
||||
// Create an Optional that focuses on the Database field
|
||||
dbOptional := Optional[Config, *DatabaseConfig]{
|
||||
GetOption: func(c Config) option.Option[*DatabaseConfig] {
|
||||
if c.Database != nil {
|
||||
return option.Of(c.Database)
|
||||
}
|
||||
return option.None[*DatabaseConfig]()
|
||||
},
|
||||
Set: func(db *DatabaseConfig) func(Config) Config {
|
||||
return func(c Config) Config {
|
||||
c.Database = db
|
||||
return c
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("should pass when optional value is present", func(t *testing.T) {
|
||||
config := Config{Database: &DatabaseConfig{Host: "localhost", Port: 5432}}
|
||||
hasDatabaseConfig := FromOptional(dbOptional)
|
||||
result := hasDatabaseConfig(config)(t)
|
||||
if !result {
|
||||
t.Error("Expected FromOptional to pass when optional value is present")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should fail when optional value is absent", func(t *testing.T) {
|
||||
mockT := &testing.T{}
|
||||
emptyConfig := Config{Database: nil}
|
||||
hasDatabaseConfig := FromOptional(dbOptional)
|
||||
result := hasDatabaseConfig(emptyConfig)(mockT)
|
||||
if result {
|
||||
t.Error("Expected FromOptional to fail when optional value is absent")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should work with nested optionals", func(t *testing.T) {
|
||||
type AdvancedSettings struct {
|
||||
Cache bool
|
||||
}
|
||||
|
||||
type Settings struct {
|
||||
Advanced *AdvancedSettings
|
||||
}
|
||||
|
||||
advancedOptional := Optional[Settings, *AdvancedSettings]{
|
||||
GetOption: func(s Settings) option.Option[*AdvancedSettings] {
|
||||
if s.Advanced != nil {
|
||||
return option.Of(s.Advanced)
|
||||
}
|
||||
return option.None[*AdvancedSettings]()
|
||||
},
|
||||
Set: func(adv *AdvancedSettings) func(Settings) Settings {
|
||||
return func(s Settings) Settings {
|
||||
s.Advanced = adv
|
||||
return s
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
settings := Settings{Advanced: &AdvancedSettings{Cache: true}}
|
||||
hasAdvanced := FromOptional(advancedOptional)
|
||||
result := hasAdvanced(settings)(t)
|
||||
if !result {
|
||||
t.Error("Expected FromOptional to pass for nested optional")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,14 +3,20 @@ package assert
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/IBM/fp-go/v2/optics/lens"
|
||||
"github.com/IBM/fp-go/v2/optics/optional"
|
||||
"github.com/IBM/fp-go/v2/optics/prism"
|
||||
"github.com/IBM/fp-go/v2/predicate"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
)
|
||||
|
||||
type (
|
||||
Result[T any] = result.Result[T]
|
||||
Reader = reader.Reader[*testing.T, bool]
|
||||
Kleisli[T any] = reader.Reader[T, Reader]
|
||||
Predicate[T any] = predicate.Predicate[T]
|
||||
Result[T any] = result.Result[T]
|
||||
Reader = reader.Reader[*testing.T, bool]
|
||||
Kleisli[T any] = reader.Reader[T, Reader]
|
||||
Predicate[T any] = predicate.Predicate[T]
|
||||
Lens[S, T any] = lens.Lens[S, T]
|
||||
Optional[S, T any] = optional.Optional[S, T]
|
||||
Prism[S, T any] = prism.Prism[S, T]
|
||||
)
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package lens
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/IBM/fp-go/v2/endomorphism"
|
||||
EQ "github.com/IBM/fp-go/v2/eq"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
@@ -597,3 +599,11 @@ func IMap[S any, AB ~func(A) B, BA ~func(B) A, A, B any](ab AB, ba BA) Operator[
|
||||
return MakeLensCurried(F.Flow2(ea.Get, ab), F.Flow2(ba, ea.Set))
|
||||
}
|
||||
}
|
||||
|
||||
func (l Lens[S, T]) String() string {
|
||||
return "Lens"
|
||||
}
|
||||
|
||||
func (l Lens[S, T]) Format(f fmt.State, c rune) {
|
||||
fmt.Fprint(f, l.String())
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
package optional
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
EM "github.com/IBM/fp-go/v2/endomorphism"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
@@ -27,6 +29,7 @@ import (
|
||||
type Optional[S, A any] struct {
|
||||
GetOption func(s S) O.Option[A]
|
||||
Set func(a A) EM.Endomorphism[S]
|
||||
name string
|
||||
}
|
||||
|
||||
// setCopy wraps a setter for a pointer into a setter that first creates a copy before
|
||||
@@ -41,29 +44,42 @@ func setCopy[SET ~func(*S, A) *S, S, A any](setter SET) func(s *S, a A) *S {
|
||||
// MakeOptional creates an Optional based on a getter and a setter function. Make sure that the setter creates a (shallow) copy of the
|
||||
// data. This happens automatically if the data is passed by value. For pointers consider to use `MakeOptionalRef`
|
||||
// and for other kinds of data structures that are copied by reference make sure the setter creates the copy.
|
||||
//
|
||||
//go:inline
|
||||
func MakeOptional[S, A any](get func(S) O.Option[A], set func(S, A) S) Optional[S, A] {
|
||||
return Optional[S, A]{GetOption: get, Set: F.Bind2of2(set)}
|
||||
return MakeOptionalWithName(get, set, "GenericOptional")
|
||||
}
|
||||
|
||||
func MakeOptionalWithName[S, A any](get func(S) O.Option[A], set func(S, A) S, name string) Optional[S, A] {
|
||||
return Optional[S, A]{GetOption: get, Set: F.Bind2of2(set), name: name}
|
||||
}
|
||||
|
||||
// MakeOptionalRef creates an Optional based on a getter and a setter function. The setter passed in does not have to create a shallow
|
||||
// copy, the implementation wraps the setter into one that copies the pointer before modifying it
|
||||
//
|
||||
//go:inline
|
||||
func MakeOptionalRef[S, A any](get func(*S) O.Option[A], set func(*S, A) *S) Optional[*S, A] {
|
||||
return MakeOptional(get, setCopy(set))
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func MakeOptionalRefWithName[S, A any](get func(*S) O.Option[A], set func(*S, A) *S, name string) Optional[*S, A] {
|
||||
return MakeOptionalWithName(get, setCopy(set), name)
|
||||
}
|
||||
|
||||
// Id returns am optional implementing the identity operation
|
||||
func id[S any](creator func(get func(S) O.Option[S], set func(S, S) S) Optional[S, S]) Optional[S, S] {
|
||||
return creator(O.Some[S], F.Second[S, S])
|
||||
func idWithName[S any](creator func(get func(S) O.Option[S], set func(S, S) S, name string) Optional[S, S], name string) Optional[S, S] {
|
||||
return creator(O.Some[S], F.Second[S, S], name)
|
||||
}
|
||||
|
||||
// Id returns am optional implementing the identity operation
|
||||
func Id[S any]() Optional[S, S] {
|
||||
return id(MakeOptional[S, S])
|
||||
return idWithName(MakeOptionalWithName[S, S], "Identity")
|
||||
}
|
||||
|
||||
// Id returns am optional implementing the identity operation
|
||||
func IdRef[S any]() Optional[*S, *S] {
|
||||
return id(MakeOptionalRef[S, *S])
|
||||
return idWithName(MakeOptionalRefWithName[S, *S], "Identity")
|
||||
}
|
||||
|
||||
func optionalModifyOption[S, A any](f func(A) A, optional Optional[S, A], s S) O.Option[S] {
|
||||
@@ -189,3 +205,11 @@ func IChainAny[S, A any]() func(Optional[S, any]) Optional[S, A] {
|
||||
return ichain(sa, fromAny, toAny)
|
||||
}
|
||||
}
|
||||
|
||||
func (l Optional[S, T]) String() string {
|
||||
return l.name
|
||||
}
|
||||
|
||||
func (l Optional[S, T]) Format(f fmt.State, c rune) {
|
||||
fmt.Fprint(f, l.String())
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
package prism
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
EM "github.com/IBM/fp-go/v2/endomorphism"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
@@ -48,33 +50,19 @@ type (
|
||||
// },
|
||||
// func(v int) Result { return Success{Value: v} },
|
||||
// )
|
||||
Prism[S, A any] interface {
|
||||
Prism[S, A any] struct {
|
||||
// GetOption attempts to extract a value of type A from S.
|
||||
// Returns Some(a) if the extraction succeeds, None otherwise.
|
||||
GetOption(s S) Option[A]
|
||||
GetOption O.Kleisli[S, A]
|
||||
|
||||
// ReverseGet constructs an S from an A.
|
||||
// This operation always succeeds.
|
||||
ReverseGet(a A) S
|
||||
}
|
||||
ReverseGet func(A) S
|
||||
|
||||
// prismImpl is the internal implementation of the Prism interface.
|
||||
prismImpl[S, A any] struct {
|
||||
get func(S) Option[A]
|
||||
rev func(A) S
|
||||
name string
|
||||
}
|
||||
)
|
||||
|
||||
// GetOption implements the Prism interface for prismImpl.
|
||||
func (prism prismImpl[S, A]) GetOption(s S) Option[A] {
|
||||
return prism.get(s)
|
||||
}
|
||||
|
||||
// ReverseGet implements the Prism interface for prismImpl.
|
||||
func (prism prismImpl[S, A]) ReverseGet(a A) S {
|
||||
return prism.rev(a)
|
||||
}
|
||||
|
||||
// MakePrism constructs a Prism from GetOption and ReverseGet functions.
|
||||
//
|
||||
// Parameters:
|
||||
@@ -91,7 +79,7 @@ func (prism prismImpl[S, A]) ReverseGet(a A) S {
|
||||
// func(n int) Option[int] { return Some(n) },
|
||||
// )
|
||||
func MakePrism[S, A any](get func(S) Option[A], rev func(A) S) Prism[S, A] {
|
||||
return prismImpl[S, A]{get, rev}
|
||||
return Prism[S, A]{get, rev, "GenericPrism"}
|
||||
}
|
||||
|
||||
// Id returns an identity prism that focuses on the entire value.
|
||||
@@ -278,3 +266,11 @@ func IMap[S any, AB ~func(A) B, BA ~func(B) A, A, B any](ab AB, ba BA) func(Pris
|
||||
return imap(sa, ab, ba)
|
||||
}
|
||||
}
|
||||
|
||||
func (l Prism[S, T]) String() string {
|
||||
return "Prism"
|
||||
}
|
||||
|
||||
func (l Prism[S, T]) Format(f fmt.State, c rune) {
|
||||
fmt.Fprint(f, l.String())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user