mirror of
https://github.com/IBM/fp-go.git
synced 2026-04-11 15:29:06 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45cc0a7fc1 |
@@ -1510,3 +1510,8 @@ func Extend[A, B any](f func([]A) B) Operator[A, B] {
|
||||
func Extract[A any](as []A) A {
|
||||
return G.Extract(as)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func UpdateAt[T any](i int, v T) func([]T) Option[[]T] {
|
||||
return G.UpdateAt[[]T](i, v)
|
||||
}
|
||||
|
||||
@@ -489,3 +489,16 @@ func Extend[GA ~[]A, GB ~[]B, A, B any](f func(GA) B) func(GA) GB {
|
||||
return MakeBy[GB](len(as), func(i int) B { return f(as[i:]) })
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateAt[GT ~[]T, T any](i int, v T) func(GT) O.Option[GT] {
|
||||
none := O.None[GT]()
|
||||
if i < 0 {
|
||||
return F.Constant1[GT](none)
|
||||
}
|
||||
return func(g GT) O.Option[GT] {
|
||||
if i >= len(g) {
|
||||
return none
|
||||
}
|
||||
return O.Of(array.UnsafeUpdateAt(g, i, v))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
package array
|
||||
|
||||
import "slices"
|
||||
|
||||
func Of[GA ~[]A, A any](a A) GA {
|
||||
return GA{a}
|
||||
}
|
||||
@@ -197,3 +199,9 @@ func Reverse[GT ~[]T, T any](as GT) GT {
|
||||
}
|
||||
return ras
|
||||
}
|
||||
|
||||
func UnsafeUpdateAt[GT ~[]T, T any](as GT, i int, v T) GT {
|
||||
c := slices.Clone(as)
|
||||
c[i] = v
|
||||
return c
|
||||
}
|
||||
|
||||
23
v2/optics/iso/generic/iso.go
Normal file
23
v2/optics/iso/generic/iso.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package generic
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/internal/functor"
|
||||
I "github.com/IBM/fp-go/v2/optics/iso"
|
||||
)
|
||||
|
||||
// AsTraversal converts a iso to a traversal
|
||||
func AsTraversal[R ~func(func(A) HKTA) func(S) HKTS, S, A, HKTS, HKTA any](
|
||||
fmap functor.MapType[A, S, HKTA, HKTS],
|
||||
) func(I.Iso[S, A]) R {
|
||||
return func(sa I.Iso[S, A]) R {
|
||||
saSet := fmap(sa.ReverseGet)
|
||||
return func(f func(A) HKTA) func(S) HKTS {
|
||||
return F.Flow3(
|
||||
sa.Get,
|
||||
f,
|
||||
saSet,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,5 +23,5 @@ import (
|
||||
)
|
||||
|
||||
func AsTraversal[E, S, A any]() func(L.Lens[S, A]) T.Traversal[E, S, A] {
|
||||
return LG.AsTraversal[T.Traversal[E, S, A]](ET.MonadMap[E, A, S])
|
||||
return LG.AsTraversal[T.Traversal[E, S, A]](ET.Map[E, A, S])
|
||||
}
|
||||
|
||||
@@ -16,19 +16,24 @@
|
||||
package generic
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/internal/functor"
|
||||
L "github.com/IBM/fp-go/v2/optics/lens"
|
||||
)
|
||||
|
||||
// AsTraversal converts a lens to a traversal
|
||||
func AsTraversal[R ~func(func(A) HKTA) func(S) HKTS, S, A, HKTS, HKTA any](
|
||||
fmap func(HKTA, func(A) S) HKTS,
|
||||
fmap functor.MapType[A, S, HKTA, HKTS],
|
||||
) func(L.Lens[S, A]) R {
|
||||
return func(sa L.Lens[S, A]) R {
|
||||
return func(f func(a A) HKTA) func(S) HKTS {
|
||||
return func(f func(A) HKTA) func(S) HKTS {
|
||||
return func(s S) HKTS {
|
||||
return fmap(f(sa.Get(s)), func(a A) S {
|
||||
return sa.Set(a)(s)
|
||||
})
|
||||
return F.Pipe1(
|
||||
f(sa.Get(s)),
|
||||
fmap(func(a A) S {
|
||||
return sa.Set(a)(s)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,5 +60,5 @@ import (
|
||||
// configs := []Config{{Timeout: O.Some(30)}, {Timeout: O.None[int]()}}
|
||||
// // Apply operations across all configs using the traversal
|
||||
func AsTraversal[S, A any]() func(Lens[S, A]) T.Traversal[S, A] {
|
||||
return LG.AsTraversal[T.Traversal[S, A]](O.MonadMap[A, S])
|
||||
return LG.AsTraversal[T.Traversal[S, A]](O.Map[A, S])
|
||||
}
|
||||
|
||||
86
v2/optics/lens/traversal/generic/identity/traversal.go
Normal file
86
v2/optics/lens/traversal/generic/identity/traversal.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// 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 identity
|
||||
|
||||
import (
|
||||
I "github.com/IBM/fp-go/v2/identity"
|
||||
G "github.com/IBM/fp-go/v2/optics/lens/traversal/generic"
|
||||
)
|
||||
|
||||
// Compose composes a lens with a traversal to create a new traversal.
|
||||
//
|
||||
// This function allows you to focus deeper into a data structure by first using
|
||||
// a lens to access a field, then using a traversal to access multiple values within
|
||||
// that field. The result is a traversal that can operate on all the nested values.
|
||||
//
|
||||
// The composition follows the pattern: Lens[S, A] → Traversal[A, B] → Traversal[S, B]
|
||||
// where the lens focuses on field A within structure S, and the traversal focuses on
|
||||
// multiple B values within A.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - S: The outer structure type
|
||||
// - A: The intermediate field type (target of the lens)
|
||||
// - B: The final focus type (targets of the traversal)
|
||||
//
|
||||
// Parameters:
|
||||
// - t: A traversal that focuses on B values within A
|
||||
//
|
||||
// Returns:
|
||||
// - A function that takes a Lens[S, A] and returns a Traversal[S, B]
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import (
|
||||
// F "github.com/IBM/fp-go/v2/function"
|
||||
// "github.com/IBM/fp-go/v2/optics/lens"
|
||||
// LT "github.com/IBM/fp-go/v2/optics/lens/traversal"
|
||||
// AI "github.com/IBM/fp-go/v2/optics/traversal/array/identity"
|
||||
// )
|
||||
//
|
||||
// type Team struct {
|
||||
// Name string
|
||||
// Members []string
|
||||
// }
|
||||
//
|
||||
// // Lens to access the Members field
|
||||
// membersLens := lens.MakeLens(
|
||||
// func(t Team) []string { return t.Members },
|
||||
// func(t Team, m []string) Team { t.Members = m; return t },
|
||||
// )
|
||||
//
|
||||
// // Traversal for array elements
|
||||
// arrayTraversal := AI.FromArray[string]()
|
||||
//
|
||||
// // Compose lens with traversal to access all member names
|
||||
// memberTraversal := F.Pipe1(
|
||||
// membersLens,
|
||||
// LT.Compose[Team, []string, string](arrayTraversal),
|
||||
// )
|
||||
//
|
||||
// team := Team{Name: "Engineering", Members: []string{"Alice", "Bob"}}
|
||||
// // Uppercase all member names
|
||||
// updated := memberTraversal(strings.ToUpper)(team)
|
||||
// // updated.Members: ["ALICE", "BOB"]
|
||||
//
|
||||
// See Also:
|
||||
// - Lens: A functional reference to a subpart of a data structure
|
||||
// - Traversal: A functional reference to multiple subparts
|
||||
// - traversal.Compose: Composes two traversals
|
||||
func Compose[S, A, B any](t Traversal[A, B, A, B]) func(Lens[S, A]) Traversal[S, B, S, B] {
|
||||
return G.Compose[S, A, B, S, A, B](
|
||||
I.Map,
|
||||
)(t)
|
||||
}
|
||||
253
v2/optics/lens/traversal/generic/identity/traversal_test.go
Normal file
253
v2/optics/lens/traversal/generic/identity/traversal_test.go
Normal file
@@ -0,0 +1,253 @@
|
||||
// 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 identity
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
AR "github.com/IBM/fp-go/v2/array"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/optics/lens"
|
||||
AI "github.com/IBM/fp-go/v2/optics/traversal/array/identity"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type Team struct {
|
||||
Name string
|
||||
Members []string
|
||||
}
|
||||
|
||||
type Company struct {
|
||||
Name string
|
||||
Teams []Team
|
||||
}
|
||||
|
||||
func TestCompose_Success(t *testing.T) {
|
||||
t.Run("composes lens with array traversal to modify nested values", func(t *testing.T) {
|
||||
// Arrange
|
||||
membersLens := lens.MakeLens(
|
||||
func(team Team) []string { return team.Members },
|
||||
func(team Team, members []string) Team {
|
||||
team.Members = members
|
||||
return team
|
||||
},
|
||||
)
|
||||
arrayTraversal := AI.FromArray[string]()
|
||||
|
||||
memberTraversal := F.Pipe1(
|
||||
membersLens,
|
||||
Compose[Team](arrayTraversal),
|
||||
)
|
||||
|
||||
team := Team{
|
||||
Name: "Engineering",
|
||||
Members: []string{"alice", "bob", "charlie"},
|
||||
}
|
||||
|
||||
// Act - uppercase all member names
|
||||
result := memberTraversal(strings.ToUpper)(team)
|
||||
|
||||
// Assert
|
||||
expected := Team{
|
||||
Name: "Engineering",
|
||||
Members: []string{"ALICE", "BOB", "CHARLIE"},
|
||||
}
|
||||
assert.Equal(t, expected, result)
|
||||
})
|
||||
|
||||
t.Run("composes lens with array traversal on empty array", func(t *testing.T) {
|
||||
// Arrange
|
||||
membersLens := lens.MakeLens(
|
||||
func(team Team) []string { return team.Members },
|
||||
func(team Team, members []string) Team {
|
||||
team.Members = members
|
||||
return team
|
||||
},
|
||||
)
|
||||
arrayTraversal := AI.FromArray[string]()
|
||||
|
||||
memberTraversal := F.Pipe1(
|
||||
membersLens,
|
||||
Compose[Team](arrayTraversal),
|
||||
)
|
||||
|
||||
team := Team{
|
||||
Name: "Engineering",
|
||||
Members: []string{},
|
||||
}
|
||||
|
||||
// Act
|
||||
result := memberTraversal(strings.ToUpper)(team)
|
||||
|
||||
// Assert
|
||||
assert.Equal(t, team, result)
|
||||
})
|
||||
|
||||
t.Run("composes lens with array traversal to transform numbers", func(t *testing.T) {
|
||||
// Arrange
|
||||
type Stats struct {
|
||||
Name string
|
||||
Scores []int
|
||||
}
|
||||
|
||||
scoresLens := lens.MakeLens(
|
||||
func(s Stats) []int { return s.Scores },
|
||||
func(s Stats, scores []int) Stats {
|
||||
s.Scores = scores
|
||||
return s
|
||||
},
|
||||
)
|
||||
arrayTraversal := AI.FromArray[int]()
|
||||
|
||||
scoreTraversal := F.Pipe1(
|
||||
scoresLens,
|
||||
Compose[Stats, []int, int](arrayTraversal),
|
||||
)
|
||||
|
||||
stats := Stats{
|
||||
Name: "Player1",
|
||||
Scores: []int{10, 20, 30},
|
||||
}
|
||||
|
||||
// Act - double all scores
|
||||
result := scoreTraversal(func(n int) int { return n * 2 })(stats)
|
||||
|
||||
// Assert
|
||||
expected := Stats{
|
||||
Name: "Player1",
|
||||
Scores: []int{20, 40, 60},
|
||||
}
|
||||
assert.Equal(t, expected, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCompose_Integration(t *testing.T) {
|
||||
t.Run("composes multiple lenses and traversals", func(t *testing.T) {
|
||||
// Arrange - nested structure with Company -> Teams -> Members
|
||||
teamsLens := lens.MakeLens(
|
||||
func(c Company) []Team { return c.Teams },
|
||||
func(c Company, teams []Team) Company {
|
||||
c.Teams = teams
|
||||
return c
|
||||
},
|
||||
)
|
||||
|
||||
// First compose: Company -> []Team -> Team
|
||||
teamArrayTraversal := AI.FromArray[Team]()
|
||||
companyToTeamTraversal := F.Pipe1(
|
||||
teamsLens,
|
||||
Compose[Company, []Team, Team](teamArrayTraversal),
|
||||
)
|
||||
|
||||
// Second compose: Team -> []string -> string
|
||||
membersLens := lens.MakeLens(
|
||||
func(team Team) []string { return team.Members },
|
||||
func(team Team, members []string) Team {
|
||||
team.Members = members
|
||||
return team
|
||||
},
|
||||
)
|
||||
memberArrayTraversal := AI.FromArray[string]()
|
||||
teamToMemberTraversal := F.Pipe1(
|
||||
membersLens,
|
||||
Compose[Team](memberArrayTraversal),
|
||||
)
|
||||
|
||||
company := Company{
|
||||
Name: "TechCorp",
|
||||
Teams: []Team{
|
||||
{Name: "Engineering", Members: []string{"alice", "bob"}},
|
||||
{Name: "Design", Members: []string{"charlie", "diana"}},
|
||||
},
|
||||
}
|
||||
|
||||
// Act - uppercase all members in all teams
|
||||
// First traverse to teams, then for each team traverse to members
|
||||
result := companyToTeamTraversal(func(team Team) Team {
|
||||
return teamToMemberTraversal(strings.ToUpper)(team)
|
||||
})(company)
|
||||
|
||||
// Assert
|
||||
expected := Company{
|
||||
Name: "TechCorp",
|
||||
Teams: []Team{
|
||||
{Name: "Engineering", Members: []string{"ALICE", "BOB"}},
|
||||
{Name: "Design", Members: []string{"CHARLIE", "DIANA"}},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCompose_EdgeCases(t *testing.T) {
|
||||
t.Run("preserves structure name when modifying members", func(t *testing.T) {
|
||||
// Arrange
|
||||
membersLens := lens.MakeLens(
|
||||
func(team Team) []string { return team.Members },
|
||||
func(team Team, members []string) Team {
|
||||
team.Members = members
|
||||
return team
|
||||
},
|
||||
)
|
||||
arrayTraversal := AI.FromArray[string]()
|
||||
|
||||
memberTraversal := F.Pipe1(
|
||||
membersLens,
|
||||
Compose[Team](arrayTraversal),
|
||||
)
|
||||
|
||||
team := Team{
|
||||
Name: "Engineering",
|
||||
Members: []string{"alice"},
|
||||
}
|
||||
|
||||
// Act
|
||||
result := memberTraversal(strings.ToUpper)(team)
|
||||
|
||||
// Assert - Name should be unchanged
|
||||
assert.Equal(t, "Engineering", result.Name)
|
||||
assert.Equal(t, AR.From("ALICE"), result.Members)
|
||||
})
|
||||
|
||||
t.Run("handles identity transformation", func(t *testing.T) {
|
||||
// Arrange
|
||||
membersLens := lens.MakeLens(
|
||||
func(team Team) []string { return team.Members },
|
||||
func(team Team, members []string) Team {
|
||||
team.Members = members
|
||||
return team
|
||||
},
|
||||
)
|
||||
arrayTraversal := AI.FromArray[string]()
|
||||
|
||||
memberTraversal := F.Pipe1(
|
||||
membersLens,
|
||||
Compose[Team](arrayTraversal),
|
||||
)
|
||||
|
||||
team := Team{
|
||||
Name: "Engineering",
|
||||
Members: []string{"alice", "bob"},
|
||||
}
|
||||
|
||||
// Act - apply identity function
|
||||
result := memberTraversal(F.Identity[string])(team)
|
||||
|
||||
// Assert - should be unchanged
|
||||
assert.Equal(t, team, result)
|
||||
})
|
||||
}
|
||||
14
v2/optics/lens/traversal/generic/identity/types.go
Normal file
14
v2/optics/lens/traversal/generic/identity/types.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package identity
|
||||
|
||||
import (
|
||||
"github.com/IBM/fp-go/v2/optics/lens"
|
||||
T "github.com/IBM/fp-go/v2/optics/traversal"
|
||||
)
|
||||
|
||||
type (
|
||||
|
||||
// Lens is a functional reference to a subpart of a data structure.
|
||||
Lens[S, A any] = lens.Lens[S, A]
|
||||
|
||||
Traversal[S, A, HKTS, HKTA any] = T.Traversal[S, A, HKTS, HKTA]
|
||||
)
|
||||
25
v2/optics/lens/traversal/generic/traversal.go
Normal file
25
v2/optics/lens/traversal/generic/traversal.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package generic
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/internal/functor"
|
||||
G "github.com/IBM/fp-go/v2/optics/lens/generic"
|
||||
TG "github.com/IBM/fp-go/v2/optics/traversal/generic"
|
||||
)
|
||||
|
||||
func Compose[S, A, B, HKTS, HKTA, HKTB any](
|
||||
fmap functor.MapType[A, S, HKTA, HKTS],
|
||||
) func(Traversal[A, B, HKTA, HKTB]) func(Lens[S, A]) Traversal[S, B, HKTS, HKTB] {
|
||||
lensTrav := G.AsTraversal[Traversal[S, A, HKTS, HKTA]](fmap)
|
||||
|
||||
return func(ab Traversal[A, B, HKTA, HKTB]) func(Lens[S, A]) Traversal[S, B, HKTS, HKTB] {
|
||||
return F.Flow2(
|
||||
lensTrav,
|
||||
TG.Compose[
|
||||
Traversal[A, B, HKTA, HKTB],
|
||||
Traversal[S, A, HKTS, HKTA],
|
||||
Traversal[S, B, HKTS, HKTB],
|
||||
](ab),
|
||||
)
|
||||
}
|
||||
}
|
||||
14
v2/optics/lens/traversal/generic/types.go
Normal file
14
v2/optics/lens/traversal/generic/types.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package generic
|
||||
|
||||
import (
|
||||
"github.com/IBM/fp-go/v2/optics/lens"
|
||||
T "github.com/IBM/fp-go/v2/optics/traversal"
|
||||
)
|
||||
|
||||
type (
|
||||
|
||||
// Lens is a functional reference to a subpart of a data structure.
|
||||
Lens[S, A any] = lens.Lens[S, A]
|
||||
|
||||
Traversal[S, A, HKTS, HKTA any] = T.Traversal[S, A, HKTS, HKTA]
|
||||
)
|
||||
79
v2/optics/optional/array/array.go
Normal file
79
v2/optics/optional/array/array.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// 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 array
|
||||
|
||||
import (
|
||||
OP "github.com/IBM/fp-go/v2/optics/optional"
|
||||
G "github.com/IBM/fp-go/v2/optics/optional/array/generic"
|
||||
)
|
||||
|
||||
// At creates an Optional that focuses on the element at a specific index in an array.
|
||||
//
|
||||
// This function returns an Optional that can get and set the element at the given index.
|
||||
// If the index is out of bounds, GetOption returns None and Set operations are no-ops
|
||||
// (the array is returned unchanged). This follows the Optional laws where operations
|
||||
// on non-existent values have no effect.
|
||||
//
|
||||
// The Optional provides safe array access without panicking on invalid indices, making
|
||||
// it ideal for functional transformations where you want to modify array elements only
|
||||
// when they exist.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// Parameters:
|
||||
// - idx: The zero-based index to focus on
|
||||
//
|
||||
// Returns:
|
||||
// - An Optional that focuses on the element at the specified index
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import (
|
||||
// AR "github.com/IBM/fp-go/v2/array"
|
||||
// OP "github.com/IBM/fp-go/v2/optics/optional"
|
||||
// OA "github.com/IBM/fp-go/v2/optics/optional/array"
|
||||
// )
|
||||
//
|
||||
// numbers := []int{10, 20, 30, 40}
|
||||
//
|
||||
// // Create an optional focusing on index 1
|
||||
// second := OA.At[int](1)
|
||||
//
|
||||
// // Get the element at index 1
|
||||
// value := second.GetOption(numbers)
|
||||
// // value: option.Some(20)
|
||||
//
|
||||
// // Set the element at index 1
|
||||
// updated := second.Set(25)(numbers)
|
||||
// // updated: []int{10, 25, 30, 40}
|
||||
//
|
||||
// // Out of bounds access returns None
|
||||
// outOfBounds := OA.At[int](10)
|
||||
// value = outOfBounds.GetOption(numbers)
|
||||
// // value: option.None[int]()
|
||||
//
|
||||
// // Out of bounds set is a no-op
|
||||
// unchanged := outOfBounds.Set(99)(numbers)
|
||||
// // unchanged: []int{10, 20, 30, 40} (original array)
|
||||
//
|
||||
// See Also:
|
||||
// - AR.Lookup: Gets an element at an index, returning an Option
|
||||
// - AR.UpdateAt: Updates an element at an index, returning an Option
|
||||
// - OP.Optional: The Optional optic type
|
||||
func At[A any](idx int) OP.Optional[[]A, A] {
|
||||
return G.At[[]A](idx)
|
||||
}
|
||||
466
v2/optics/optional/array/array_test.go
Normal file
466
v2/optics/optional/array/array_test.go
Normal file
@@ -0,0 +1,466 @@
|
||||
// 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 array
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
EQ "github.com/IBM/fp-go/v2/eq"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestAt_GetOption tests the GetOption functionality
|
||||
func TestAt_GetOption(t *testing.T) {
|
||||
t.Run("returns Some for valid index", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30, 40}
|
||||
optional := At[int](1)
|
||||
|
||||
result := optional.GetOption(numbers)
|
||||
|
||||
assert.Equal(t, O.Some(20), result)
|
||||
})
|
||||
|
||||
t.Run("returns Some for first element", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30}
|
||||
optional := At[int](0)
|
||||
|
||||
result := optional.GetOption(numbers)
|
||||
|
||||
assert.Equal(t, O.Some(10), result)
|
||||
})
|
||||
|
||||
t.Run("returns Some for last element", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30}
|
||||
optional := At[int](2)
|
||||
|
||||
result := optional.GetOption(numbers)
|
||||
|
||||
assert.Equal(t, O.Some(30), result)
|
||||
})
|
||||
|
||||
t.Run("returns None for negative index", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30}
|
||||
optional := At[int](-1)
|
||||
|
||||
result := optional.GetOption(numbers)
|
||||
|
||||
assert.Equal(t, O.None[int](), result)
|
||||
})
|
||||
|
||||
t.Run("returns None for out of bounds index", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30}
|
||||
optional := At[int](10)
|
||||
|
||||
result := optional.GetOption(numbers)
|
||||
|
||||
assert.Equal(t, O.None[int](), result)
|
||||
})
|
||||
|
||||
t.Run("returns None for empty array", func(t *testing.T) {
|
||||
numbers := []int{}
|
||||
optional := At[int](0)
|
||||
|
||||
result := optional.GetOption(numbers)
|
||||
|
||||
assert.Equal(t, O.None[int](), result)
|
||||
})
|
||||
|
||||
t.Run("returns None for nil array", func(t *testing.T) {
|
||||
var numbers []int
|
||||
optional := At[int](0)
|
||||
|
||||
result := optional.GetOption(numbers)
|
||||
|
||||
assert.Equal(t, O.None[int](), result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestAt_Set tests the Set functionality
|
||||
func TestAt_Set(t *testing.T) {
|
||||
t.Run("updates element at valid index", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30, 40}
|
||||
optional := At[int](1)
|
||||
|
||||
result := optional.Set(25)(numbers)
|
||||
|
||||
assert.Equal(t, []int{10, 25, 30, 40}, result)
|
||||
assert.Equal(t, []int{10, 20, 30, 40}, numbers) // Original unchanged
|
||||
})
|
||||
|
||||
t.Run("updates first element", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30}
|
||||
optional := At[int](0)
|
||||
|
||||
result := optional.Set(5)(numbers)
|
||||
|
||||
assert.Equal(t, []int{5, 20, 30}, result)
|
||||
})
|
||||
|
||||
t.Run("updates last element", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30}
|
||||
optional := At[int](2)
|
||||
|
||||
result := optional.Set(35)(numbers)
|
||||
|
||||
assert.Equal(t, []int{10, 20, 35}, result)
|
||||
})
|
||||
|
||||
t.Run("is no-op for negative index", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30}
|
||||
optional := At[int](-1)
|
||||
|
||||
result := optional.Set(99)(numbers)
|
||||
|
||||
assert.Equal(t, numbers, result)
|
||||
})
|
||||
|
||||
t.Run("is no-op for out of bounds index", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30}
|
||||
optional := At[int](10)
|
||||
|
||||
result := optional.Set(99)(numbers)
|
||||
|
||||
assert.Equal(t, numbers, result)
|
||||
})
|
||||
|
||||
t.Run("is no-op for empty array", func(t *testing.T) {
|
||||
numbers := []int{}
|
||||
optional := At[int](0)
|
||||
|
||||
result := optional.Set(99)(numbers)
|
||||
|
||||
assert.Equal(t, numbers, result)
|
||||
})
|
||||
|
||||
t.Run("is no-op for nil array", func(t *testing.T) {
|
||||
var numbers []int
|
||||
optional := At[int](0)
|
||||
|
||||
result := optional.Set(99)(numbers)
|
||||
|
||||
assert.Equal(t, numbers, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestAt_OptionalLaw1_GetSetNoOp tests Optional Law 1: GetSet Law (No-op on None)
|
||||
// If GetOption(s) returns None, then Set(a)(s) must return s unchanged (no-op).
|
||||
func TestAt_OptionalLaw1_GetSetNoOp(t *testing.T) {
|
||||
t.Run("out of bounds index - set is no-op", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30}
|
||||
optional := At[int](10)
|
||||
|
||||
// Verify GetOption returns None
|
||||
assert.Equal(t, O.None[int](), optional.GetOption(numbers))
|
||||
|
||||
// Set should be a no-op
|
||||
result := optional.Set(99)(numbers)
|
||||
assert.Equal(t, numbers, result)
|
||||
})
|
||||
|
||||
t.Run("negative index - set is no-op", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30}
|
||||
optional := At[int](-1)
|
||||
|
||||
// Verify GetOption returns None
|
||||
assert.Equal(t, O.None[int](), optional.GetOption(numbers))
|
||||
|
||||
// Set should be a no-op
|
||||
result := optional.Set(99)(numbers)
|
||||
assert.Equal(t, numbers, result)
|
||||
})
|
||||
|
||||
t.Run("empty array - set is no-op", func(t *testing.T) {
|
||||
numbers := []int{}
|
||||
optional := At[int](0)
|
||||
|
||||
// Verify GetOption returns None
|
||||
assert.Equal(t, O.None[int](), optional.GetOption(numbers))
|
||||
|
||||
// Set should be a no-op
|
||||
result := optional.Set(99)(numbers)
|
||||
assert.Equal(t, numbers, result)
|
||||
})
|
||||
|
||||
t.Run("nil array - set is no-op", func(t *testing.T) {
|
||||
var numbers []int
|
||||
optional := At[int](0)
|
||||
|
||||
// Verify GetOption returns None
|
||||
assert.Equal(t, O.None[int](), optional.GetOption(numbers))
|
||||
|
||||
// Set should be a no-op
|
||||
result := optional.Set(99)(numbers)
|
||||
assert.Equal(t, numbers, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestAt_OptionalLaw2_SetGet tests Optional Law 2: SetGet Law (Get what you Set)
|
||||
// If GetOption(s) returns Some(_), then GetOption(Set(a)(s)) must return Some(a).
|
||||
func TestAt_OptionalLaw2_SetGet(t *testing.T) {
|
||||
t.Run("set then get returns the set value", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30, 40}
|
||||
optional := At[int](1)
|
||||
|
||||
// Verify GetOption returns Some (precondition)
|
||||
assert.True(t, O.IsSome(optional.GetOption(numbers)))
|
||||
|
||||
// Set a new value
|
||||
newValue := 25
|
||||
updated := optional.Set(newValue)(numbers)
|
||||
|
||||
// GetOption on updated should return Some(newValue)
|
||||
result := optional.GetOption(updated)
|
||||
assert.Equal(t, O.Some(newValue), result)
|
||||
})
|
||||
|
||||
t.Run("set first element then get", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30}
|
||||
optional := At[int](0)
|
||||
|
||||
assert.True(t, O.IsSome(optional.GetOption(numbers)))
|
||||
|
||||
newValue := 5
|
||||
updated := optional.Set(newValue)(numbers)
|
||||
|
||||
result := optional.GetOption(updated)
|
||||
assert.Equal(t, O.Some(newValue), result)
|
||||
})
|
||||
|
||||
t.Run("set last element then get", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30}
|
||||
optional := At[int](2)
|
||||
|
||||
assert.True(t, O.IsSome(optional.GetOption(numbers)))
|
||||
|
||||
newValue := 35
|
||||
updated := optional.Set(newValue)(numbers)
|
||||
|
||||
result := optional.GetOption(updated)
|
||||
assert.Equal(t, O.Some(newValue), result)
|
||||
})
|
||||
|
||||
t.Run("multiple indices satisfy law", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30, 40, 50}
|
||||
|
||||
for i := range 5 {
|
||||
optional := At[int](i)
|
||||
|
||||
assert.True(t, O.IsSome(optional.GetOption(numbers)))
|
||||
|
||||
newValue := i * 100
|
||||
updated := optional.Set(newValue)(numbers)
|
||||
|
||||
result := optional.GetOption(updated)
|
||||
assert.Equal(t, O.Some(newValue), result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestAt_OptionalLaw3_SetSet tests Optional Law 3: SetSet Law (Last Set Wins)
|
||||
// Setting twice is the same as setting once with the final value.
|
||||
// Formally: Set(b)(Set(a)(s)) = Set(b)(s)
|
||||
func TestAt_OptionalLaw3_SetSet(t *testing.T) {
|
||||
eqSlice := EQ.FromEquals(func(a, b []int) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range len(a) {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
t.Run("setting twice equals setting once with final value", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30, 40}
|
||||
optional := At[int](1)
|
||||
|
||||
// Set twice: first to 25, then to 99
|
||||
setTwice := F.Pipe2(
|
||||
numbers,
|
||||
optional.Set(25),
|
||||
optional.Set(99),
|
||||
)
|
||||
|
||||
// Set once with final value
|
||||
setOnce := optional.Set(99)(numbers)
|
||||
|
||||
assert.True(t, eqSlice.Equals(setTwice, setOnce))
|
||||
})
|
||||
|
||||
t.Run("multiple sets - last one wins", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30}
|
||||
optional := At[int](0)
|
||||
|
||||
// Set multiple times
|
||||
result := F.Pipe4(
|
||||
numbers,
|
||||
optional.Set(1),
|
||||
optional.Set(2),
|
||||
optional.Set(3),
|
||||
optional.Set(4),
|
||||
)
|
||||
|
||||
// Should equal setting once with final value
|
||||
expected := optional.Set(4)(numbers)
|
||||
|
||||
assert.True(t, eqSlice.Equals(result, expected))
|
||||
})
|
||||
|
||||
t.Run("set twice on out of bounds - both no-ops", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30}
|
||||
optional := At[int](10)
|
||||
|
||||
// Set twice on out of bounds
|
||||
setTwice := F.Pipe2(
|
||||
numbers,
|
||||
optional.Set(25),
|
||||
optional.Set(99),
|
||||
)
|
||||
|
||||
// Set once on out of bounds
|
||||
setOnce := optional.Set(99)(numbers)
|
||||
|
||||
// Both should be no-ops, returning original
|
||||
assert.True(t, eqSlice.Equals(setTwice, numbers))
|
||||
assert.True(t, eqSlice.Equals(setOnce, numbers))
|
||||
assert.True(t, eqSlice.Equals(setTwice, setOnce))
|
||||
})
|
||||
}
|
||||
|
||||
// TestAt_EdgeCases tests edge cases and boundary conditions
|
||||
func TestAt_EdgeCases(t *testing.T) {
|
||||
t.Run("single element array", func(t *testing.T) {
|
||||
numbers := []int{42}
|
||||
optional := At[int](0)
|
||||
|
||||
// Get
|
||||
assert.Equal(t, O.Some(42), optional.GetOption(numbers))
|
||||
|
||||
// Set
|
||||
updated := optional.Set(99)(numbers)
|
||||
assert.Equal(t, []int{99}, updated)
|
||||
|
||||
// Out of bounds
|
||||
outOfBounds := At[int](1)
|
||||
assert.Equal(t, O.None[int](), outOfBounds.GetOption(numbers))
|
||||
assert.Equal(t, numbers, outOfBounds.Set(99)(numbers))
|
||||
})
|
||||
|
||||
t.Run("large array", func(t *testing.T) {
|
||||
numbers := make([]int, 1000)
|
||||
for i := range 1000 {
|
||||
numbers[i] = i
|
||||
}
|
||||
|
||||
optional := At[int](500)
|
||||
|
||||
// Get
|
||||
assert.Equal(t, O.Some(500), optional.GetOption(numbers))
|
||||
|
||||
// Set
|
||||
updated := optional.Set(9999)(numbers)
|
||||
assert.Equal(t, 9999, updated[500])
|
||||
assert.Equal(t, 500, numbers[500]) // Original unchanged
|
||||
})
|
||||
|
||||
t.Run("works with different types", func(t *testing.T) {
|
||||
// String array
|
||||
strings := []string{"a", "b", "c"}
|
||||
strOptional := At[string](1)
|
||||
assert.Equal(t, O.Some("b"), strOptional.GetOption(strings))
|
||||
assert.Equal(t, []string{"a", "x", "c"}, strOptional.Set("x")(strings))
|
||||
|
||||
// Bool array
|
||||
bools := []bool{true, false, true}
|
||||
boolOptional := At[bool](1)
|
||||
assert.Equal(t, O.Some(false), boolOptional.GetOption(bools))
|
||||
assert.Equal(t, []bool{true, true, true}, boolOptional.Set(true)(bools))
|
||||
})
|
||||
|
||||
t.Run("preserves array capacity", func(t *testing.T) {
|
||||
numbers := make([]int, 3, 10)
|
||||
numbers[0], numbers[1], numbers[2] = 10, 20, 30
|
||||
|
||||
optional := At[int](1)
|
||||
updated := optional.Set(25)(numbers)
|
||||
|
||||
assert.Equal(t, []int{10, 25, 30}, updated)
|
||||
assert.Equal(t, 3, len(updated))
|
||||
})
|
||||
}
|
||||
|
||||
// TestAt_Integration tests integration scenarios
|
||||
func TestAt_Integration(t *testing.T) {
|
||||
t.Run("multiple optionals on same array", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30, 40}
|
||||
|
||||
first := At[int](0)
|
||||
second := At[int](1)
|
||||
third := At[int](2)
|
||||
|
||||
// Update multiple indices
|
||||
result := F.Pipe3(
|
||||
numbers,
|
||||
first.Set(1),
|
||||
second.Set(2),
|
||||
third.Set(3),
|
||||
)
|
||||
|
||||
assert.Equal(t, []int{1, 2, 3, 40}, result)
|
||||
assert.Equal(t, []int{10, 20, 30, 40}, numbers) // Original unchanged
|
||||
})
|
||||
|
||||
t.Run("chaining operations", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30}
|
||||
optional := At[int](1)
|
||||
|
||||
// Get, verify, set, get again
|
||||
original := optional.GetOption(numbers)
|
||||
assert.Equal(t, O.Some(20), original)
|
||||
|
||||
updated := optional.Set(25)(numbers)
|
||||
newValue := optional.GetOption(updated)
|
||||
assert.Equal(t, O.Some(25), newValue)
|
||||
|
||||
// Original still unchanged
|
||||
assert.Equal(t, O.Some(20), optional.GetOption(numbers))
|
||||
})
|
||||
|
||||
t.Run("conditional update based on current value", func(t *testing.T) {
|
||||
numbers := []int{10, 20, 30}
|
||||
optional := At[int](1)
|
||||
|
||||
// Get current value and conditionally update
|
||||
result := F.Pipe1(
|
||||
optional.GetOption(numbers),
|
||||
O.Fold(
|
||||
func() []int { return numbers },
|
||||
func(current int) []int {
|
||||
if current > 15 {
|
||||
return optional.Set(current * 2)(numbers)
|
||||
}
|
||||
return numbers
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert.Equal(t, []int{10, 40, 30}, result)
|
||||
})
|
||||
}
|
||||
98
v2/optics/optional/array/generic/array.go
Normal file
98
v2/optics/optional/array/generic/array.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// 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 generic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
AR "github.com/IBM/fp-go/v2/array/generic"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/lazy"
|
||||
OP "github.com/IBM/fp-go/v2/optics/optional"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
)
|
||||
|
||||
// At creates an Optional that focuses on the element at a specific index in an array.
|
||||
//
|
||||
// This function returns an Optional that can get and set the element at the given index.
|
||||
// If the index is out of bounds, GetOption returns None and Set operations are no-ops
|
||||
// (the array is returned unchanged). This follows the Optional laws where operations
|
||||
// on non-existent values have no effect.
|
||||
//
|
||||
// The Optional provides safe array access without panicking on invalid indices, making
|
||||
// it ideal for functional transformations where you want to modify array elements only
|
||||
// when they exist.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// Parameters:
|
||||
// - idx: The zero-based index to focus on
|
||||
//
|
||||
// Returns:
|
||||
// - An Optional that focuses on the element at the specified index
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import (
|
||||
// AR "github.com/IBM/fp-go/v2/array"
|
||||
// OP "github.com/IBM/fp-go/v2/optics/optional"
|
||||
// OA "github.com/IBM/fp-go/v2/optics/optional/array"
|
||||
// )
|
||||
//
|
||||
// numbers := []int{10, 20, 30, 40}
|
||||
//
|
||||
// // Create an optional focusing on index 1
|
||||
// second := OA.At[int](1)
|
||||
//
|
||||
// // Get the element at index 1
|
||||
// value := second.GetOption(numbers)
|
||||
// // value: option.Some(20)
|
||||
//
|
||||
// // Set the element at index 1
|
||||
// updated := second.Set(25)(numbers)
|
||||
// // updated: []int{10, 25, 30, 40}
|
||||
//
|
||||
// // Out of bounds access returns None
|
||||
// outOfBounds := OA.At[int](10)
|
||||
// value = outOfBounds.GetOption(numbers)
|
||||
// // value: option.None[int]()
|
||||
//
|
||||
// // Out of bounds set is a no-op
|
||||
// unchanged := outOfBounds.Set(99)(numbers)
|
||||
// // unchanged: []int{10, 20, 30, 40} (original array)
|
||||
//
|
||||
// See Also:
|
||||
// - AR.Lookup: Gets an element at an index, returning an Option
|
||||
// - AR.UpdateAt: Updates an element at an index, returning an Option
|
||||
// - OP.Optional: The Optional optic type
|
||||
func At[GA ~[]A, A any](idx int) OP.Optional[GA, A] {
|
||||
lookup := AR.Lookup[GA](idx)
|
||||
return OP.MakeOptionalCurriedWithName(
|
||||
lookup,
|
||||
func(a A) func(GA) GA {
|
||||
update := AR.UpdateAt[GA](idx, a)
|
||||
return func(as GA) GA {
|
||||
return F.Pipe2(
|
||||
as,
|
||||
update,
|
||||
O.GetOrElse(lazy.Of(as)),
|
||||
)
|
||||
}
|
||||
},
|
||||
fmt.Sprintf("At[%d]", idx),
|
||||
)
|
||||
}
|
||||
34
v2/optics/optional/traversal.go
Normal file
34
v2/optics/optional/traversal.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package optional
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/internal/functor"
|
||||
"github.com/IBM/fp-go/v2/internal/pointed"
|
||||
"github.com/IBM/fp-go/v2/lazy"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
)
|
||||
|
||||
func AsTraversal[R ~func(func(A) HKTA) func(S) HKTS, S, A, HKTS, HKTA any](
|
||||
fof pointed.OfType[S, HKTS],
|
||||
fmap functor.MapType[A, S, HKTA, HKTS],
|
||||
) func(Optional[S, A]) R {
|
||||
return func(sa Optional[S, A]) R {
|
||||
return func(f func(A) HKTA) func(S) HKTS {
|
||||
return func(s S) HKTS {
|
||||
return F.Pipe2(
|
||||
s,
|
||||
sa.GetOption,
|
||||
O.Fold(
|
||||
lazy.Of(fof(s)),
|
||||
F.Flow2(
|
||||
f,
|
||||
fmap(func(a A) S {
|
||||
return sa.Set(a)(s)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -310,8 +310,10 @@ func TestAsTraversal(t *testing.T) {
|
||||
return Identity[Option[int]]{Value: s}
|
||||
}
|
||||
|
||||
fmap := func(ia Identity[int], f func(int) Option[int]) Identity[Option[int]] {
|
||||
return Identity[Option[int]]{Value: f(ia.Value)}
|
||||
fmap := func(f func(int) Option[int]) func(Identity[int]) Identity[Option[int]] {
|
||||
return func(ia Identity[int]) Identity[Option[int]] {
|
||||
return Identity[Option[int]]{Value: f(ia.Value)}
|
||||
}
|
||||
}
|
||||
|
||||
type TraversalFunc func(func(int) Identity[int]) func(Option[int]) Identity[Option[int]]
|
||||
|
||||
@@ -17,6 +17,9 @@ package prism
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/internal/functor"
|
||||
"github.com/IBM/fp-go/v2/internal/pointed"
|
||||
"github.com/IBM/fp-go/v2/lazy"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
)
|
||||
|
||||
@@ -58,24 +61,23 @@ import (
|
||||
// higher-kinded types and applicative functors. Most users will work
|
||||
// directly with prisms rather than converting them to traversals.
|
||||
func AsTraversal[R ~func(func(A) HKTA) func(S) HKTS, S, A, HKTS, HKTA any](
|
||||
fof func(S) HKTS,
|
||||
fmap func(HKTA, func(A) S) HKTS,
|
||||
fof pointed.OfType[S, HKTS],
|
||||
fmap functor.MapType[A, S, HKTA, HKTS],
|
||||
) func(Prism[S, A]) R {
|
||||
return func(sa Prism[S, A]) R {
|
||||
return func(f func(a A) HKTA) func(S) HKTS {
|
||||
return func(f func(A) HKTA) func(S) HKTS {
|
||||
return func(s S) HKTS {
|
||||
return F.Pipe2(
|
||||
s,
|
||||
sa.GetOption,
|
||||
O.Fold(
|
||||
// If prism doesn't match, return the original value lifted into HKTS
|
||||
F.Nullary2(F.Constant(s), fof),
|
||||
// If prism matches, apply f to the extracted value and map back
|
||||
func(a A) HKTS {
|
||||
return fmap(f(a), func(a A) S {
|
||||
return prismModify(F.Constant1[A](a), sa, s)
|
||||
})
|
||||
},
|
||||
lazy.Of(fof(s)),
|
||||
F.Flow2(
|
||||
f,
|
||||
fmap(func(a A) S {
|
||||
return Set[S](a)(sa)(s)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,6 @@ import (
|
||||
)
|
||||
|
||||
// FromArray returns a traversal from an array for the identity [Monoid]
|
||||
func FromArray[E, A any](m M.Monoid[E]) G.Traversal[[]A, A, C.Const[E, []A], C.Const[E, A]] {
|
||||
func FromArray[A, E any](m M.Monoid[E]) G.Traversal[[]A, A, C.Const[E, []A], C.Const[E, A]] {
|
||||
return AR.FromArray[[]A](m)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,51 @@ import (
|
||||
G "github.com/IBM/fp-go/v2/optics/traversal/generic"
|
||||
)
|
||||
|
||||
// FromArray returns a traversal from an array for the identity monad
|
||||
// FromArray creates a traversal for array elements using the Identity functor.
|
||||
//
|
||||
// This is a specialized version of the generic FromArray that uses the Identity
|
||||
// functor, which provides the simplest possible computational context (no context).
|
||||
// This makes it ideal for straightforward array transformations where you want to
|
||||
// modify elements directly without additional effects.
|
||||
//
|
||||
// The Identity functor means that operations are applied directly to values without
|
||||
// wrapping them in any additional structure. This results in clean, efficient
|
||||
// traversals that simply map functions over array elements.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - GA: Array type constraint (e.g., []A)
|
||||
// - A: The element type within the array
|
||||
//
|
||||
// Returns:
|
||||
// - A Traversal that can transform all elements in an array
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import (
|
||||
// F "github.com/IBM/fp-go/v2/function"
|
||||
// T "github.com/IBM/fp-go/v2/optics/traversal"
|
||||
// TI "github.com/IBM/fp-go/v2/optics/traversal/array/generic/identity"
|
||||
// )
|
||||
//
|
||||
// // Create a traversal for integer arrays
|
||||
// arrayTraversal := TI.FromArray[[]int, int]()
|
||||
//
|
||||
// // Compose with identity traversal
|
||||
// traversal := F.Pipe1(
|
||||
// T.Id[[]int, []int](),
|
||||
// T.Compose[[]int, []int, []int, int](arrayTraversal),
|
||||
// )
|
||||
//
|
||||
// // Double all numbers in the array
|
||||
// numbers := []int{1, 2, 3, 4, 5}
|
||||
// doubled := traversal(func(n int) int { return n * 2 })(numbers)
|
||||
// // doubled: []int{2, 4, 6, 8, 10}
|
||||
//
|
||||
// See Also:
|
||||
// - AR.FromArray: Generic version with configurable functor
|
||||
// - I.Of: Identity functor's pure/of operation
|
||||
// - I.Map: Identity functor's map operation
|
||||
// - I.Ap: Identity functor's applicative operation
|
||||
func FromArray[GA ~[]A, A any]() G.Traversal[GA, A, GA, A] {
|
||||
return AR.FromArray[GA](
|
||||
I.Of[GA],
|
||||
@@ -29,3 +73,75 @@ func FromArray[GA ~[]A, A any]() G.Traversal[GA, A, GA, A] {
|
||||
I.Ap[GA, A],
|
||||
)
|
||||
}
|
||||
|
||||
// At creates a function that focuses a traversal on a specific array index using the Identity functor.
|
||||
//
|
||||
// This is a specialized version of the generic At that uses the Identity functor,
|
||||
// providing the simplest computational context for array element access. It transforms
|
||||
// a traversal focusing on an array into a traversal focusing on the element at the
|
||||
// specified index.
|
||||
//
|
||||
// The Identity functor means operations are applied directly without additional wrapping,
|
||||
// making this ideal for straightforward element modifications. If the index is out of
|
||||
// bounds, the traversal focuses on zero elements (no-op).
|
||||
//
|
||||
// Type Parameters:
|
||||
// - GA: Array type constraint (e.g., []A)
|
||||
// - S: The source type of the outer traversal
|
||||
// - A: The element type within the array
|
||||
//
|
||||
// Parameters:
|
||||
// - idx: The zero-based index to focus on
|
||||
//
|
||||
// Returns:
|
||||
// - A function that transforms a traversal on arrays into a traversal on a specific element
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import (
|
||||
// F "github.com/IBM/fp-go/v2/function"
|
||||
// T "github.com/IBM/fp-go/v2/optics/traversal"
|
||||
// TI "github.com/IBM/fp-go/v2/optics/traversal/array/generic/identity"
|
||||
// )
|
||||
//
|
||||
// type Person struct {
|
||||
// Name string
|
||||
// Hobbies []string
|
||||
// }
|
||||
//
|
||||
// // Create a traversal focusing on hobbies
|
||||
// hobbiesTraversal := T.Id[Person, []string]()
|
||||
//
|
||||
// // Focus on the second hobby (index 1)
|
||||
// secondHobby := F.Pipe1(
|
||||
// hobbiesTraversal,
|
||||
// TI.At[[]string, Person, string](1),
|
||||
// )
|
||||
//
|
||||
// // Modify the second hobby
|
||||
// person := Person{Name: "Alice", Hobbies: []string{"reading", "coding", "gaming"}}
|
||||
// updated := secondHobby(func(s string) string {
|
||||
// return s + "!"
|
||||
// })(person)
|
||||
// // updated.Hobbies: []string{"reading", "coding!", "gaming"}
|
||||
//
|
||||
// // Out of bounds index is a no-op
|
||||
// outOfBounds := F.Pipe1(
|
||||
// hobbiesTraversal,
|
||||
// TI.At[[]string, Person, string](10),
|
||||
// )
|
||||
// unchanged := outOfBounds(func(s string) string {
|
||||
// return s + "!"
|
||||
// })(person)
|
||||
// // unchanged.Hobbies: []string{"reading", "coding", "gaming"} (no change)
|
||||
//
|
||||
// See Also:
|
||||
// - AR.At: Generic version with configurable functor
|
||||
// - I.Of: Identity functor's pure/of operation
|
||||
// - I.Map: Identity functor's map operation
|
||||
func At[GA ~[]A, S, A any](idx int) func(G.Traversal[S, GA, S, GA]) G.Traversal[S, A, S, A] {
|
||||
return AR.At[GA, S, A, S](
|
||||
I.Of[GA],
|
||||
I.Map[A, GA],
|
||||
)(idx)
|
||||
}
|
||||
|
||||
@@ -16,19 +16,105 @@
|
||||
package generic
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/internal/apply"
|
||||
AR "github.com/IBM/fp-go/v2/internal/array"
|
||||
"github.com/IBM/fp-go/v2/internal/functor"
|
||||
"github.com/IBM/fp-go/v2/internal/pointed"
|
||||
"github.com/IBM/fp-go/v2/optics/optional"
|
||||
OA "github.com/IBM/fp-go/v2/optics/optional/array/generic"
|
||||
G "github.com/IBM/fp-go/v2/optics/traversal/generic"
|
||||
)
|
||||
|
||||
// FromArray returns a traversal from an array
|
||||
func FromArray[GA ~[]A, GB ~[]B, A, B, HKTB, HKTAB, HKTRB any](
|
||||
fof func(GB) HKTRB,
|
||||
fmap func(func(GB) func(B) GB) func(HKTRB) HKTAB,
|
||||
fap func(HKTB) func(HKTAB) HKTRB,
|
||||
fof pointed.OfType[GB, HKTRB],
|
||||
fmap functor.MapType[GB, func(B) GB, HKTRB, HKTAB],
|
||||
fap apply.ApType[HKTB, HKTRB, HKTAB],
|
||||
) G.Traversal[GA, A, HKTRB, HKTB] {
|
||||
return func(f func(A) HKTB) func(s GA) HKTRB {
|
||||
return func(s GA) HKTRB {
|
||||
return AR.MonadTraverse(fof, fmap, fap, s, f)
|
||||
}
|
||||
return func(f func(A) HKTB) func(GA) HKTRB {
|
||||
return AR.Traverse[GA](fof, fmap, fap, f)
|
||||
}
|
||||
}
|
||||
|
||||
// At creates a function that focuses a traversal on a specific array index.
|
||||
//
|
||||
// This function takes an index and returns a function that transforms a traversal
|
||||
// focusing on an array into a traversal focusing on the element at that index.
|
||||
// It works by:
|
||||
// 1. Creating an Optional that focuses on the array element at the given index
|
||||
// 2. Converting that Optional into a Traversal
|
||||
// 3. Composing it with the original traversal
|
||||
//
|
||||
// If the index is out of bounds, the traversal will focus on zero elements (no-op),
|
||||
// following the Optional laws where operations on non-existent values have no effect.
|
||||
//
|
||||
// This is particularly useful when you have a nested structure containing arrays
|
||||
// and want to traverse to a specific element within those arrays.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - GA: Array type constraint (e.g., []A)
|
||||
// - S: The source type of the outer traversal
|
||||
// - A: The element type within the array
|
||||
// - HKTS: Higher-kinded type for S (functor/applicative context)
|
||||
// - HKTGA: Higher-kinded type for GA (functor/applicative context)
|
||||
// - HKTA: Higher-kinded type for A (functor/applicative context)
|
||||
//
|
||||
// Parameters:
|
||||
// - fof: Function to lift GA into the higher-kinded type HKTGA (pure/of operation)
|
||||
// - fmap: Function to map over HKTA and produce HKTGA (functor map operation)
|
||||
//
|
||||
// Returns:
|
||||
// - A function that takes an index and returns a traversal transformer
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import (
|
||||
// F "github.com/IBM/fp-go/v2/function"
|
||||
// "github.com/IBM/fp-go/v2/identity"
|
||||
// T "github.com/IBM/fp-go/v2/optics/traversal"
|
||||
// TA "github.com/IBM/fp-go/v2/optics/traversal/array/generic"
|
||||
// )
|
||||
//
|
||||
// type Person struct {
|
||||
// Name string
|
||||
// Hobbies []string
|
||||
// }
|
||||
//
|
||||
// // Create a traversal focusing on the hobbies array
|
||||
// hobbiesTraversal := T.Id[Person, []string]()
|
||||
//
|
||||
// // Focus on the first hobby (index 0)
|
||||
// firstHobby := F.Pipe1(
|
||||
// hobbiesTraversal,
|
||||
// TA.At[[]string, Person, string](
|
||||
// identity.Of[[]string],
|
||||
// identity.Map[string, []string],
|
||||
// )(0),
|
||||
// )
|
||||
//
|
||||
// // Modify the first hobby
|
||||
// person := Person{Name: "Alice", Hobbies: []string{"reading", "coding"}}
|
||||
// updated := firstHobby(func(s string) string {
|
||||
// return s + "!"
|
||||
// })(person)
|
||||
// // updated.Hobbies: []string{"reading!", "coding"}
|
||||
//
|
||||
// See Also:
|
||||
// - OA.At: Creates an Optional focusing on an array element
|
||||
// - optional.AsTraversal: Converts an Optional to a Traversal
|
||||
// - G.Compose: Composes two traversals
|
||||
func At[GA ~[]A, S, A, HKTS, HKTGA, HKTA any](
|
||||
fof pointed.OfType[GA, HKTGA],
|
||||
fmap functor.MapType[A, GA, HKTA, HKTGA],
|
||||
) func(int) func(G.Traversal[S, GA, HKTS, HKTGA]) G.Traversal[S, A, HKTS, HKTA] {
|
||||
return F.Flow3(
|
||||
OA.At[GA],
|
||||
optional.AsTraversal[G.Traversal[GA, A, HKTGA, HKTA]](fof, fmap),
|
||||
G.Compose[
|
||||
G.Traversal[GA, A, HKTGA, HKTA],
|
||||
G.Traversal[S, GA, HKTS, HKTGA],
|
||||
G.Traversal[S, A, HKTS, HKTA],
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,12 @@ package generic
|
||||
import (
|
||||
AR "github.com/IBM/fp-go/v2/array/generic"
|
||||
C "github.com/IBM/fp-go/v2/constant"
|
||||
"github.com/IBM/fp-go/v2/endomorphism"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/internal/functor"
|
||||
"github.com/IBM/fp-go/v2/internal/pointed"
|
||||
"github.com/IBM/fp-go/v2/optics/prism"
|
||||
"github.com/IBM/fp-go/v2/predicate"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -47,7 +52,7 @@ func FromTraversable[
|
||||
}
|
||||
|
||||
// FoldMap maps each target to a `Monoid` and combines the result
|
||||
func FoldMap[M, S, A any](f func(A) M) func(sa Traversal[S, A, C.Const[M, S], C.Const[M, A]]) func(S) M {
|
||||
func FoldMap[S, M, A any](f func(A) M) func(sa Traversal[S, A, C.Const[M, S], C.Const[M, A]]) func(S) M {
|
||||
return func(sa Traversal[S, A, C.Const[M, S], C.Const[M, A]]) func(S) M {
|
||||
return F.Flow2(
|
||||
F.Pipe1(
|
||||
@@ -61,13 +66,84 @@ func FoldMap[M, S, A any](f func(A) M) func(sa Traversal[S, A, C.Const[M, S], C.
|
||||
|
||||
// Fold maps each target to a `Monoid` and combines the result
|
||||
func Fold[S, A any](sa Traversal[S, A, C.Const[A, S], C.Const[A, A]]) func(S) A {
|
||||
return FoldMap[A, S](F.Identity[A])(sa)
|
||||
return FoldMap[S](F.Identity[A])(sa)
|
||||
}
|
||||
|
||||
// GetAll gets all the targets of a traversal
|
||||
func GetAll[GA ~[]A, S, A any](s S) func(sa Traversal[S, A, C.Const[GA, S], C.Const[GA, A]]) GA {
|
||||
fmap := FoldMap[GA, S](AR.Of[GA, A])
|
||||
fmap := FoldMap[S](AR.Of[GA, A])
|
||||
return func(sa Traversal[S, A, C.Const[GA, S], C.Const[GA, A]]) GA {
|
||||
return fmap(sa)(s)
|
||||
}
|
||||
}
|
||||
|
||||
// Filter creates a function that filters the targets of a traversal based on a predicate.
|
||||
//
|
||||
// This function allows you to refine a traversal to only focus on values that satisfy
|
||||
// a given predicate. It works by converting the predicate into a prism, then converting
|
||||
// that prism into a traversal, and finally composing it with the original traversal.
|
||||
//
|
||||
// The filtering is selective: when modifying values through the filtered traversal,
|
||||
// only values that satisfy the predicate will be transformed. Values that don't
|
||||
// satisfy the predicate remain unchanged.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - S: The source type
|
||||
// - A: The focus type (the values being filtered)
|
||||
// - HKTS: Higher-kinded type for S (functor/applicative context)
|
||||
// - HKTA: Higher-kinded type for A (functor/applicative context)
|
||||
//
|
||||
// Parameters:
|
||||
// - fof: Function to lift A into the higher-kinded type HKTA (pure/of operation)
|
||||
// - fmap: Function to map over HKTA (functor map operation)
|
||||
//
|
||||
// Returns:
|
||||
// - A function that takes a predicate and returns an endomorphism on traversals
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import (
|
||||
// AR "github.com/IBM/fp-go/v2/array"
|
||||
// F "github.com/IBM/fp-go/v2/function"
|
||||
// "github.com/IBM/fp-go/v2/identity"
|
||||
// N "github.com/IBM/fp-go/v2/number"
|
||||
// AI "github.com/IBM/fp-go/v2/optics/traversal/array/identity"
|
||||
// )
|
||||
//
|
||||
// // Create a traversal for array elements
|
||||
// arrayTraversal := AI.FromArray[int]()
|
||||
// baseTraversal := F.Pipe1(
|
||||
// Id[[]int, []int](),
|
||||
// Compose[[]int, []int, []int, int](arrayTraversal),
|
||||
// )
|
||||
//
|
||||
// // Filter to only positive numbers
|
||||
// isPositive := N.MoreThan(0)
|
||||
// filteredTraversal := F.Pipe1(
|
||||
// baseTraversal,
|
||||
// Filter[[]int, int](identity.Of[int], identity.Map[int, int])(isPositive),
|
||||
// )
|
||||
//
|
||||
// // Double only positive numbers
|
||||
// numbers := []int{-2, -1, 0, 1, 2, 3}
|
||||
// result := filteredTraversal(func(n int) int { return n * 2 })(numbers)
|
||||
// // result: [-2, -1, 0, 2, 4, 6]
|
||||
//
|
||||
// See Also:
|
||||
// - prism.FromPredicate: Creates a prism from a predicate
|
||||
// - prism.AsTraversal: Converts a prism to a traversal
|
||||
// - Compose: Composes two traversals
|
||||
func Filter[
|
||||
S, HKTS, A, HKTA any](
|
||||
fof pointed.OfType[A, HKTA],
|
||||
fmap functor.MapType[A, A, HKTA, HKTA],
|
||||
) func(predicate.Predicate[A]) endomorphism.Endomorphism[Traversal[S, A, HKTS, HKTA]] {
|
||||
return F.Flow3(
|
||||
prism.FromPredicate,
|
||||
prism.AsTraversal[Traversal[A, A, HKTA, HKTA]](fof, fmap),
|
||||
Compose[
|
||||
Traversal[A, A, HKTA, HKTA],
|
||||
Traversal[S, A, HKTS, HKTA],
|
||||
Traversal[S, A, HKTS, HKTA]],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,46 +18,110 @@ package traversal
|
||||
import (
|
||||
C "github.com/IBM/fp-go/v2/constant"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/identity"
|
||||
"github.com/IBM/fp-go/v2/internal/functor"
|
||||
"github.com/IBM/fp-go/v2/internal/pointed"
|
||||
G "github.com/IBM/fp-go/v2/optics/traversal/generic"
|
||||
)
|
||||
|
||||
// Id is the identity constructor of a traversal
|
||||
func Id[S, A any]() G.Traversal[S, S, A, A] {
|
||||
func Id[S, A any]() Traversal[S, S, A, A] {
|
||||
return F.Identity[func(S) A]
|
||||
}
|
||||
|
||||
// Modify applies a transformation function to a traversal
|
||||
func Modify[S, A any](f func(A) A) func(sa G.Traversal[S, A, S, A]) func(S) S {
|
||||
return func(sa G.Traversal[S, A, S, A]) func(S) S {
|
||||
return sa(f)
|
||||
}
|
||||
func Modify[S, A any](f Endomorphism[A]) func(Traversal[S, A, S, A]) Endomorphism[S] {
|
||||
return identity.Flap[Endomorphism[S]](f)
|
||||
}
|
||||
|
||||
// Set sets a constant value for all values of the traversal
|
||||
func Set[S, A any](a A) func(sa G.Traversal[S, A, S, A]) func(S) S {
|
||||
func Set[S, A any](a A) func(Traversal[S, A, S, A]) Endomorphism[S] {
|
||||
return Modify[S](F.Constant1[A](a))
|
||||
}
|
||||
|
||||
// FoldMap maps each target to a `Monoid` and combines the result
|
||||
func FoldMap[M, S, A any](f func(A) M) func(sa G.Traversal[S, A, C.Const[M, S], C.Const[M, A]]) func(S) M {
|
||||
return G.FoldMap[M, S](f)
|
||||
func FoldMap[S, M, A any](f func(A) M) func(sa Traversal[S, A, C.Const[M, S], C.Const[M, A]]) func(S) M {
|
||||
return G.FoldMap[S](f)
|
||||
}
|
||||
|
||||
// Fold maps each target to a `Monoid` and combines the result
|
||||
func Fold[S, A any](sa G.Traversal[S, A, C.Const[A, S], C.Const[A, A]]) func(S) A {
|
||||
func Fold[S, A any](sa Traversal[S, A, C.Const[A, S], C.Const[A, A]]) func(S) A {
|
||||
return G.Fold(sa)
|
||||
}
|
||||
|
||||
// GetAll gets all the targets of a traversal
|
||||
func GetAll[S, A any](s S) func(sa G.Traversal[S, A, C.Const[[]A, S], C.Const[[]A, A]]) []A {
|
||||
func GetAll[A, S any](s S) func(sa Traversal[S, A, C.Const[[]A, S], C.Const[[]A, A]]) []A {
|
||||
return G.GetAll[[]A](s)
|
||||
}
|
||||
|
||||
// Compose composes two traversables
|
||||
func Compose[
|
||||
S, A, B, HKTS, HKTA, HKTB any](ab G.Traversal[A, B, HKTA, HKTB]) func(sa G.Traversal[S, A, HKTS, HKTA]) G.Traversal[S, B, HKTS, HKTB] {
|
||||
S, HKTS, A, B, HKTA, HKTB any](ab Traversal[A, B, HKTA, HKTB]) func(Traversal[S, A, HKTS, HKTA]) Traversal[S, B, HKTS, HKTB] {
|
||||
return G.Compose[
|
||||
G.Traversal[A, B, HKTA, HKTB],
|
||||
G.Traversal[S, A, HKTS, HKTA],
|
||||
G.Traversal[S, B, HKTS, HKTB]](ab)
|
||||
Traversal[A, B, HKTA, HKTB],
|
||||
Traversal[S, A, HKTS, HKTA],
|
||||
Traversal[S, B, HKTS, HKTB]](ab)
|
||||
}
|
||||
|
||||
// Filter creates a function that filters the targets of a traversal based on a predicate.
|
||||
//
|
||||
// This function allows you to refine a traversal to only focus on values that satisfy
|
||||
// a given predicate. It works by converting the predicate into a prism, then converting
|
||||
// that prism into a traversal, and finally composing it with the original traversal.
|
||||
//
|
||||
// The filtering is selective: when modifying values through the filtered traversal,
|
||||
// only values that satisfy the predicate will be transformed. Values that don't
|
||||
// satisfy the predicate remain unchanged.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - S: The source type
|
||||
// - A: The focus type (the values being filtered)
|
||||
// - HKTS: Higher-kinded type for S (functor/applicative context)
|
||||
// - HKTA: Higher-kinded type for A (functor/applicative context)
|
||||
//
|
||||
// Parameters:
|
||||
// - fof: Function to lift A into the higher-kinded type HKTA (pure/of operation)
|
||||
// - fmap: Function to map over HKTA (functor map operation)
|
||||
//
|
||||
// Returns:
|
||||
// - A function that takes a predicate and returns an endomorphism on traversals
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import (
|
||||
// AR "github.com/IBM/fp-go/v2/array"
|
||||
// F "github.com/IBM/fp-go/v2/function"
|
||||
// "github.com/IBM/fp-go/v2/identity"
|
||||
// N "github.com/IBM/fp-go/v2/number"
|
||||
// AI "github.com/IBM/fp-go/v2/optics/traversal/array/identity"
|
||||
// )
|
||||
//
|
||||
// // Create a traversal for array elements
|
||||
// arrayTraversal := AI.FromArray[int]()
|
||||
// baseTraversal := F.Pipe1(
|
||||
// Id[[]int, []int](),
|
||||
// Compose[[]int, []int, []int, int](arrayTraversal),
|
||||
// )
|
||||
//
|
||||
// // Filter to only positive numbers
|
||||
// isPositive := N.MoreThan(0)
|
||||
// filteredTraversal := F.Pipe1(
|
||||
// baseTraversal,
|
||||
// Filter[[]int, int](identity.Of[int], identity.Map[int, int])(isPositive),
|
||||
// )
|
||||
//
|
||||
// // Double only positive numbers
|
||||
// numbers := []int{-2, -1, 0, 1, 2, 3}
|
||||
// result := filteredTraversal(func(n int) int { return n * 2 })(numbers)
|
||||
// // result: [-2, -1, 0, 2, 4, 6]
|
||||
//
|
||||
// See Also:
|
||||
// - prism.FromPredicate: Creates a prism from a predicate
|
||||
// - prism.AsTraversal: Converts a prism to a traversal
|
||||
// - Compose: Composes two traversals
|
||||
func Filter[S, HKTS, A, HKTA any](
|
||||
fof pointed.OfType[A, HKTA],
|
||||
fmap functor.MapType[A, A, HKTA, HKTA],
|
||||
) func(Predicate[A]) Endomorphism[Traversal[S, A, HKTS, HKTA]] {
|
||||
return G.Filter[S, HKTS](fof, fmap)
|
||||
}
|
||||
|
||||
@@ -32,14 +32,14 @@ func TestGetAll(t *testing.T) {
|
||||
|
||||
as := AR.From(1, 2, 3)
|
||||
|
||||
tr := AT.FromArray[[]int, int](AR.Monoid[int]())
|
||||
tr := AT.FromArray[int](AR.Monoid[int]())
|
||||
|
||||
sa := F.Pipe1(
|
||||
Id[[]int, C.Const[[]int, []int]](),
|
||||
Compose[[]int, []int, int, C.Const[[]int, []int]](tr),
|
||||
Compose[[]int, C.Const[[]int, []int], []int, int](tr),
|
||||
)
|
||||
|
||||
getall := GetAll[[]int, int](as)(sa)
|
||||
getall := GetAll[int](as)(sa)
|
||||
|
||||
assert.Equal(t, AR.From(1, 2, 3), getall)
|
||||
}
|
||||
@@ -54,7 +54,7 @@ func TestFold(t *testing.T) {
|
||||
|
||||
sa := F.Pipe1(
|
||||
Id[[]int, C.Const[int, []int]](),
|
||||
Compose[[]int, []int, int, C.Const[int, []int]](tr),
|
||||
Compose[[]int, C.Const[int, []int], []int, int](tr),
|
||||
)
|
||||
|
||||
folded := Fold(sa)(as)
|
||||
@@ -70,10 +70,245 @@ func TestTraverse(t *testing.T) {
|
||||
|
||||
sa := F.Pipe1(
|
||||
Id[[]int, []int](),
|
||||
Compose[[]int, []int, int, []int](tr),
|
||||
Compose[[]int, []int, []int, int](tr),
|
||||
)
|
||||
|
||||
res := sa(utils.Double)(as)
|
||||
|
||||
assert.Equal(t, AR.From(2, 4, 6), res)
|
||||
}
|
||||
|
||||
func TestFilter_Success(t *testing.T) {
|
||||
t.Run("filters and modifies only matching elements", func(t *testing.T) {
|
||||
// Arrange
|
||||
numbers := []int{-2, -1, 0, 1, 2, 3}
|
||||
arrayTraversal := AI.FromArray[int]()
|
||||
baseTraversal := F.Pipe1(
|
||||
Id[[]int, []int](),
|
||||
Compose[[]int, []int, []int, int](arrayTraversal),
|
||||
)
|
||||
|
||||
// Filter to only positive numbers
|
||||
isPositive := N.MoreThan(0)
|
||||
filteredTraversal := F.Pipe1(
|
||||
baseTraversal,
|
||||
Filter[[]int, []int, int, int](F.Identity[int], F.Identity[func(int) int])(isPositive),
|
||||
)
|
||||
|
||||
// Act - double only positive numbers
|
||||
result := filteredTraversal(func(n int) int { return n * 2 })(numbers)
|
||||
|
||||
// Assert
|
||||
assert.Equal(t, []int{-2, -1, 0, 2, 4, 6}, result)
|
||||
})
|
||||
|
||||
t.Run("filters even numbers and triples them", func(t *testing.T) {
|
||||
// Arrange
|
||||
numbers := []int{1, 2, 3, 4, 5, 6}
|
||||
arrayTraversal := AI.FromArray[int]()
|
||||
baseTraversal := F.Pipe1(
|
||||
Id[[]int, []int](),
|
||||
Compose[[]int, []int, []int, int](arrayTraversal),
|
||||
)
|
||||
|
||||
// Filter to only even numbers
|
||||
isEven := func(n int) bool { return n%2 == 0 }
|
||||
filteredTraversal := F.Pipe1(
|
||||
baseTraversal,
|
||||
Filter[[]int, []int, int, int](F.Identity[int], F.Identity[func(int) int])(isEven),
|
||||
)
|
||||
|
||||
// Act
|
||||
result := filteredTraversal(func(n int) int { return n * 3 })(numbers)
|
||||
|
||||
// Assert
|
||||
assert.Equal(t, []int{1, 6, 3, 12, 5, 18}, result)
|
||||
})
|
||||
|
||||
t.Run("filters strings by length", func(t *testing.T) {
|
||||
// Arrange
|
||||
words := []string{"a", "ab", "abc", "abcd", "abcde"}
|
||||
arrayTraversal := AI.FromArray[string]()
|
||||
baseTraversal := F.Pipe1(
|
||||
Id[[]string, []string](),
|
||||
Compose[[]string, []string, []string, string](arrayTraversal),
|
||||
)
|
||||
|
||||
// Filter strings with length > 2
|
||||
longerThanTwo := func(s string) bool { return len(s) > 2 }
|
||||
filteredTraversal := F.Pipe1(
|
||||
baseTraversal,
|
||||
Filter[[]string, []string, string, string](F.Identity[string], F.Identity[func(string) string])(longerThanTwo),
|
||||
)
|
||||
|
||||
// Act - convert to uppercase
|
||||
result := filteredTraversal(func(s string) string {
|
||||
return s + "!"
|
||||
})(words)
|
||||
|
||||
// Assert
|
||||
assert.Equal(t, []string{"a", "ab", "abc!", "abcd!", "abcde!"}, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFilter_EdgeCases(t *testing.T) {
|
||||
t.Run("empty array returns empty array", func(t *testing.T) {
|
||||
// Arrange
|
||||
numbers := []int{}
|
||||
arrayTraversal := AI.FromArray[int]()
|
||||
baseTraversal := F.Pipe1(
|
||||
Id[[]int, []int](),
|
||||
Compose[[]int, []int, []int, int](arrayTraversal),
|
||||
)
|
||||
|
||||
isPositive := N.MoreThan(0)
|
||||
filteredTraversal := F.Pipe1(
|
||||
baseTraversal,
|
||||
Filter[[]int, []int, int, int](F.Identity[int], F.Identity[func(int) int])(isPositive),
|
||||
)
|
||||
|
||||
// Act
|
||||
result := filteredTraversal(utils.Double)(numbers)
|
||||
|
||||
// Assert
|
||||
assert.Equal(t, []int{}, result)
|
||||
})
|
||||
|
||||
t.Run("no elements match predicate", func(t *testing.T) {
|
||||
// Arrange
|
||||
numbers := []int{-5, -4, -3, -2, -1}
|
||||
arrayTraversal := AI.FromArray[int]()
|
||||
baseTraversal := F.Pipe1(
|
||||
Id[[]int, []int](),
|
||||
Compose[[]int, []int, []int, int](arrayTraversal),
|
||||
)
|
||||
|
||||
isPositive := N.MoreThan(0)
|
||||
filteredTraversal := F.Pipe1(
|
||||
baseTraversal,
|
||||
Filter[[]int, []int, int, int](F.Identity[int], F.Identity[func(int) int])(isPositive),
|
||||
)
|
||||
|
||||
// Act
|
||||
result := filteredTraversal(utils.Double)(numbers)
|
||||
|
||||
// Assert - all elements unchanged
|
||||
assert.Equal(t, []int{-5, -4, -3, -2, -1}, result)
|
||||
})
|
||||
|
||||
t.Run("all elements match predicate", func(t *testing.T) {
|
||||
// Arrange
|
||||
numbers := []int{1, 2, 3, 4, 5}
|
||||
arrayTraversal := AI.FromArray[int]()
|
||||
baseTraversal := F.Pipe1(
|
||||
Id[[]int, []int](),
|
||||
Compose[[]int, []int, []int, int](arrayTraversal),
|
||||
)
|
||||
|
||||
isPositive := N.MoreThan(0)
|
||||
filteredTraversal := F.Pipe1(
|
||||
baseTraversal,
|
||||
Filter[[]int, []int, int, int](F.Identity[int], F.Identity[func(int) int])(isPositive),
|
||||
)
|
||||
|
||||
// Act
|
||||
result := filteredTraversal(utils.Double)(numbers)
|
||||
|
||||
// Assert - all elements doubled
|
||||
assert.Equal(t, []int{2, 4, 6, 8, 10}, result)
|
||||
})
|
||||
|
||||
t.Run("single element matching", func(t *testing.T) {
|
||||
// Arrange
|
||||
numbers := []int{42}
|
||||
arrayTraversal := AI.FromArray[int]()
|
||||
baseTraversal := F.Pipe1(
|
||||
Id[[]int, []int](),
|
||||
Compose[[]int, []int, []int, int](arrayTraversal),
|
||||
)
|
||||
|
||||
isPositive := N.MoreThan(0)
|
||||
filteredTraversal := F.Pipe1(
|
||||
baseTraversal,
|
||||
Filter[[]int, []int, int, int](F.Identity[int], F.Identity[func(int) int])(isPositive),
|
||||
)
|
||||
|
||||
// Act
|
||||
result := filteredTraversal(utils.Double)(numbers)
|
||||
|
||||
// Assert
|
||||
assert.Equal(t, []int{84}, result)
|
||||
})
|
||||
|
||||
t.Run("single element not matching", func(t *testing.T) {
|
||||
// Arrange
|
||||
numbers := []int{-42}
|
||||
arrayTraversal := AI.FromArray[int]()
|
||||
baseTraversal := F.Pipe1(
|
||||
Id[[]int, []int](),
|
||||
Compose[[]int, []int, []int, int](arrayTraversal),
|
||||
)
|
||||
|
||||
isPositive := N.MoreThan(0)
|
||||
filteredTraversal := F.Pipe1(
|
||||
baseTraversal,
|
||||
Filter[[]int, []int, int, int](F.Identity[int], F.Identity[func(int) int])(isPositive),
|
||||
)
|
||||
|
||||
// Act
|
||||
result := filteredTraversal(utils.Double)(numbers)
|
||||
|
||||
// Assert
|
||||
assert.Equal(t, []int{-42}, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFilter_Integration(t *testing.T) {
|
||||
t.Run("multiple filters composed", func(t *testing.T) {
|
||||
// Arrange
|
||||
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
arrayTraversal := AI.FromArray[int]()
|
||||
baseTraversal := F.Pipe1(
|
||||
Id[[]int, []int](),
|
||||
Compose[[]int, []int, []int, int](arrayTraversal),
|
||||
)
|
||||
|
||||
// Filter to only even numbers, then only those > 4
|
||||
isEven := func(n int) bool { return n%2 == 0 }
|
||||
greaterThanFour := N.MoreThan(4)
|
||||
|
||||
filteredTraversal := F.Pipe2(
|
||||
baseTraversal,
|
||||
Filter[[]int, []int, int, int](F.Identity[int], F.Identity[func(int) int])(isEven),
|
||||
Filter[[]int, []int, int, int](F.Identity[int], F.Identity[func(int) int])(greaterThanFour),
|
||||
)
|
||||
|
||||
// Act - add 100 to matching elements
|
||||
result := filteredTraversal(func(n int) int { return n + 100 })(numbers)
|
||||
|
||||
// Assert - only 6, 8, 10 should be modified
|
||||
assert.Equal(t, []int{1, 2, 3, 4, 5, 106, 7, 108, 9, 110}, result)
|
||||
})
|
||||
|
||||
t.Run("filter with identity transformation", func(t *testing.T) {
|
||||
// Arrange
|
||||
numbers := []int{1, 2, 3, 4, 5}
|
||||
arrayTraversal := AI.FromArray[int]()
|
||||
baseTraversal := F.Pipe1(
|
||||
Id[[]int, []int](),
|
||||
Compose[[]int, []int, []int, int](arrayTraversal),
|
||||
)
|
||||
|
||||
isEven := func(n int) bool { return n%2 == 0 }
|
||||
filteredTraversal := F.Pipe1(
|
||||
baseTraversal,
|
||||
Filter[[]int, []int, int, int](F.Identity[int], F.Identity[func(int) int])(isEven),
|
||||
)
|
||||
|
||||
// Act - identity transformation
|
||||
result := filteredTraversal(F.Identity[int])(numbers)
|
||||
|
||||
// Assert - array unchanged
|
||||
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
|
||||
})
|
||||
}
|
||||
|
||||
15
v2/optics/traversal/types.go
Normal file
15
v2/optics/traversal/types.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package traversal
|
||||
|
||||
import (
|
||||
"github.com/IBM/fp-go/v2/endomorphism"
|
||||
G "github.com/IBM/fp-go/v2/optics/traversal/generic"
|
||||
"github.com/IBM/fp-go/v2/predicate"
|
||||
)
|
||||
|
||||
type (
|
||||
Endomorphism[A any] = endomorphism.Endomorphism[A]
|
||||
|
||||
Traversal[S, A, HKTS, HKTA any] = G.Traversal[S, A, HKTS, HKTA]
|
||||
|
||||
Predicate[A any] = predicate.Predicate[A]
|
||||
)
|
||||
Reference in New Issue
Block a user