mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-23 22:14:53 +02:00
fix: add traversal for sequences
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
@@ -77,8 +77,7 @@ func IsNonNil[GA ~[]A, A any](as GA) bool {
|
|||||||
|
|
||||||
func Reduce[GA ~[]A, A, B any](fa GA, f func(B, A) B, initial B) B {
|
func Reduce[GA ~[]A, A, B any](fa GA, f func(B, A) B, initial B) B {
|
||||||
current := initial
|
current := initial
|
||||||
count := len(fa)
|
for i := range len(fa) {
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
current = f(current, fa[i])
|
current = f(current, fa[i])
|
||||||
}
|
}
|
||||||
return current
|
return current
|
||||||
@@ -86,8 +85,7 @@ func Reduce[GA ~[]A, A, B any](fa GA, f func(B, A) B, initial B) B {
|
|||||||
|
|
||||||
func ReduceWithIndex[GA ~[]A, A, B any](fa GA, f func(int, B, A) B, initial B) B {
|
func ReduceWithIndex[GA ~[]A, A, B any](fa GA, f func(int, B, A) B, initial B) B {
|
||||||
current := initial
|
current := initial
|
||||||
count := len(fa)
|
for i := range len(fa) {
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
current = f(i, current, fa[i])
|
current = f(i, current, fa[i])
|
||||||
}
|
}
|
||||||
return current
|
return current
|
||||||
|
|||||||
61
v2/internal/iter/iter.go
Normal file
61
v2/internal/iter/iter.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package iter
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/IBM/fp-go/v2/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MonadReduceWithIndex[GA ~func(yield func(A) bool), A, B any](fa GA, f func(int, B, A) B, initial B) B {
|
||||||
|
current := initial
|
||||||
|
var i int
|
||||||
|
for a := range fa {
|
||||||
|
current = f(i, current, a)
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadReduce[GA ~func(yield func(A) bool), A, B any](fa GA, f func(B, A) B, initial B) B {
|
||||||
|
current := initial
|
||||||
|
for a := range fa {
|
||||||
|
current = f(current, a)
|
||||||
|
}
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concat concatenates two sequences, yielding all elements from left followed by all elements from right.
|
||||||
|
func Concat[GT ~func(yield func(T) bool), T any](left, right GT) GT {
|
||||||
|
return func(yield func(T) bool) {
|
||||||
|
for t := range left {
|
||||||
|
if !yield(t) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for t := range right {
|
||||||
|
if !yield(t) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Of[GA ~func(yield func(A) bool), A any](a A) GA {
|
||||||
|
return func(yield func(A) bool) {
|
||||||
|
yield(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadAppend[GA ~func(yield func(A) bool), A any](f GA, tail A) GA {
|
||||||
|
return Concat(f, Of[GA](tail))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Append[GA ~func(yield func(A) bool), A any](tail A) func(GA) GA {
|
||||||
|
return F.Bind2nd(Concat[GA], Of[GA](tail))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Prepend[GA ~func(yield func(A) bool), A any](head A) func(GA) GA {
|
||||||
|
return F.Bind1st(Concat[GA], Of[GA](head))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Empty[GA ~func(yield func(A) bool), A any]() GA {
|
||||||
|
return func(_ func(A) bool) {}
|
||||||
|
}
|
||||||
152
v2/internal/iter/traverse.go
Normal file
152
v2/internal/iter/traverse.go
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
// 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 iter
|
||||||
|
|
||||||
|
import (
|
||||||
|
F "github.com/IBM/fp-go/v2/function"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
We need to pass the members of the applicative explicitly, because golang does neither support higher kinded types nor template methods on structs or interfaces
|
||||||
|
|
||||||
|
HKTRB = HKT<GB>
|
||||||
|
HKTB = HKT<B>
|
||||||
|
HKTAB = HKT<func(A)B>
|
||||||
|
*/
|
||||||
|
func MonadTraverse[GA ~func(yield func(A) bool), GB ~func(yield func(B) bool), 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,
|
||||||
|
|
||||||
|
ta GA,
|
||||||
|
f func(A) HKTB) HKTRB {
|
||||||
|
return MonadTraverseReduce(fof, fmap, fap, ta, f, MonadAppend[GB, B], Empty[GB]())
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
We need to pass the members of the applicative explicitly, because golang does neither support higher kinded types nor template methods on structs or interfaces
|
||||||
|
|
||||||
|
HKTRB = HKT<GB>
|
||||||
|
HKTB = HKT<B>
|
||||||
|
HKTAB = HKT<func(A)B>
|
||||||
|
*/
|
||||||
|
func MonadTraverseWithIndex[GA ~func(yield func(A) bool), GB ~func(yield func(B) bool), 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,
|
||||||
|
|
||||||
|
ta GA,
|
||||||
|
f func(int, A) HKTB) HKTRB {
|
||||||
|
return MonadTraverseReduceWithIndex(fof, fmap, fap, ta, f, MonadAppend[GB, B], Empty[GB]())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Traverse[GA ~func(yield func(A) bool), GB ~func(yield func(B) bool), 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,
|
||||||
|
|
||||||
|
f func(A) HKTB) func(GA) HKTRB {
|
||||||
|
|
||||||
|
return func(ma GA) HKTRB {
|
||||||
|
return MonadTraverse(fof, fmap, fap, ma, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TraverseWithIndex[GA ~func(yield func(A) bool), GB ~func(yield func(B) bool), 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,
|
||||||
|
|
||||||
|
f func(int, A) HKTB) func(GA) HKTRB {
|
||||||
|
|
||||||
|
return func(ma GA) HKTRB {
|
||||||
|
return MonadTraverseWithIndex(fof, fmap, fap, ma, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadTraverseReduce[GA ~func(yield func(A) bool), GB, 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,
|
||||||
|
|
||||||
|
ta GA,
|
||||||
|
|
||||||
|
transform func(A) HKTB,
|
||||||
|
reduce func(GB, B) GB,
|
||||||
|
initial GB,
|
||||||
|
) HKTRB {
|
||||||
|
mmap := fmap(F.Curry2(reduce))
|
||||||
|
|
||||||
|
return MonadReduce(ta, func(r HKTRB, a A) HKTRB {
|
||||||
|
return F.Pipe2(
|
||||||
|
r,
|
||||||
|
mmap,
|
||||||
|
fap(transform(a)),
|
||||||
|
)
|
||||||
|
}, fof(initial))
|
||||||
|
}
|
||||||
|
|
||||||
|
func MonadTraverseReduceWithIndex[GA ~func(yield func(A) bool), GB, 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,
|
||||||
|
|
||||||
|
ta GA,
|
||||||
|
|
||||||
|
transform func(int, A) HKTB,
|
||||||
|
reduce func(GB, B) GB,
|
||||||
|
initial GB,
|
||||||
|
) HKTRB {
|
||||||
|
mmap := fmap(F.Curry2(reduce))
|
||||||
|
|
||||||
|
return MonadReduceWithIndex(ta, func(idx int, r HKTRB, a A) HKTRB {
|
||||||
|
return F.Pipe2(
|
||||||
|
r,
|
||||||
|
mmap,
|
||||||
|
fap(transform(idx, a)),
|
||||||
|
)
|
||||||
|
}, fof(initial))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TraverseReduce[GA ~func(yield func(A) bool), GB, 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,
|
||||||
|
|
||||||
|
transform func(A) HKTB,
|
||||||
|
reduce func(GB, B) GB,
|
||||||
|
initial GB,
|
||||||
|
) func(GA) HKTRB {
|
||||||
|
return func(ta GA) HKTRB {
|
||||||
|
return MonadTraverseReduce(fof, fmap, fap, ta, transform, reduce, initial)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TraverseReduceWithIndex[GA ~func(yield func(A) bool), GB, 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,
|
||||||
|
|
||||||
|
transform func(int, A) HKTB,
|
||||||
|
reduce func(GB, B) GB,
|
||||||
|
initial GB,
|
||||||
|
) func(GA) HKTRB {
|
||||||
|
return func(ta GA) HKTRB {
|
||||||
|
return MonadTraverseReduceWithIndex(fof, fmap, fap, ta, transform, reduce, initial)
|
||||||
|
}
|
||||||
|
}
|
||||||
9
v2/internal/iter/types.go
Normal file
9
v2/internal/iter/types.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package iter
|
||||||
|
|
||||||
|
import (
|
||||||
|
I "iter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Seq[A any] = I.Seq[A]
|
||||||
|
)
|
||||||
@@ -18,6 +18,7 @@ package io
|
|||||||
import (
|
import (
|
||||||
F "github.com/IBM/fp-go/v2/function"
|
F "github.com/IBM/fp-go/v2/function"
|
||||||
INTA "github.com/IBM/fp-go/v2/internal/array"
|
INTA "github.com/IBM/fp-go/v2/internal/array"
|
||||||
|
INTI "github.com/IBM/fp-go/v2/internal/iter"
|
||||||
INTR "github.com/IBM/fp-go/v2/internal/record"
|
INTR "github.com/IBM/fp-go/v2/internal/record"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -60,6 +61,16 @@ func TraverseArray[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B] {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TraverseIter[A, B any](f Kleisli[A, B]) Kleisli[Seq[A], Seq[B]] {
|
||||||
|
return INTI.Traverse[Seq[A]](
|
||||||
|
Of[Seq[B]],
|
||||||
|
Map[Seq[B], func(B) Seq[B]],
|
||||||
|
Ap[Seq[B], B],
|
||||||
|
|
||||||
|
f,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// TraverseArrayWithIndex is like TraverseArray but the function also receives the index.
|
// TraverseArrayWithIndex is like TraverseArray but the function also receives the index.
|
||||||
// Executes in parallel by default.
|
// Executes in parallel by default.
|
||||||
//
|
//
|
||||||
|
|||||||
7
v2/io/types.go
Normal file
7
v2/io/types.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package io
|
||||||
|
|
||||||
|
import "iter"
|
||||||
|
|
||||||
|
type (
|
||||||
|
Seq[T any] = iter.Seq[T]
|
||||||
|
)
|
||||||
@@ -48,6 +48,7 @@ import (
|
|||||||
|
|
||||||
F "github.com/IBM/fp-go/v2/function"
|
F "github.com/IBM/fp-go/v2/function"
|
||||||
"github.com/IBM/fp-go/v2/internal/functor"
|
"github.com/IBM/fp-go/v2/internal/functor"
|
||||||
|
G "github.com/IBM/fp-go/v2/internal/iter"
|
||||||
M "github.com/IBM/fp-go/v2/monoid"
|
M "github.com/IBM/fp-go/v2/monoid"
|
||||||
"github.com/IBM/fp-go/v2/option"
|
"github.com/IBM/fp-go/v2/option"
|
||||||
)
|
)
|
||||||
@@ -58,10 +59,10 @@ import (
|
|||||||
//
|
//
|
||||||
// seq := Of(42)
|
// seq := Of(42)
|
||||||
// // yields: 42
|
// // yields: 42
|
||||||
|
//
|
||||||
|
//go:inline
|
||||||
func Of[A any](a A) Seq[A] {
|
func Of[A any](a A) Seq[A] {
|
||||||
return func(yield Predicate[A]) {
|
return G.Of[Seq[A]](a)
|
||||||
yield(a)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Of2 creates a key-value sequence containing a single key-value pair.
|
// Of2 creates a key-value sequence containing a single key-value pair.
|
||||||
@@ -521,7 +522,7 @@ func From[A any](data ...A) Seq[A] {
|
|||||||
//
|
//
|
||||||
//go:inline
|
//go:inline
|
||||||
func Empty[A any]() Seq[A] {
|
func Empty[A any]() Seq[A] {
|
||||||
return func(_ Predicate[A]) {}
|
return G.Empty[Seq[A]]()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeBy creates a sequence of n elements by applying a function to each index.
|
// MakeBy creates a sequence of n elements by applying a function to each index.
|
||||||
@@ -566,12 +567,10 @@ func Replicate[A any](n int, a A) Seq[A] {
|
|||||||
// seq := From(1, 2, 3, 4, 5)
|
// seq := From(1, 2, 3, 4, 5)
|
||||||
// sum := MonadReduce(seq, func(acc, x int) int { return acc + x }, 0)
|
// sum := MonadReduce(seq, func(acc, x int) int { return acc + x }, 0)
|
||||||
// // returns: 15
|
// // returns: 15
|
||||||
|
//
|
||||||
|
//go:inline
|
||||||
func MonadReduce[A, B any](fa Seq[A], f func(B, A) B, initial B) B {
|
func MonadReduce[A, B any](fa Seq[A], f func(B, A) B, initial B) B {
|
||||||
current := initial
|
return G.MonadReduce(fa, f, initial)
|
||||||
for a := range fa {
|
|
||||||
current = f(current, a)
|
|
||||||
}
|
|
||||||
return current
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reduce returns a function that reduces a sequence to a single value.
|
// Reduce returns a function that reduces a sequence to a single value.
|
||||||
@@ -598,14 +597,10 @@ func Reduce[A, B any](f func(B, A) B, initial B) func(Seq[A]) B {
|
|||||||
// return acc + (i * x)
|
// return acc + (i * x)
|
||||||
// }, 0)
|
// }, 0)
|
||||||
// // returns: 0*10 + 1*20 + 2*30 = 80
|
// // returns: 0*10 + 1*20 + 2*30 = 80
|
||||||
|
//
|
||||||
|
//go:inline
|
||||||
func MonadReduceWithIndex[A, B any](fa Seq[A], f func(int, B, A) B, initial B) B {
|
func MonadReduceWithIndex[A, B any](fa Seq[A], f func(int, B, A) B, initial B) B {
|
||||||
current := initial
|
return G.MonadReduceWithIndex(fa, f, initial)
|
||||||
var i int
|
|
||||||
for a := range fa {
|
|
||||||
current = f(i, current, a)
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
return current
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReduceWithIndex returns a function that reduces with index.
|
// ReduceWithIndex returns a function that reduces with index.
|
||||||
@@ -831,7 +826,7 @@ func Flap[B, A any](a A) Operator[func(A) B, B] {
|
|||||||
//
|
//
|
||||||
//go:inline
|
//go:inline
|
||||||
func Prepend[A any](head A) Operator[A, A] {
|
func Prepend[A any](head A) Operator[A, A] {
|
||||||
return F.Bind1st(concat[A], Of(head))
|
return G.Prepend[Seq[A]](head)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append returns a function that adds an element to the end of a sequence.
|
// Append returns a function that adds an element to the end of a sequence.
|
||||||
@@ -844,7 +839,7 @@ func Prepend[A any](head A) Operator[A, A] {
|
|||||||
//
|
//
|
||||||
//go:inline
|
//go:inline
|
||||||
func Append[A any](tail A) Operator[A, A] {
|
func Append[A any](tail A) Operator[A, A] {
|
||||||
return F.Bind2nd(concat[A], Of(tail))
|
return G.Append[Seq[A]](tail)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MonadZip combines two sequences into a sequence of pairs.
|
// MonadZip combines two sequences into a sequence of pairs.
|
||||||
|
|||||||
@@ -16,25 +16,10 @@
|
|||||||
package iter
|
package iter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
G "github.com/IBM/fp-go/v2/internal/iter"
|
||||||
M "github.com/IBM/fp-go/v2/monoid"
|
M "github.com/IBM/fp-go/v2/monoid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// concat concatenates two sequences, yielding all elements from left followed by all elements from right.
|
|
||||||
func concat[T any](left, right Seq[T]) Seq[T] {
|
|
||||||
return func(yield Predicate[T]) {
|
|
||||||
for t := range left {
|
|
||||||
if !yield(t) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for t := range right {
|
|
||||||
if !yield(t) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Monoid returns a Monoid instance for Seq[T].
|
// Monoid returns a Monoid instance for Seq[T].
|
||||||
// The monoid's concat operation concatenates sequences, and the empty value is an empty sequence.
|
// The monoid's concat operation concatenates sequences, and the empty value is an empty sequence.
|
||||||
//
|
//
|
||||||
@@ -48,5 +33,5 @@ func concat[T any](left, right Seq[T]) Seq[T] {
|
|||||||
//
|
//
|
||||||
//go:inline
|
//go:inline
|
||||||
func Monoid[T any]() M.Monoid[Seq[T]] {
|
func Monoid[T any]() M.Monoid[Seq[T]] {
|
||||||
return M.MakeMonoid(concat[T], Empty[T]())
|
return M.MakeMonoid(G.Concat[Seq[T]], Empty[T]())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Traversal[E, S, A any] T.Traversal[S, A, ET.Either[E, S], ET.Either[E, A]]
|
Traversal[E, S, A any] = T.Traversal[S, A, ET.Either[E, S], ET.Either[E, A]]
|
||||||
)
|
)
|
||||||
|
|
||||||
func Compose[
|
func Compose[
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Traversal[S, A, HKTS, HKTA any] func(func(A) HKTA) func(S) HKTS
|
Traversal[S, A, HKTS, HKTA any] = func(func(A) HKTA) func(S) HKTS
|
||||||
)
|
)
|
||||||
|
|
||||||
func Compose[
|
func Compose[
|
||||||
|
|||||||
29
v2/optics/traversal/result/traversal.go
Normal file
29
v2/optics/traversal/result/traversal.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// 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 result
|
||||||
|
|
||||||
|
import (
|
||||||
|
T "github.com/IBM/fp-go/v2/optics/traversal/generic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Compose[
|
||||||
|
S, A, B any](ab Traversal[A, B]) Operator[S, A, B] {
|
||||||
|
return T.Compose[
|
||||||
|
Traversal[A, B],
|
||||||
|
Traversal[S, A],
|
||||||
|
Traversal[S, B],
|
||||||
|
](ab)
|
||||||
|
}
|
||||||
12
v2/optics/traversal/result/types.go
Normal file
12
v2/optics/traversal/result/types.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package result
|
||||||
|
|
||||||
|
import (
|
||||||
|
T "github.com/IBM/fp-go/v2/optics/traversal/generic"
|
||||||
|
"github.com/IBM/fp-go/v2/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Traversal[S, A any] = T.Traversal[S, A, Result[S], Result[A]]
|
||||||
|
Result[T any] = result.Result[T]
|
||||||
|
Operator[S, A, B any] = func(Traversal[S, A]) Traversal[S, B]
|
||||||
|
)
|
||||||
65
v2/option/iter.go
Normal file
65
v2/option/iter.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (c) 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 option
|
||||||
|
|
||||||
|
import (
|
||||||
|
INTI "github.com/IBM/fp-go/v2/internal/iter"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TraverseIter transforms a sequence by applying a function that returns an Option to each element.
|
||||||
|
// Returns Some containing a sequence of results if all operations succeed, None if any fails.
|
||||||
|
// This function is useful for processing sequences where each element may fail validation or transformation.
|
||||||
|
//
|
||||||
|
// The traversal short-circuits on the first None encountered, making it efficient for validation pipelines.
|
||||||
|
// The resulting sequence is lazy and will only be evaluated when iterated.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// // Parse a sequence of strings to integers
|
||||||
|
// parse := func(s string) Option[int] {
|
||||||
|
// n, err := strconv.Atoi(s)
|
||||||
|
// if err != nil { return None[int]() }
|
||||||
|
// return Some(n)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Create a sequence of strings
|
||||||
|
// strings := func(yield func(string) bool) {
|
||||||
|
// for _, s := range []string{"1", "2", "3"} {
|
||||||
|
// if !yield(s) { return }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// result := TraverseIter(parse)(strings)
|
||||||
|
// // result is Some(sequence of [1, 2, 3])
|
||||||
|
//
|
||||||
|
// // With invalid input
|
||||||
|
// invalidStrings := func(yield func(string) bool) {
|
||||||
|
// for _, s := range []string{"1", "invalid", "3"} {
|
||||||
|
// if !yield(s) { return }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// result := TraverseIter(parse)(invalidStrings)
|
||||||
|
// // result is None because "invalid" cannot be parsed
|
||||||
|
func TraverseIter[A, B any](f Kleisli[A, B]) Kleisli[Seq[A], Seq[B]] {
|
||||||
|
return INTI.Traverse[Seq[A]](
|
||||||
|
Of[Seq[B]],
|
||||||
|
Map[Seq[B], func(B) Seq[B]],
|
||||||
|
Ap[Seq[B]],
|
||||||
|
|
||||||
|
f,
|
||||||
|
)
|
||||||
|
}
|
||||||
329
v2/option/iter_test.go
Normal file
329
v2/option/iter_test.go
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
// Copyright (c) 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 option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
F "github.com/IBM/fp-go/v2/function"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helper function to create a sequence from a slice
|
||||||
|
func seqFromSlice[T any](items []T) Seq[T] {
|
||||||
|
return slices.Values(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to collect a sequence into a slice
|
||||||
|
func collectSeq[T any](seq Seq[T]) []T {
|
||||||
|
return slices.Collect(seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraverseIter_AllSome(t *testing.T) {
|
||||||
|
// Test case where all transformations succeed
|
||||||
|
parse := func(s string) Option[int] {
|
||||||
|
n, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return None[int]()
|
||||||
|
}
|
||||||
|
return Some(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
input := seqFromSlice([]string{"1", "2", "3", "4", "5"})
|
||||||
|
result := TraverseIter(parse)(input)
|
||||||
|
|
||||||
|
assert.True(t, IsSome(result), "Expected Some result when all transformations succeed")
|
||||||
|
|
||||||
|
collected := MonadFold(result, func() []int { return nil }, collectSeq[int])
|
||||||
|
expected := []int{1, 2, 3, 4, 5}
|
||||||
|
assert.Equal(t, expected, collected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraverseIter_ContainsNone(t *testing.T) {
|
||||||
|
// Test case where one transformation fails
|
||||||
|
parse := func(s string) Option[int] {
|
||||||
|
n, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return None[int]()
|
||||||
|
}
|
||||||
|
return Some(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
input := seqFromSlice([]string{"1", "invalid", "3"})
|
||||||
|
result := TraverseIter(parse)(input)
|
||||||
|
|
||||||
|
assert.True(t, IsNone(result), "Expected None when any transformation fails")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraverseIter_EmptySequence(t *testing.T) {
|
||||||
|
// Test with empty sequence
|
||||||
|
double := func(x int) Option[int] {
|
||||||
|
return Some(x * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
input := seqFromSlice([]int{})
|
||||||
|
result := TraverseIter(double)(input)
|
||||||
|
|
||||||
|
assert.True(t, IsSome(result), "Expected Some for empty sequence")
|
||||||
|
|
||||||
|
collected := MonadFold(result, func() []int { return nil }, collectSeq[int])
|
||||||
|
assert.Empty(t, collected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraverseIter_SingleElement(t *testing.T) {
|
||||||
|
// Test with single element - success case
|
||||||
|
validate := func(x int) Option[int] {
|
||||||
|
if x > 0 {
|
||||||
|
return Some(x * 2)
|
||||||
|
}
|
||||||
|
return None[int]()
|
||||||
|
}
|
||||||
|
|
||||||
|
input := seqFromSlice([]int{5})
|
||||||
|
result := TraverseIter(validate)(input)
|
||||||
|
|
||||||
|
assert.True(t, IsSome(result))
|
||||||
|
collected := MonadFold(result, func() []int { return nil }, collectSeq[int])
|
||||||
|
assert.Equal(t, []int{10}, collected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraverseIter_SingleElementFails(t *testing.T) {
|
||||||
|
// Test with single element - failure case
|
||||||
|
validate := func(x int) Option[int] {
|
||||||
|
if x > 0 {
|
||||||
|
return Some(x * 2)
|
||||||
|
}
|
||||||
|
return None[int]()
|
||||||
|
}
|
||||||
|
|
||||||
|
input := seqFromSlice([]int{-5})
|
||||||
|
result := TraverseIter(validate)(input)
|
||||||
|
|
||||||
|
assert.True(t, IsNone(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraverseIter_Validation(t *testing.T) {
|
||||||
|
// Test validation use case
|
||||||
|
validatePositive := func(x int) Option[int] {
|
||||||
|
if x > 0 {
|
||||||
|
return Some(x)
|
||||||
|
}
|
||||||
|
return None[int]()
|
||||||
|
}
|
||||||
|
|
||||||
|
// All positive
|
||||||
|
input1 := seqFromSlice([]int{1, 2, 3, 4})
|
||||||
|
result1 := TraverseIter(validatePositive)(input1)
|
||||||
|
assert.True(t, IsSome(result1))
|
||||||
|
|
||||||
|
// Contains negative
|
||||||
|
input2 := seqFromSlice([]int{1, -2, 3})
|
||||||
|
result2 := TraverseIter(validatePositive)(input2)
|
||||||
|
assert.True(t, IsNone(result2))
|
||||||
|
|
||||||
|
// Contains zero
|
||||||
|
input3 := seqFromSlice([]int{1, 0, 3})
|
||||||
|
result3 := TraverseIter(validatePositive)(input3)
|
||||||
|
assert.True(t, IsNone(result3))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraverseIter_Transformation(t *testing.T) {
|
||||||
|
// Test transformation use case
|
||||||
|
safeDivide := func(x int) Option[float64] {
|
||||||
|
if x != 0 {
|
||||||
|
return Some(100.0 / float64(x))
|
||||||
|
}
|
||||||
|
return None[float64]()
|
||||||
|
}
|
||||||
|
|
||||||
|
// All non-zero
|
||||||
|
input1 := seqFromSlice([]int{1, 2, 4, 5})
|
||||||
|
result1 := TraverseIter(safeDivide)(input1)
|
||||||
|
assert.True(t, IsSome(result1))
|
||||||
|
|
||||||
|
collected := MonadFold(result1, func() []float64 { return nil }, collectSeq[float64])
|
||||||
|
expected := []float64{100.0, 50.0, 25.0, 20.0}
|
||||||
|
assert.Equal(t, expected, collected)
|
||||||
|
|
||||||
|
// Contains zero
|
||||||
|
input2 := seqFromSlice([]int{1, 0, 4})
|
||||||
|
result2 := TraverseIter(safeDivide)(input2)
|
||||||
|
assert.True(t, IsNone(result2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraverseIter_ShortCircuit(t *testing.T) {
|
||||||
|
// Test that traversal short-circuits on first None
|
||||||
|
callCount := 0
|
||||||
|
countingFunc := func(x int) Option[int] {
|
||||||
|
callCount++
|
||||||
|
if x < 0 {
|
||||||
|
return None[int]()
|
||||||
|
}
|
||||||
|
return Some(x * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// First element fails
|
||||||
|
input := seqFromSlice([]int{-1, 2, 3, 4, 5})
|
||||||
|
result := TraverseIter(countingFunc)(input)
|
||||||
|
|
||||||
|
assert.True(t, IsNone(result))
|
||||||
|
// Should have called the function for elements until the first failure
|
||||||
|
// Note: The exact count depends on implementation details of the traverse function
|
||||||
|
assert.Greater(t, callCount, 0, "Function should be called at least once")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraverseIter_LazyEvaluation(t *testing.T) {
|
||||||
|
// Test that the result sequence is lazy
|
||||||
|
transform := func(x int) Option[int] {
|
||||||
|
return Some(x * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
input := seqFromSlice([]int{1, 2, 3, 4, 5})
|
||||||
|
result := TraverseIter(transform)(input)
|
||||||
|
|
||||||
|
assert.True(t, IsSome(result))
|
||||||
|
|
||||||
|
// Partially consume the sequence
|
||||||
|
callCount := 0
|
||||||
|
MonadFold(result, func() int { return 0 }, func(seq Seq[int]) int {
|
||||||
|
for val := range seq {
|
||||||
|
callCount++
|
||||||
|
_ = val
|
||||||
|
if callCount == 2 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return callCount
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, 2, callCount, "Should only evaluate consumed elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraverseIter_ComplexTransformation(t *testing.T) {
|
||||||
|
// Test with more complex transformation
|
||||||
|
type Person struct {
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
validatePerson := func(name string) Option[Person] {
|
||||||
|
if name == "" {
|
||||||
|
return None[Person]()
|
||||||
|
}
|
||||||
|
return Some(Person{Name: name, Age: len(name)})
|
||||||
|
}
|
||||||
|
|
||||||
|
input := seqFromSlice([]string{"Alice", "Bob", "Charlie"})
|
||||||
|
result := TraverseIter(validatePerson)(input)
|
||||||
|
|
||||||
|
assert.True(t, IsSome(result))
|
||||||
|
|
||||||
|
collected := MonadFold(result, func() []Person { return nil }, collectSeq[Person])
|
||||||
|
expected := []Person{
|
||||||
|
{Name: "Alice", Age: 5},
|
||||||
|
{Name: "Bob", Age: 3},
|
||||||
|
{Name: "Charlie", Age: 7},
|
||||||
|
}
|
||||||
|
assert.Equal(t, expected, collected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraverseIter_WithPipeline(t *testing.T) {
|
||||||
|
// Test TraverseIter in a functional pipeline
|
||||||
|
parse := func(s string) Option[int] {
|
||||||
|
n, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return None[int]()
|
||||||
|
}
|
||||||
|
return Some(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
input := seqFromSlice([]string{"1", "2", "3", "4", "5"})
|
||||||
|
|
||||||
|
result := F.Pipe2(
|
||||||
|
input,
|
||||||
|
TraverseIter(parse),
|
||||||
|
Map(collectSeq[int]),
|
||||||
|
)
|
||||||
|
|
||||||
|
collected := MonadFold(result, func() []int { return nil }, F.Identity[[]int])
|
||||||
|
expected := []int{1, 2, 3, 4, 5}
|
||||||
|
assert.Equal(t, expected, collected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTraverseIter_ChainedTransformations(t *testing.T) {
|
||||||
|
// Test chaining multiple transformations
|
||||||
|
parseAndValidate := func(s string) Option[int] {
|
||||||
|
n, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return None[int]()
|
||||||
|
}
|
||||||
|
if n > 0 {
|
||||||
|
return Some(n)
|
||||||
|
}
|
||||||
|
return None[int]()
|
||||||
|
}
|
||||||
|
|
||||||
|
// All valid
|
||||||
|
input1 := seqFromSlice([]string{"1", "2", "3"})
|
||||||
|
result1 := TraverseIter(parseAndValidate)(input1)
|
||||||
|
assert.True(t, IsSome(result1))
|
||||||
|
|
||||||
|
// Contains invalid number
|
||||||
|
input2 := seqFromSlice([]string{"1", "invalid", "3"})
|
||||||
|
result2 := TraverseIter(parseAndValidate)(input2)
|
||||||
|
assert.True(t, IsNone(result2))
|
||||||
|
|
||||||
|
// Contains non-positive number
|
||||||
|
input3 := seqFromSlice([]string{"1", "0", "3"})
|
||||||
|
result3 := TraverseIter(parseAndValidate)(input3)
|
||||||
|
assert.True(t, IsNone(result3))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example test demonstrating usage
|
||||||
|
func ExampleTraverseIter() {
|
||||||
|
// Parse a sequence of strings to integers
|
||||||
|
parse := func(s string) Option[int] {
|
||||||
|
n, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return None[int]()
|
||||||
|
}
|
||||||
|
return Some(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a sequence of valid strings
|
||||||
|
validStrings := seqFromSlice([]string{"1", "2", "3"})
|
||||||
|
result := TraverseIter(parse)(validStrings)
|
||||||
|
|
||||||
|
if IsSome(result) {
|
||||||
|
numbers := MonadFold(result, func() []int { return nil }, collectSeq[int])
|
||||||
|
fmt.Println(numbers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a sequence with invalid string
|
||||||
|
invalidStrings := seqFromSlice([]string{"1", "invalid", "3"})
|
||||||
|
result2 := TraverseIter(parse)(invalidStrings)
|
||||||
|
|
||||||
|
if IsNone(result2) {
|
||||||
|
fmt.Println("Parsing failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// [1 2 3]
|
||||||
|
// Parsing failed
|
||||||
|
}
|
||||||
7
v2/option/types.go
Normal file
7
v2/option/types.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import "iter"
|
||||||
|
|
||||||
|
type (
|
||||||
|
Seq[T any] = iter.Seq[T]
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user