1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-06-23 00:27:49 +02:00

fix: implement FilterChain

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
Dr. Carsten Leue
2023-08-10 11:46:30 +02:00
parent cd53cb7036
commit c0028918ae
14 changed files with 311 additions and 17 deletions

View File

@ -108,10 +108,16 @@ func MonadFilterMap[A, B any](fa []A, f func(a A) O.Option[B]) []B {
return G.MonadFilterMap[[]A, []B](fa, f)
}
// FilterChain maps an array with an iterating function that returns an [O.Option] and it keeps only the Some values discarding the Nones.
func FilterMap[A, B any](f func(a A) O.Option[B]) func([]A) []B {
return G.FilterMap[[]A, []B](f)
}
// FilterChain maps an array with an iterating function that returns an [O.Option] of an array. It keeps only the Some values discarding the Nones and then flattens the result.
func FilterChain[A, B any](f func(A) O.Option[[]B]) func([]A) []B {
return G.FilterChain[[]A](f)
}
func FilterMapRef[A, B any](pred func(a *A) bool, f func(a *A) B) func([]A) []B {
return func(fa []A) []B {
return filterMapRef(fa, pred, f)
@ -237,7 +243,7 @@ func Intercalate[A any](m M.Monoid[A]) func(A) func([]A) A {
}
func Flatten[A any](mma [][]A) []A {
return MonadChain(mma, F.Identity[[]A])
return G.Flatten(mma)
}
func Slice[A any](low, high int) func(as []A) []A {

View File

@ -16,6 +16,7 @@
package array
import (
"fmt"
"strings"
"testing"
@ -142,3 +143,33 @@ func TestPartition(t *testing.T) {
assert.Equal(t, T.MakeTuple2(Empty[int](), Empty[int]()), Partition(pred)(Empty[int]()))
assert.Equal(t, T.MakeTuple2(From(1), From(3)), Partition(pred)(From(1, 3)))
}
func TestFilterChain(t *testing.T) {
src := From(1, 2, 3)
f := func(i int) O.Option[[]string] {
if i%2 != 0 {
return O.Of(From(fmt.Sprintf("a%d", i), fmt.Sprintf("b%d", i)))
}
return O.None[[]string]()
}
res := FilterChain(f)(src)
assert.Equal(t, From("a1", "b1", "a3", "b3"), res)
}
func TestFilterMap(t *testing.T) {
src := From(1, 2, 3)
f := func(i int) O.Option[string] {
if i%2 != 0 {
return O.Of(fmt.Sprintf("a%d", i))
}
return O.None[string]()
}
res := FilterMap(f)(src)
assert.Equal(t, From("a1", "a3"), res)
}

View File

@ -118,6 +118,17 @@ func MonadFilterMap[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(a A) O.Option[B])
return filterMap[GA, GB](fa, f)
}
func FilterChain[GA ~[]A, GB ~[]B, A, B any](f func(a A) O.Option[GB]) func(GA) GB {
return F.Flow2(
FilterMap[GA, []GB](f),
Flatten[[]GB],
)
}
func Flatten[GAA ~[]GA, GA ~[]A, A any](mma GAA) GA {
return MonadChain(mma, F.Identity[GA])
}
func FilterMap[GA ~[]A, GB ~[]B, A, B any](f func(a A) O.Option[B]) func(GA) GB {
return F.Bind2nd(MonadFilterMap[GA, GB, A, B], f)
}

View File

@ -21,16 +21,12 @@ import (
S "github.com/IBM/fp-go/semigroup"
)
func concat[A any](first, second func(A) A) func(A) A {
return F.Flow2(first, second)
}
// Semigroup for the Endomorphism where the `concat` operation is the usual function composition.
func Semigroup[A any]() S.Semigroup[func(A) A] {
return S.MakeSemigroup(concat[A])
return S.MakeSemigroup(F.Flow2[func(A) A, func(A) A])
}
// Monoid for the Endomorphism where the `concat` operation is the usual function composition.
func Monoid[A any]() M.Monoid[func(A) A] {
return M.MakeMonoid(concat[A], F.Identity[A])
return M.MakeMonoid(F.Flow2[func(A) A, func(A) A], F.Identity[A])
}

View File

@ -219,3 +219,10 @@ func Ap[GUV ~func() O.Option[T.Tuple2[GUV, func(U) V]], GV ~func() O.Option[T.Tu
func MonadAp[GUV ~func() O.Option[T.Tuple2[GUV, func(U) V]], GV ~func() O.Option[T.Tuple2[GV, V]], GU ~func() O.Option[T.Tuple2[GU, U]], U, V any](fab GUV, ma GU) GV {
return Ap[GUV, GV, GU](ma)(fab)
}
func FilterChain[GVV ~func() O.Option[T.Tuple2[GVV, GV]], GV ~func() O.Option[T.Tuple2[GV, V]], GU ~func() O.Option[T.Tuple2[GU, U]], FCT ~func(U) O.Option[GV], U, V any](f FCT) func(ma GU) GV {
return F.Flow2(
FilterMap[GVV, GU](f),
Flatten[GVV],
)
}

View File

@ -118,3 +118,8 @@ func Repeat[U any](n int, a U) Iterator[U] {
func Count(start int) Iterator[int] {
return G.Count[Iterator[int]](start)
}
// FilterChain filters and transforms the content of an iterator
func FilterChain[U, V any](f func(U) O.Option[Iterator[V]]) func(ma Iterator[U]) Iterator[V] {
return G.FilterChain[Iterator[Iterator[V]], Iterator[V], Iterator[U]](f)
}

17
record/doc.go Normal file
View File

@ -0,0 +1,17 @@
// Copyright (c) 2023 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 record contains monadic operations for maps as well as a rich set of utility functions
package record

View File

@ -16,6 +16,7 @@
package generic
import (
F "github.com/IBM/fp-go/function"
M "github.com/IBM/fp-go/monoid"
S "github.com/IBM/fp-go/semigroup"
)
@ -26,3 +27,17 @@ func UnionMonoid[N ~map[K]V, K comparable, V any](s S.Semigroup[V]) M.Monoid[N]
Empty[N](),
)
}
func UnionLastMonoid[N ~map[K]V, K comparable, V any]() M.Monoid[N] {
return M.MakeMonoid(
unionLast[N],
Empty[N](),
)
}
func UnionFirstMonoid[N ~map[K]V, K comparable, V any]() M.Monoid[N] {
return M.MakeMonoid(
F.Swap(unionLast[N]),
Empty[N](),
)
}

View File

@ -19,6 +19,7 @@ import (
F "github.com/IBM/fp-go/function"
G "github.com/IBM/fp-go/internal/record"
Mg "github.com/IBM/fp-go/magma"
Mo "github.com/IBM/fp-go/monoid"
O "github.com/IBM/fp-go/option"
T "github.com/IBM/fp-go/tuple"
)
@ -82,6 +83,34 @@ func MonadMap[M ~map[K]V, N ~map[K]R, K comparable, V, R any](r M, f func(V) R)
return MonadMapWithIndex[M, N](r, F.Ignore1of2[K](f))
}
func MonadChainWithIndex[M ~map[K]V1, N ~map[K]V2, K comparable, V1, V2 any](m Mo.Monoid[N], r M, f func(K, V1) N) N {
return G.ReduceWithIndex(r, func(k K, dst N, b V1) N {
return m.Concat(dst, f(k, b))
}, m.Empty())
}
func MonadChain[M ~map[K]V1, N ~map[K]V2, K comparable, V1, V2 any](m Mo.Monoid[N], r M, f func(V1) N) N {
return G.Reduce(r, func(dst N, b V1) N {
return m.Concat(dst, f(b))
}, m.Empty())
}
func ChainWithIndex[M ~map[K]V1, N ~map[K]V2, K comparable, V1, V2 any](m Mo.Monoid[N]) func(func(K, V1) N) func(M) N {
return func(f func(K, V1) N) func(M) N {
return func(ma M) N {
return MonadChainWithIndex(m, ma, f)
}
}
}
func Chain[M ~map[K]V1, N ~map[K]V2, K comparable, V1, V2 any](m Mo.Monoid[N]) func(func(V1) N) func(M) N {
return func(f func(V1) N) func(M) N {
return func(ma M) N {
return MonadChain(m, ma, f)
}
}
}
func MonadMapWithIndex[M ~map[K]V, N ~map[K]R, K comparable, V, R any](r M, f func(K, V) R) N {
return G.ReduceWithIndex(r, func(k K, dst N, v V) N {
return upsertAtReadWrite(dst, k, f(k, v))
@ -114,15 +143,14 @@ func MapRefWithIndex[M ~map[K]V, N ~map[K]R, K comparable, V, R any](f func(K, *
return F.Bind2nd(MonadMapRefWithIndex[M, N, K, V, R], f)
}
func lookup[M ~map[K]V, K comparable, V any](r M, k K) O.Option[V] {
if val, ok := r[k]; ok {
func Lookup[M ~map[K]V, K comparable, V any](k K) func(M) O.Option[V] {
n := O.None[V]()
return func(m M) O.Option[V] {
if val, ok := m[k]; ok {
return O.Some(val)
}
return O.None[V]()
}
func Lookup[M ~map[K]V, K comparable, V any](k K) func(M) O.Option[V] {
return F.Bind2nd(lookup[M, K, V], k)
return n
}
}
func Has[M ~map[K]V, K comparable, V any](k K, r M) bool {
@ -161,6 +189,31 @@ func union[M ~map[K]V, K comparable, V any](m Mg.Magma[V], left M, right M) M {
return result
}
func unionLast[M ~map[K]V, K comparable, V any](left M, right M) M {
lenLeft := len(left)
if lenLeft == 0 {
return right
}
lenRight := len(right)
if lenRight == 0 {
return left
}
result := make(M, lenLeft+lenRight)
for k, v := range left {
result[k] = v
}
for k, v := range right {
result[k] = v
}
return result
}
func Union[M ~map[K]V, K comparable, V any](m Mg.Magma[V]) func(M) func(M) M {
return func(right M) func(M) M {
return func(left M) M {
@ -169,6 +222,22 @@ func Union[M ~map[K]V, K comparable, V any](m Mg.Magma[V]) func(M) func(M) M {
}
}
func UnionLast[M ~map[K]V, K comparable, V any](right M) func(M) M {
return func(left M) M {
return unionLast(left, right)
}
}
func Merge[M ~map[K]V, K comparable, V any](right M) func(M) M {
return UnionLast(right)
}
func UnionFirst[M ~map[K]V, K comparable, V any](right M) func(M) M {
return func(left M) M {
return unionLast(right, left)
}
}
func Empty[M ~map[K]V, K comparable, V any]() M {
return make(M)
}
@ -269,6 +338,33 @@ func FilterMap[M ~map[K]V1, N ~map[K]V2, K comparable, V1, V2 any](f func(V1) O.
return F.Bind2nd(filterMapWithIndex[M, N, K, V1, V2], F.Ignore1of2[K](f))
}
// Flatten converts a nested map into a regular map
func Flatten[M ~map[K]N, N ~map[K]V, K comparable, V any](m Mo.Monoid[N]) func(M) N {
return Chain[M, N](m)(F.Identity[N])
}
// FilterChainWithIndex creates a new map with only the elements for which the transformation function creates a Some
func FilterChainWithIndex[M ~map[K]V1, N ~map[K]V2, K comparable, V1, V2 any](m Mo.Monoid[N]) func(func(K, V1) O.Option[N]) func(M) N {
flatten := Flatten[map[K]N, N](m)
return func(f func(K, V1) O.Option[N]) func(M) N {
return F.Flow2(
FilterMapWithIndex[M, map[K]N](f),
flatten,
)
}
}
// FilterChain creates a new map with only the elements for which the transformation function creates a Some
func FilterChain[M ~map[K]V1, N ~map[K]V2, K comparable, V1, V2 any](m Mo.Monoid[N]) func(func(V1) O.Option[N]) func(M) N {
flatten := Flatten[map[K]N, N](m)
return func(f func(V1) O.Option[N]) func(M) N {
return F.Flow2(
FilterMap[M, map[K]N](f),
flatten,
)
}
}
// IsNil checks if the map is set to nil
func IsNil[M ~map[K]V, K comparable, V any](m M) bool {
return m == nil

View File

@ -20,8 +20,19 @@ import (
)
func UnionSemigroup[N ~map[K]V, K comparable, V any](s S.Semigroup[V]) S.Semigroup[N] {
union := Union[N, K, V](s)
return S.MakeSemigroup(func(first N, second N) N {
return union(second)(first)
return union(s, first, second)
})
}
func UnionLastSemigroup[N ~map[K]V, K comparable, V any]() S.Semigroup[N] {
return S.MakeSemigroup(func(first N, second N) N {
return unionLast(first, second)
})
}
func UnionFirstSemigroup[N ~map[K]V, K comparable, V any]() S.Semigroup[N] {
return S.MakeSemigroup(func(first N, second N) N {
return unionLast(second, first)
})
}

View File

@ -21,6 +21,22 @@ import (
S "github.com/IBM/fp-go/semigroup"
)
// UnionMonoid computes the union of two maps of the same type
func UnionMonoid[K comparable, V any](s S.Semigroup[V]) M.Monoid[map[K]V] {
return G.UnionMonoid[map[K]V](s)
}
// UnionLastMonoid computes the union of two maps of the same type giving the last map precedence
func UnionLastMonoid[K comparable, V any]() M.Monoid[map[K]V] {
return G.UnionLastMonoid[map[K]V]()
}
// UnionFirstMonoid computes the union of two maps of the same type giving the first map precedence
func UnionFirstMonoid[K comparable, V any]() M.Monoid[map[K]V] {
return G.UnionFirstMonoid[map[K]V]()
}
// MergeMonoid computes the union of two maps of the same type giving the last map precedence
func MergeMonoid[K comparable, V any]() M.Monoid[map[K]V] {
return G.UnionLastMonoid[map[K]V]()
}

View File

@ -17,27 +17,33 @@ package record
import (
Mg "github.com/IBM/fp-go/magma"
Mo "github.com/IBM/fp-go/monoid"
O "github.com/IBM/fp-go/option"
G "github.com/IBM/fp-go/record/generic"
T "github.com/IBM/fp-go/tuple"
)
// IsEmpty tests if a map is empty
func IsEmpty[K comparable, V any](r map[K]V) bool {
return G.IsEmpty(r)
}
// IsNonEmpty tests if a map is not empty
func IsNonEmpty[K comparable, V any](r map[K]V) bool {
return G.IsNonEmpty(r)
}
// Keys returns the key in a map
func Keys[K comparable, V any](r map[K]V) []K {
return G.Keys[map[K]V, []K](r)
}
// Values returns the values in a map
func Values[K comparable, V any](r map[K]V) []V {
return G.Values[map[K]V, []V](r)
}
// Collect applies a collector function to the key value pairs in a map and returns the result as an array
func Collect[K comparable, V, R any](f func(K, V) R) func(map[K]V) []R {
return G.Collect[map[K]V, []R](f)
}
@ -90,10 +96,12 @@ func MapRefWithIndex[K comparable, V, R any](f func(K, *V) R) func(map[K]V) map[
return G.MapRefWithIndex[map[K]V, map[K]R](f)
}
// Lookup returns the entry for a key in a map if it exists
func Lookup[K comparable, V any](k K) func(map[K]V) O.Option[V] {
return G.Lookup[map[K]V](k)
}
// Has tests if a key is contained in a map
func Has[K comparable, V any](k K, r map[K]V) bool {
return G.Has(k, r)
}
@ -102,10 +110,17 @@ func Union[K comparable, V any](m Mg.Magma[V]) func(map[K]V) func(map[K]V) map[K
return G.Union[map[K]V](m)
}
// Merge combines two maps giving the values in the right one precedence. Also refer to [MergeMonoid]
func Merge[K comparable, V any](right map[K]V) func(map[K]V) map[K]V {
return G.Merge[map[K]V](right)
}
// Empty creates an empty map
func Empty[K comparable, V any]() map[K]V {
return G.Empty[map[K]V]()
}
// Size returns the number of elements in a map
func Size[K comparable, V any](r map[K]V) int {
return G.Size(r)
}
@ -130,6 +145,7 @@ func DeleteAt[K comparable, V any](k K) func(map[K]V) map[K]V {
return G.DeleteAt[map[K]V](k)
}
// Singleton creates a new map with a single entry
func Singleton[K comparable, V any](k K, v V) map[K]V {
return G.Singleton[map[K]V](k, v)
}
@ -168,3 +184,34 @@ func IsNonNil[K comparable, V any](m map[K]V) bool {
func ConstNil[K comparable, V any]() map[K]V {
return (map[K]V)(nil)
}
func MonadChainWithIndex[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2], r map[K]V1, f func(K, V1) map[K]V2) map[K]V2 {
return G.MonadChainWithIndex(m, r, f)
}
func MonadChain[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2], r map[K]V1, f func(V1) map[K]V2) map[K]V2 {
return G.MonadChain(m, r, f)
}
func ChainWithIndex[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2]) func(func(K, V1) map[K]V2) func(map[K]V1) map[K]V2 {
return G.ChainWithIndex[map[K]V1](m)
}
func Chain[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2]) func(func(V1) map[K]V2) func(map[K]V1) map[K]V2 {
return G.Chain[map[K]V1](m)
}
// Flatten converts a nested map into a regular map
func Flatten[K comparable, V any](m Mo.Monoid[map[K]V]) func(map[K]map[K]V) map[K]V {
return G.Flatten[map[K]map[K]V](m)
}
// FilterChainWithIndex creates a new map with only the elements for which the transformation function creates a Some
func FilterChainWithIndex[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2]) func(func(K, V1) O.Option[map[K]V2]) func(map[K]V1) map[K]V2 {
return G.FilterChainWithIndex[map[K]V1](m)
}
// FilterChain creates a new map with only the elements for which the transformation function creates a Some
func FilterChain[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2]) func(func(V1) O.Option[map[K]V2]) func(map[K]V1) map[K]V2 {
return G.FilterChain[map[K]V1](m)
}

View File

@ -16,6 +16,7 @@
package record
import (
"fmt"
"sort"
"testing"
@ -71,3 +72,30 @@ func TestLookup(t *testing.T) {
assert.Equal(t, O.Some("a"), Lookup[string, string]("a")(data))
assert.Equal(t, O.None[string](), Lookup[string, string]("a1")(data))
}
func TestFilterChain(t *testing.T) {
src := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
f := func(k string, value int) O.Option[map[string]string] {
if value%2 != 0 {
return O.Of(map[string]string{
k: fmt.Sprintf("%s%d", k, value),
})
}
return O.None[map[string]string]()
}
// monoid
monoid := MergeMonoid[string, string]()
res := FilterChainWithIndex[int](monoid)(f)(src)
assert.Equal(t, map[string]string{
"a": "a1",
"c": "c3",
}, res)
}

View File

@ -22,3 +22,11 @@ import (
func UnionSemigroup[K comparable, V any](s S.Semigroup[V]) S.Semigroup[map[K]V] {
return G.UnionSemigroup[map[K]V](s)
}
func UnionLastSemigroup[K comparable, V any]() S.Semigroup[map[K]V] {
return G.UnionLastSemigroup[map[K]V]()
}
func UnionFirstSemigroup[K comparable, V any]() S.Semigroup[map[K]V] {
return G.UnionFirstSemigroup[map[K]V]()
}