1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-09-03 20:06:08 +02:00

Compare commits

..

11 Commits

Author SHA1 Message Date
Dr. Carsten Leue
5675893896 fix: add DI
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-11-25 16:55:37 +01:00
Carsten Leue
2f0ea57da5 Merge branch 'main' into dependency-injection 2023-11-11 22:22:01 +01:00
Carsten Leue
c0b16c675b fix: add reduce and filter (#79)
Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>
2023-11-11 16:50:18 +01:00
Carsten Leue
796f627d20 fix: add Switch to function package
Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>
2023-11-11 14:00:41 +01:00
Carsten Leue
77e1efe6be fix: simplify provider
Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>
2023-11-10 22:13:14 +01:00
Carsten Leue
677392dfce fix: simplify DI implementation
Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>
2023-11-10 21:44:24 +01:00
Carsten Leue
6a8c3b3a21 Merge branch 'main' into dependency-injection 2023-11-10 21:40:41 +01:00
Carsten Leue
4b68e66528 fix: add samples for Any and Next to iterator package (#78)
Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>
2023-11-10 13:46:10 +01:00
Carsten Leue
cc2f8e54e5 fix: add multi provider
Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>
2023-11-09 21:53:55 +01:00
Carsten Leue
16f708d69f fix: add initial DI implementation
Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>
2023-11-09 19:44:11 +01:00
Carsten Leue
37430c0698 fix: checkin
Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>
2023-11-08 20:21:38 +01:00
33 changed files with 1939 additions and 34 deletions

30
array/any.go Normal file
View File

@@ -0,0 +1,30 @@
// 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 array
import (
G "github.com/IBM/fp-go/array/generic"
)
// AnyWithIndex tests if any of the elements in the array matches the predicate
func AnyWithIndex[A any](pred func(int, A) bool) func([]A) bool {
return G.AnyWithIndex[[]A](pred)
}
// Any tests if any of the elements in the array matches the predicate
func Any[A any](pred func(A) bool) func([]A) bool {
return G.Any[[]A](pred)
}

30
array/any_test.go Normal file
View File

@@ -0,0 +1,30 @@
// 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 array
import (
"testing"
F "github.com/IBM/fp-go/function"
"github.com/stretchr/testify/assert"
)
func TestAny(t *testing.T) {
anyBool := Any(F.Identity[bool])
assert.True(t, anyBool(From(false, true, false)))
assert.False(t, anyBool(From(false, false, false)))
}

View File

@@ -52,6 +52,10 @@ func MonadMapRef[A, B any](as []A, f func(a *A) B) []B {
return bs
}
func MapWithIndex[A, B any](f func(int, A) B) func([]A) []B {
return G.MapWithIndex[[]A, []B](f)
}
func Map[A, B any](f func(a A) B) func([]A) []B {
return F.Bind2nd(MonadMap[A, B], f)
}
@@ -60,18 +64,6 @@ func MapRef[A, B any](f func(a *A) B) func([]A) []B {
return F.Bind2nd(MonadMapRef[A, B], f)
}
func filter[A any](fa []A, pred func(A) bool) []A {
var result []A
count := len(fa)
for i := 0; i < count; i++ {
a := fa[i]
if pred(a) {
result = append(result, a)
}
}
return result
}
func filterRef[A any](fa []A, pred func(a *A) bool) []A {
var result []A
count := len(fa)
@@ -96,23 +88,38 @@ func filterMapRef[A, B any](fa []A, pred func(a *A) bool, f func(a *A) B) []B {
return result
}
// Filter returns a new array with all elements from the original array that match a predicate
func Filter[A any](pred func(A) bool) func([]A) []A {
return F.Bind2nd(filter[A], pred)
return G.Filter[[]A](pred)
}
// FilterWithIndex returns a new array with all elements from the original array that match a predicate
func FilterWithIndex[A any](pred func(int, A) bool) func([]A) []A {
return G.FilterWithIndex[[]A](pred)
}
func FilterRef[A any](pred func(*A) bool) func([]A) []A {
return F.Bind2nd(filterRef[A], pred)
}
func MonadFilterMap[A, B any](fa []A, f func(a A) O.Option[B]) []B {
func MonadFilterMap[A, B any](fa []A, f func(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 {
func MonadFilterMapWithIndex[A, B any](fa []A, f func(int, A) O.Option[B]) []B {
return G.MonadFilterMapWithIndex[[]A, []B](fa, f)
}
// FilterMap 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) O.Option[B]) func([]A) []B {
return G.FilterMap[[]A, []B](f)
}
// FilterMapWithIndex maps an array with an iterating function that returns an [O.Option] and it keeps only the Some values discarding the Nones.
func FilterMapWithIndex[A, B any](f func(int, A) O.Option[B]) func([]A) []B {
return G.FilterMapWithIndex[[]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)
@@ -134,9 +141,19 @@ func reduceRef[A, B any](fa []A, f func(B, *A) B, initial B) B {
}
func Reduce[A, B any](f func(B, A) B, initial B) func([]A) B {
return func(as []A) B {
return array.Reduce(as, f, initial)
}
return G.Reduce[[]A](f, initial)
}
func ReduceWithIndex[A, B any](f func(int, B, A) B, initial B) func([]A) B {
return G.ReduceWithIndex[[]A](f, initial)
}
func ReduceRight[A, B any](f func(A, B) B, initial B) func([]A) B {
return G.ReduceRight[[]A](f, initial)
}
func ReduceRightWithIndex[A, B any](f func(int, A, B) B, initial B) func([]A) B {
return G.ReduceRightWithIndex[[]A](f, initial)
}
func ReduceRef[A, B any](f func(B, *A) B, initial B) func([]A) B {
@@ -300,6 +317,11 @@ func FoldMap[A, B any](m M.Monoid[B]) func(func(A) B) func([]A) B {
return G.FoldMap[[]A](m)
}
// FoldMapWithIndex maps and folds an array. Map the Array passing each value to the iterating function. Then fold the results using the provided Monoid.
func FoldMapWithIndex[A, B any](m M.Monoid[B]) func(func(int, A) B) func([]A) B {
return G.FoldMapWithIndex[[]A](m)
}
// Fold folds the array using the provided Monoid.
func Fold[A any](m M.Monoid[A]) func([]A) A {
return G.Fold[[]A](m)

View File

@@ -59,6 +59,17 @@ func TestMap(t *testing.T) {
assert.Equal(t, dst, []string{"A", "B", "C"})
}
func TestReduceRight(t *testing.T) {
values := From("a", "b", "c")
f := func(a, acc string) string {
return fmt.Sprintf("%s%s", acc, a)
}
b := ""
assert.Equal(t, "cba", ReduceRight(f, b)(values))
assert.Equal(t, "", ReduceRight(f, b)(Empty[string]()))
}
func TestReduce(t *testing.T) {
values := MakeBy(101, F.Identity[int])

77
array/example_any_test.go Normal file
View File

@@ -0,0 +1,77 @@
// 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 array
import (
"fmt"
F "github.com/IBM/fp-go/function"
O "github.com/IBM/fp-go/option"
)
func Example_any() {
pred := func(val int) bool {
return val&2 == 0
}
data1 := From(1, 2, 3)
fmt.Println(Any(pred)(data1))
// Output:
// true
}
func Example_any_filter() {
pred := func(val int) bool {
return val&2 == 0
}
data1 := From(1, 2, 3)
// Any tests if any of the entries in the array matches the condition
Any := F.Flow2(
Filter(pred),
IsNonEmpty[int],
)
fmt.Println(Any(data1))
// Output:
// true
}
func Example_any_find() {
pred := func(val int) bool {
return val&2 == 0
}
data1 := From(1, 2, 3)
// Any tests if any of the entries in the array matches the condition
Any := F.Flow2(
FindFirst(pred),
O.IsSome[int],
)
fmt.Println(Any(data1))
// Output:
// true
}

View File

@@ -0,0 +1,55 @@
// 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 array
import (
"fmt"
F "github.com/IBM/fp-go/function"
)
func Example_find() {
pred := func(val int) bool {
return val&2 == 0
}
data1 := From(1, 2, 3)
fmt.Println(FindFirst(pred)(data1))
// Output:
// Some[int](1)
}
func Example_find_filter() {
pred := func(val int) bool {
return val&2 == 0
}
data1 := From(1, 2, 3)
Find := F.Flow2(
Filter(pred),
Head[int],
)
fmt.Println(Find(data1))
// Output:
// Some[int](1)
}

61
array/find.go Normal file
View File

@@ -0,0 +1,61 @@
// 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 array
import (
G "github.com/IBM/fp-go/array/generic"
O "github.com/IBM/fp-go/option"
)
// FindFirst finds the first element which satisfies a predicate (or a refinement) function
func FindFirst[A any](pred func(A) bool) func([]A) O.Option[A] {
return G.FindFirst[[]A](pred)
}
// FindFirstWithIndex finds the first element which satisfies a predicate (or a refinement) function
func FindFirstWithIndex[A any](pred func(int, A) bool) func([]A) O.Option[A] {
return G.FindFirstWithIndex[[]A](pred)
}
// FindFirstMap finds the first element returned by an [O.Option] based selector function
func FindFirstMap[A, B any](sel func(A) O.Option[B]) func([]A) O.Option[B] {
return G.FindFirstMap[[]A](sel)
}
// FindFirstMapWithIndex finds the first element returned by an [O.Option] based selector function
func FindFirstMapWithIndex[A, B any](sel func(int, A) O.Option[B]) func([]A) O.Option[B] {
return G.FindFirstMapWithIndex[[]A](sel)
}
// FindLast finds the Last element which satisfies a predicate (or a refinement) function
func FindLast[A any](pred func(A) bool) func([]A) O.Option[A] {
return G.FindLast[[]A](pred)
}
// FindLastWithIndex finds the Last element which satisfies a predicate (or a refinement) function
func FindLastWithIndex[A any](pred func(int, A) bool) func([]A) O.Option[A] {
return G.FindLastWithIndex[[]A](pred)
}
// FindLastMap finds the Last element returned by an [O.Option] based selector function
func FindLastMap[A, B any](sel func(A) O.Option[B]) func([]A) O.Option[B] {
return G.FindLastMap[[]A](sel)
}
// FindLastMapWithIndex finds the Last element returned by an [O.Option] based selector function
func FindLastMapWithIndex[A, B any](sel func(int, A) O.Option[B]) func([]A) O.Option[B] {
return G.FindLastMapWithIndex[[]A](sel)
}

34
array/generic/any.go Normal file
View File

@@ -0,0 +1,34 @@
// 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 generic
import (
F "github.com/IBM/fp-go/function"
O "github.com/IBM/fp-go/option"
)
// AnyWithIndex tests if any of the elements in the array matches the predicate
func AnyWithIndex[AS ~[]A, PRED ~func(int, A) bool, A any](pred PRED) func(AS) bool {
return F.Flow2(
FindFirstWithIndex[AS](pred),
O.IsSome[A],
)
}
// Any tests if any of the elements in the array matches the predicate
func Any[AS ~[]A, PRED ~func(A) bool, A any](pred PRED) func(AS) bool {
return AnyWithIndex[AS](F.Ignore1of2[int](pred))
}

View File

@@ -29,14 +29,46 @@ func Of[GA ~[]A, A any](value A) GA {
return GA{value}
}
func Reduce[GA ~[]A, A, B any](fa GA, f func(B, A) B, initial B) B {
func Reduce[GA ~[]A, A, B any](f func(B, A) B, initial B) func(GA) B {
return func(as GA) B {
return MonadReduce[GA](as, f, initial)
}
}
func ReduceWithIndex[GA ~[]A, A, B any](f func(int, B, A) B, initial B) func(GA) B {
return func(as GA) B {
return MonadReduceWithIndex[GA](as, f, initial)
}
}
func ReduceRight[GA ~[]A, A, B any](f func(A, B) B, initial B) func(GA) B {
return func(as GA) B {
return MonadReduceRight[GA](as, f, initial)
}
}
func ReduceRightWithIndex[GA ~[]A, A, B any](f func(int, A, B) B, initial B) func(GA) B {
return func(as GA) B {
return MonadReduceRightWithIndex[GA](as, f, initial)
}
}
func MonadReduce[GA ~[]A, A, B any](fa GA, f func(B, A) B, initial B) B {
return array.Reduce(fa, f, initial)
}
func ReduceWithIndex[GA ~[]A, A, B any](fa GA, f func(int, B, A) B, initial B) B {
func MonadReduceWithIndex[GA ~[]A, A, B any](fa GA, f func(int, B, A) B, initial B) B {
return array.ReduceWithIndex(fa, f, initial)
}
func MonadReduceRight[GA ~[]A, A, B any](fa GA, f func(A, B) B, initial B) B {
return array.ReduceRight(fa, f, initial)
}
func MonadReduceRightWithIndex[GA ~[]A, A, B any](fa GA, f func(int, A, B) B, initial B) B {
return array.ReduceRightWithIndex(fa, f, initial)
}
// From constructs an array from a set of variadic arguments
func From[GA ~[]A, A any](data ...A) GA {
return data
@@ -118,20 +150,56 @@ func Map[GA ~[]A, GB ~[]B, A, B any](f func(a A) B) func(GA) GB {
return F.Bind2nd(MonadMap[GA, GB, A, B], f)
}
func MonadMapWithIndex[GA ~[]A, GB ~[]B, A, B any](as GA, f func(int, A) B) GB {
return array.MonadMapWithIndex[GA, GB](as, f)
}
func MapWithIndex[GA ~[]A, GB ~[]B, A, B any](f func(int, A) B) func(GA) GB {
return F.Bind2nd(MonadMapWithIndex[GA, GB, A, B], f)
}
func Size[GA ~[]A, A any](as GA) int {
return len(as)
}
func filterMap[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(a A) O.Option[B]) GB {
func filterMap[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(A) O.Option[B]) GB {
return array.Reduce(fa, func(bs GB, a A) GB {
return O.MonadFold(f(a), F.Constant(bs), F.Bind1st(Append[GB, B], bs))
}, Empty[GB]())
}
func MonadFilterMap[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(a A) O.Option[B]) GB {
func filterMapWithIndex[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(int, A) O.Option[B]) GB {
return array.ReduceWithIndex(fa, func(idx int, bs GB, a A) GB {
return O.MonadFold(f(idx, a), F.Constant(bs), F.Bind1st(Append[GB, B], bs))
}, Empty[GB]())
}
func MonadFilterMap[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(A) O.Option[B]) GB {
return filterMap[GA, GB](fa, f)
}
func MonadFilterMapWithIndex[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(int, A) O.Option[B]) GB {
return filterMapWithIndex[GA, GB](fa, f)
}
func filterWithIndex[AS ~[]A, PRED ~func(int, A) bool, A any](fa AS, pred PRED) AS {
result := make(AS, 0, len(fa))
for i, a := range fa {
if pred(i, a) {
result = append(result, a)
}
}
return result
}
func FilterWithIndex[AS ~[]A, PRED ~func(int, A) bool, A any](pred PRED) func(AS) AS {
return F.Bind2nd(filterWithIndex[AS, PRED, A], pred)
}
func Filter[AS ~[]A, PRED ~func(A) bool, A any](pred PRED) func(AS) AS {
return FilterWithIndex[AS](F.Ignore1of2[int](pred))
}
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),
@@ -143,10 +211,14 @@ 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 {
func FilterMap[GA ~[]A, GB ~[]B, A, B any](f func(A) O.Option[B]) func(GA) GB {
return F.Bind2nd(MonadFilterMap[GA, GB, A, B], f)
}
func FilterMapWithIndex[GA ~[]A, GB ~[]B, A, B any](f func(int, A) O.Option[B]) func(GA) GB {
return F.Bind2nd(MonadFilterMapWithIndex[GA, GB, A, B], f)
}
func MonadPartition[GA ~[]A, A any](as GA, pred func(A) bool) tuple.Tuple2[GA, GA] {
left := Empty[GA]()
right := Empty[GA]()
@@ -242,6 +314,16 @@ func FoldMap[AS ~[]A, A, B any](m M.Monoid[B]) func(func(A) B) func(AS) B {
}
}
func FoldMapWithIndex[AS ~[]A, A, B any](m M.Monoid[B]) func(func(int, A) B) func(AS) B {
return func(f func(int, A) B) func(AS) B {
return func(as AS) B {
return array.ReduceWithIndex(as, func(idx int, cur B, a A) B {
return m.Concat(cur, f(idx, a))
}, m.Empty())
}
}
}
func Fold[AS ~[]A, A any](m M.Monoid[A]) func(AS) A {
return func(as AS) A {
return array.Reduce(as, m.Concat, m.Empty())

97
array/generic/find.go Normal file
View File

@@ -0,0 +1,97 @@
// 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 generic
import (
F "github.com/IBM/fp-go/function"
O "github.com/IBM/fp-go/option"
)
// FindFirstWithIndex finds the first element which satisfies a predicate (or a refinement) function
func FindFirstWithIndex[AS ~[]A, PRED ~func(int, A) bool, A any](pred PRED) func(AS) O.Option[A] {
none := O.None[A]()
return func(as AS) O.Option[A] {
for i, a := range as {
if pred(i, a) {
return O.Some(a)
}
}
return none
}
}
// FindFirst finds the first element which satisfies a predicate (or a refinement) function
func FindFirst[AS ~[]A, PRED ~func(A) bool, A any](pred PRED) func(AS) O.Option[A] {
return FindFirstWithIndex[AS](F.Ignore1of2[int](pred))
}
// FindFirstMapWithIndex finds the first element returned by an [O.Option] based selector function
func FindFirstMapWithIndex[AS ~[]A, PRED ~func(int, A) O.Option[B], A, B any](pred PRED) func(AS) O.Option[B] {
none := O.None[B]()
return func(as AS) O.Option[B] {
count := len(as)
for i := 0; i < count; i++ {
out := pred(i, as[i])
if O.IsSome(out) {
return out
}
}
return none
}
}
// FindFirstMap finds the first element returned by an [O.Option] based selector function
func FindFirstMap[AS ~[]A, PRED ~func(A) O.Option[B], A, B any](pred PRED) func(AS) O.Option[B] {
return FindFirstMapWithIndex[AS](F.Ignore1of2[int](pred))
}
// FindLastWithIndex finds the first element which satisfies a predicate (or a refinement) function
func FindLastWithIndex[AS ~[]A, PRED ~func(int, A) bool, A any](pred PRED) func(AS) O.Option[A] {
none := O.None[A]()
return func(as AS) O.Option[A] {
for i := len(as) - 1; i >= 0; i-- {
a := as[i]
if pred(i, a) {
return O.Some(a)
}
}
return none
}
}
// FindLast finds the first element which satisfies a predicate (or a refinement) function
func FindLast[AS ~[]A, PRED ~func(A) bool, A any](pred PRED) func(AS) O.Option[A] {
return FindLastWithIndex[AS](F.Ignore1of2[int](pred))
}
// FindLastMapWithIndex finds the first element returned by an [O.Option] based selector function
func FindLastMapWithIndex[AS ~[]A, PRED ~func(int, A) O.Option[B], A, B any](pred PRED) func(AS) O.Option[B] {
none := O.None[B]()
return func(as AS) O.Option[B] {
for i := len(as) - 1; i >= 0; i-- {
out := pred(i, as[i])
if O.IsSome(out) {
return out
}
}
return none
}
}
// FindLastMap finds the first element returned by an [O.Option] based selector function
func FindLastMap[AS ~[]A, PRED ~func(A) O.Option[B], A, B any](pred PRED) func(AS) O.Option[B] {
return FindLastMapWithIndex[AS](F.Ignore1of2[int](pred))
}

View File

@@ -18,6 +18,7 @@ package array
import (
"github.com/IBM/fp-go/internal/array"
M "github.com/IBM/fp-go/monoid"
S "github.com/IBM/fp-go/semigroup"
)
func concat[T any](left, right []T) []T {
@@ -40,6 +41,10 @@ func Monoid[T any]() M.Monoid[[]T] {
return M.MakeMonoid(concat[T], Empty[T]())
}
func Semigroup[T any]() S.Semigroup[[]T] {
return S.MakeSemigroup(concat[T])
}
func addLen[A any](count int, data []A) int {
return count + len(data)
}

168
di/erasure/injector.go Normal file
View File

@@ -0,0 +1,168 @@
// 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 erasure
import (
A "github.com/IBM/fp-go/array"
"github.com/IBM/fp-go/errors"
F "github.com/IBM/fp-go/function"
I "github.com/IBM/fp-go/identity"
IG "github.com/IBM/fp-go/identity/generic"
IOE "github.com/IBM/fp-go/ioeither"
L "github.com/IBM/fp-go/lazy"
O "github.com/IBM/fp-go/option"
R "github.com/IBM/fp-go/record"
T "github.com/IBM/fp-go/tuple"
"sync"
)
func providerToEntry(p Provider) T.Tuple2[string, ProviderFactory] {
return T.MakeTuple2(p.Provides().Id(), p.Factory())
}
func itemProviderToMap(p Provider) map[string][]ProviderFactory {
return R.Singleton(p.Provides().Id(), A.Of(p.Factory()))
}
var (
// missingProviderError returns a [ProviderFactory] that fails due to a missing dependency
missingProviderError = F.Flow4(
Dependency.String,
errors.OnSome[string]("no provider for dependency [%s]"),
IOE.Left[any, error],
F.Constant1[InjectableFactory, IOE.IOEither[error, any]],
)
// missingProviderErrorOrDefault returns the default [ProviderFactory] or an error
missingProviderErrorOrDefault = F.Flow3(
T.Replicate2[Dependency],
T.Map2(Dependency.ProviderFactory, F.Flow2(missingProviderError, F.Constant[ProviderFactory])),
T.Tupled2(O.MonadGetOrElse[ProviderFactory]),
)
emptyMulti any = A.Empty[any]()
// emptyMultiDependency returns a [ProviderFactory] for an empty, multi dependency
emptyMultiDependency = F.Constant1[Dependency](F.Constant1[InjectableFactory](IOE.Of[error](emptyMulti)))
// handleMissingProvider covers the case of a missing provider. It either
// returns an error or an empty multi value provider
handleMissingProvider = F.Flow2(
F.Ternary(isMultiDependency, emptyMultiDependency, missingProviderErrorOrDefault),
F.Constant[ProviderFactory],
)
// mergeItemProviders is a monoid for item provider factories
mergeItemProviders = R.UnionMonoid[string](A.Semigroup[ProviderFactory]())
// mergeProviders is a monoid for provider factories
mergeProviders = R.UnionLastMonoid[string, ProviderFactory]()
// collectItemProviders create a provider map for item providers
collectItemProviders = F.Flow2(
A.FoldMap[Provider](mergeItemProviders)(itemProviderToMap),
R.Map[string](itemProviderFactory),
)
// collectProviders collects non-item providers
collectProviders = F.Flow2(
A.Map(providerToEntry),
R.FromEntries[string, ProviderFactory],
)
// assembleProviders constructs the provider map for item and non-item providers
assembleProviders = F.Flow3(
A.Partition(isItemProvider),
T.Map2(collectProviders, collectItemProviders),
T.Tupled2(mergeProviders.Concat),
)
)
// isMultiDependency tests if a dependency is a container dependency
func isMultiDependency(dep Dependency) bool {
return dep.Flag()&Multi == Multi
}
// isItemProvider tests if a provivder provides a single item
func isItemProvider(provider Provider) bool {
return provider.Provides().Flag()&Item == Item
}
// itemProviderFactory combines multiple factories into one, returning an array
func itemProviderFactory(fcts []ProviderFactory) ProviderFactory {
return func(inj InjectableFactory) IOE.IOEither[error, any] {
return F.Pipe2(
fcts,
IOE.TraverseArray(I.Flap[IOE.IOEither[error, any]](inj)),
IOE.Map[error](F.ToAny[[]any]),
)
}
}
// MakeInjector creates an [InjectableFactory] based on a set of [Provider]s
func MakeInjector(providers []Provider) InjectableFactory {
type Result = IOE.IOEither[error, any]
type LazyResult = L.Lazy[Result]
// resolved stores the values resolved so far, key is the string ID
// of the token, value is a lazy result
var resolved sync.Map
// provide a mapping for all providers
factoryById := assembleProviders(providers)
// the actual factory, we need lazy initialization
var injFct InjectableFactory
// lazy initialization, so we can cross reference it
injFct = func(token Dependency) Result {
key := token.Id()
// according to https://github.com/golang/go/issues/44159 this
// is the best way to use the sync map
actual, loaded := resolved.Load(key)
if !loaded {
computeResult := L.MakeLazy(func() Result {
return F.Pipe5(
token,
T.Replicate2[Dependency],
T.Map2(F.Flow3(
Dependency.Id,
R.Lookup[ProviderFactory, string],
I.Ap[O.Option[ProviderFactory]](factoryById),
), handleMissingProvider),
T.Tupled2(O.MonadGetOrElse[ProviderFactory]),
IG.Ap[ProviderFactory](injFct),
IOE.Memoize[error, any],
)
})
actual, _ = resolved.LoadOrStore(key, F.Pipe1(
computeResult,
L.Memoize[Result],
))
}
return actual.(LazyResult)()
}
return injFct
}

174
di/erasure/provider.go Normal file
View File

@@ -0,0 +1,174 @@
// 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 erasure
import (
"fmt"
A "github.com/IBM/fp-go/array"
E "github.com/IBM/fp-go/either"
F "github.com/IBM/fp-go/function"
I "github.com/IBM/fp-go/identity"
IO "github.com/IBM/fp-go/io"
IOG "github.com/IBM/fp-go/io/generic"
IOE "github.com/IBM/fp-go/ioeither"
IOO "github.com/IBM/fp-go/iooption"
Int "github.com/IBM/fp-go/number/integer"
O "github.com/IBM/fp-go/option"
R "github.com/IBM/fp-go/record"
)
type InjectableFactory = func(Dependency) IOE.IOEither[error, any]
type ProviderFactory = func(InjectableFactory) IOE.IOEither[error, any]
type paramIndex = map[int]int
type paramValue = map[int]any
type handler = func(paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue]
type Provider interface {
fmt.Stringer
// Provides returns the [Dependency] implemented by this provider
Provides() Dependency
// Factory returns s function that can create an instance of the dependency based on an [InjectableFactory]
Factory() ProviderFactory
}
type provider struct {
provides Dependency
factory ProviderFactory
}
func (p *provider) Provides() Dependency {
return p.provides
}
func (p *provider) Factory() ProviderFactory {
return p.factory
}
func (p *provider) String() string {
return fmt.Sprintf("Provider for [%s]", p.provides)
}
func MakeProvider(token Dependency, fct ProviderFactory) Provider {
return &provider{token, fct}
}
func mapFromToken(idx int, token Dependency) map[int]paramIndex {
return R.Singleton(token.Flag()&BehaviourMask, R.Singleton(idx, idx))
}
var (
mergeTokenMaps = R.UnionMonoid[int](R.UnionLastSemigroup[int, int]())
foldDeps = A.FoldMapWithIndex[Dependency](mergeTokenMaps)(mapFromToken)
mergeMaps = R.UnionLastMonoid[int, any]()
collectParams = R.CollectOrd[any, any](Int.Ord)(F.SK[int, any])
handlers = map[int]handler{
Identity: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return F.Pipe1(
mp,
IOE.TraverseRecord[int](getAt(res)),
)
}
},
Option: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return F.Pipe3(
mp,
IOG.TraverseRecord[IO.IO[map[int]E.Either[error, any]], paramIndex](getAt(res)),
IO.Map(R.Map[int](F.Flow2(
E.ToOption[error, any],
F.ToAny[O.Option[any]],
))),
IOE.FromIO[error, paramValue],
)
}
},
IOEither: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return F.Pipe2(
mp,
R.Map[int](F.Flow2(
getAt(res),
F.ToAny[IOE.IOEither[error, any]],
)),
IOE.Of[error, paramValue],
)
}
},
IOOption: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return F.Pipe2(
mp,
R.Map[int](F.Flow3(
getAt(res),
IOO.FromIOEither[error, any],
F.ToAny[IOO.IOOption[any]],
)),
IOE.Of[error, paramValue],
)
}
},
}
)
type Mapping = map[int]paramIndex
func getAt[T any](ar []T) func(idx int) T {
return func(idx int) T {
return ar[idx]
}
}
func handleMapping(mp Mapping) func(res []IOE.IOEither[error, any]) IOE.IOEither[error, []any] {
preFct := F.Pipe1(
mp,
R.Collect(func(idx int, p paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return handlers[idx](p)
}),
)
doFct := F.Flow2(
I.Flap[IOE.IOEither[error, paramValue], []IOE.IOEither[error, any]],
IOE.TraverseArray[error, func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue], paramValue],
)
postFct := IOE.Map[error](F.Flow2(
A.Fold(mergeMaps),
collectParams,
))
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, []any] {
return F.Pipe2(
preFct,
doFct(res),
postFct,
)
}
}
// MakeProviderFactory constructs a [ProviderFactory] based on a set of [Dependency]s and
// a function that accepts the resolved dependencies to return a result
func MakeProviderFactory(
deps []Dependency,
fct func(param ...any) IOE.IOEither[error, any]) ProviderFactory {
return F.Flow3(
F.Curry2(A.MonadMap[Dependency, IOE.IOEither[error, any]])(deps),
handleMapping(foldDeps(deps)),
IOE.Chain(F.Unvariadic0(fct)),
)
}

45
di/erasure/token.go Normal file
View File

@@ -0,0 +1,45 @@
// 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 erasure
import (
"fmt"
O "github.com/IBM/fp-go/option"
)
const (
BehaviourMask = 0x0f
Identity = 0 // required dependency
Option = 1 // optional dependency
IOEither = 2 // lazy and required
IOOption = 3 // lazy and optional
TypeMask = 0xf0
Multi = 1 << 4 // array of implementations
Item = 2 << 4 // item of a multi token
)
// Dependency describes the relationship to a service
type Dependency interface {
fmt.Stringer
// Id returns a unique identifier for a token that can be used as a cache key
Id() string
// Flag returns a tag that identifies the behaviour of the dependency
Flag() int
// ProviderFactory optionally returns an attached [ProviderFactory] that represents the default for this dependency
ProviderFactory() O.Option[ProviderFactory]
}

33
di/injector.go Normal file
View File

@@ -0,0 +1,33 @@
// 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 di
import (
DIE "github.com/IBM/fp-go/di/erasure"
F "github.com/IBM/fp-go/function"
IG "github.com/IBM/fp-go/identity/generic"
IOE "github.com/IBM/fp-go/ioeither"
RIOE "github.com/IBM/fp-go/readerioeither"
)
// Resolve performs a type safe resolution of a dependency
func Resolve[T any](token InjectionToken[T]) RIOE.ReaderIOEither[DIE.InjectableFactory, error, T] {
return F.Flow2(
IG.Ap[DIE.InjectableFactory](asDependency(token)),
IOE.ChainEitherK(token.Unerase),
)
}

166
di/provider.go Normal file
View File

@@ -0,0 +1,166 @@
// 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 di
import (
A "github.com/IBM/fp-go/array"
DIE "github.com/IBM/fp-go/di/erasure"
E "github.com/IBM/fp-go/either"
"github.com/IBM/fp-go/errors"
F "github.com/IBM/fp-go/function"
IOE "github.com/IBM/fp-go/ioeither"
T "github.com/IBM/fp-go/tuple"
)
func lookupAt[T any](idx int, token Dependency[T]) func(params []any) E.Either[error, T] {
return F.Flow3(
A.Lookup[any](idx),
E.FromOption[any](errors.OnNone("No parameter at position %d", idx)),
E.Chain(token.Unerase),
)
}
func eraseProviderFactory0[R any](f func() IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] {
return func(params ...any) IOE.IOEither[error, any] {
return F.Pipe1(
f(),
IOE.Map[error](F.ToAny[R]),
)
}
}
func eraseProviderFactory1[T1 any, R any](
d1 Dependency[T1],
f func(T1) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] {
ft := T.Tupled1(f)
t1 := lookupAt[T1](0, d1)
return func(params ...any) IOE.IOEither[error, any] {
return F.Pipe3(
E.SequenceT1(t1(params)),
IOE.FromEither[error, T.Tuple1[T1]],
IOE.Chain(ft),
IOE.Map[error](F.ToAny[R]),
)
}
}
func eraseProviderFactory2[T1, T2 any, R any](
d1 Dependency[T1],
d2 Dependency[T2],
f func(T1, T2) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] {
ft := T.Tupled2(f)
t1 := lookupAt[T1](0, d1)
t2 := lookupAt[T2](1, d2)
return func(params ...any) IOE.IOEither[error, any] {
return F.Pipe3(
E.SequenceT2(t1(params), t2(params)),
IOE.FromEither[error, T.Tuple2[T1, T2]],
IOE.Chain(ft),
IOE.Map[error](F.ToAny[R]),
)
}
}
func MakeProviderFactory0[R any](
fct func() IOE.IOEither[error, R],
) DIE.ProviderFactory {
return DIE.MakeProviderFactory(
A.Empty[DIE.Dependency](),
eraseProviderFactory0(fct),
)
}
// MakeTokenWithDefault0 create a unique `InjectionToken` for a specific type with an attached default provider
func MakeTokenWithDefault0[R any](name string, fct func() IOE.IOEither[error, R]) InjectionToken[R] {
return MakeTokenWithDefault[R](name, MakeProviderFactory0(fct))
}
func MakeProvider0[R any](
token InjectionToken[R],
fct func() IOE.IOEither[error, R],
) DIE.Provider {
return DIE.MakeProvider(
token,
MakeProviderFactory0(fct),
)
}
func MakeProviderFactory1[T1, R any](
d1 Dependency[T1],
fct func(T1) IOE.IOEither[error, R],
) DIE.ProviderFactory {
return DIE.MakeProviderFactory(
A.From[DIE.Dependency](d1),
eraseProviderFactory1(d1, fct),
)
}
// MakeTokenWithDefault1 create a unique `InjectionToken` for a specific type with an attached default provider
func MakeTokenWithDefault1[T1, R any](name string,
d1 Dependency[T1],
fct func(T1) IOE.IOEither[error, R]) InjectionToken[R] {
return MakeTokenWithDefault[R](name, MakeProviderFactory1(d1, fct))
}
func MakeProvider1[T1, R any](
token InjectionToken[R],
d1 Dependency[T1],
fct func(T1) IOE.IOEither[error, R],
) DIE.Provider {
return DIE.MakeProvider(
token,
MakeProviderFactory1(d1, fct),
)
}
func MakeProviderFactory2[T1, T2, R any](
d1 Dependency[T1],
d2 Dependency[T2],
fct func(T1, T2) IOE.IOEither[error, R],
) DIE.ProviderFactory {
return DIE.MakeProviderFactory(
A.From[DIE.Dependency](d1, d2),
eraseProviderFactory2(d1, d2, fct),
)
}
// MakeTokenWithDefault2 create a unique `InjectionToken` for a specific type with an attached default provider
func MakeTokenWithDefault2[T1, T2, R any](name string,
d1 Dependency[T1],
d2 Dependency[T2],
fct func(T1, T2) IOE.IOEither[error, R]) InjectionToken[R] {
return MakeTokenWithDefault[R](name, MakeProviderFactory2(d1, d2, fct))
}
func MakeProvider2[T1, T2, R any](
token InjectionToken[R],
d1 Dependency[T1],
d2 Dependency[T2],
fct func(T1, T2) IOE.IOEither[error, R],
) DIE.Provider {
return DIE.MakeProvider(
token,
MakeProviderFactory2(d1, d2, fct),
)
}
// ConstProvider simple implementation for a provider with a constant value
func ConstProvider[R any](token InjectionToken[R], value R) DIE.Provider {
return MakeProvider0[R](token, F.Constant(IOE.Of[error](value)))
}

351
di/provider_test.go Normal file
View File

@@ -0,0 +1,351 @@
// 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 di
import (
"fmt"
"testing"
"time"
A "github.com/IBM/fp-go/array"
DIE "github.com/IBM/fp-go/di/erasure"
E "github.com/IBM/fp-go/either"
F "github.com/IBM/fp-go/function"
IOE "github.com/IBM/fp-go/ioeither"
O "github.com/IBM/fp-go/option"
"github.com/stretchr/testify/assert"
)
var (
INJ_KEY2 = MakeToken[string]("INJ_KEY2")
INJ_KEY1 = MakeToken[string]("INJ_KEY1")
INJ_KEY3 = MakeToken[string]("INJ_KEY3")
)
func TestSimpleProvider(t *testing.T) {
var staticCount int
staticValue := func(value string) func() IOE.IOEither[error, string] {
return func() IOE.IOEither[error, string] {
return func() E.Either[error, string] {
staticCount++
return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
}
}
}
var dynamicCount int
dynamicValue := func(value string) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
}
}
p1 := MakeProvider0(INJ_KEY1, staticValue("Carsten"))
p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Identity(), dynamicValue)
inj := DIE.MakeInjector(A.From(p1, p2))
i1 := Resolve(INJ_KEY1)
i2 := Resolve(INJ_KEY2)
res := IOE.SequenceT4(
i2(inj),
i1(inj),
i2(inj),
i1(inj),
)
r := res()
assert.True(t, E.IsRight(r))
assert.Equal(t, 1, staticCount)
assert.Equal(t, 1, dynamicCount)
}
func TestOptionalProvider(t *testing.T) {
var staticCount int
staticValue := func(value string) func() IOE.IOEither[error, string] {
return func() IOE.IOEither[error, string] {
return func() E.Either[error, string] {
staticCount++
return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
}
}
}
var dynamicCount int
dynamicValue := func(value O.Option[string]) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
}
}
p1 := MakeProvider0(INJ_KEY1, staticValue("Carsten"))
p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Option(), dynamicValue)
inj := DIE.MakeInjector(A.From(p1, p2))
i1 := Resolve(INJ_KEY1)
i2 := Resolve(INJ_KEY2)
res := IOE.SequenceT4(
i2(inj),
i1(inj),
i2(inj),
i1(inj),
)
r := res()
assert.True(t, E.IsRight(r))
assert.Equal(t, 1, staticCount)
assert.Equal(t, 1, dynamicCount)
}
func TestOptionalProviderMissingDependency(t *testing.T) {
var dynamicCount int
dynamicValue := func(value O.Option[string]) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
}
}
p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Option(), dynamicValue)
inj := DIE.MakeInjector(A.From(p2))
i2 := Resolve(INJ_KEY2)
res := IOE.SequenceT2(
i2(inj),
i2(inj),
)
r := res()
assert.True(t, E.IsRight(r))
assert.Equal(t, 1, dynamicCount)
}
func TestProviderMissingDependency(t *testing.T) {
var dynamicCount int
dynamicValue := func(value string) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
}
}
p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Identity(), dynamicValue)
inj := DIE.MakeInjector(A.From(p2))
i2 := Resolve(INJ_KEY2)
res := IOE.SequenceT2(
i2(inj),
i2(inj),
)
r := res()
assert.True(t, E.IsLeft(r))
assert.Equal(t, 0, dynamicCount)
}
func TestEagerAndLazyProvider(t *testing.T) {
var staticCount int
staticValue := func(value string) func() IOE.IOEither[error, string] {
return func() IOE.IOEither[error, string] {
return func() E.Either[error, string] {
staticCount++
return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
}
}
}
var dynamicCount int
dynamicValue := func(value string) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
}
}
var lazyEagerCount int
lazyEager := func(laz IOE.IOEither[error, string], eager string) IOE.IOEither[error, string] {
return F.Pipe1(
laz,
IOE.Chain(func(lazValue string) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
lazyEagerCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s], [%s] at [%s]", lazValue, eager, time.Now()))
}
}),
)
}
p1 := MakeProvider0(INJ_KEY1, staticValue("Carsten"))
p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Identity(), dynamicValue)
p3 := MakeProvider2(INJ_KEY3, INJ_KEY2.IOEither(), INJ_KEY1.Identity(), lazyEager)
inj := DIE.MakeInjector(A.From(p1, p2, p3))
i3 := Resolve(INJ_KEY3)
r := i3(inj)()
fmt.Println(r)
assert.True(t, E.IsRight(r))
assert.Equal(t, 1, staticCount)
assert.Equal(t, 1, dynamicCount)
assert.Equal(t, 1, lazyEagerCount)
}
func TestItemProvider(t *testing.T) {
// define a multi token
injMulti := MakeMultiToken[string]("configs")
// provide some values
v1 := ConstProvider(injMulti.Item(), "Value1")
v2 := ConstProvider(injMulti.Item(), "Value2")
// mix in non-multi values
p1 := ConstProvider(INJ_KEY1, "Value3")
p2 := ConstProvider(INJ_KEY2, "Value4")
// populate the injector
inj := DIE.MakeInjector(A.From(p1, v1, p2, v2))
// access the multi value
multi := Resolve(injMulti.Container())
multiInj := multi(inj)
value := multiInj()
assert.Equal(t, E.Of[error](A.From("Value1", "Value2")), value)
}
func TestEmptyItemProvider(t *testing.T) {
// define a multi token
injMulti := MakeMultiToken[string]("configs")
// mix in non-multi values
p1 := ConstProvider(INJ_KEY1, "Value3")
p2 := ConstProvider(INJ_KEY2, "Value4")
// populate the injector
inj := DIE.MakeInjector(A.From(p1, p2))
// access the multi value
multi := Resolve(injMulti.Container())
multiInj := multi(inj)
value := multiInj()
assert.Equal(t, E.Of[error](A.Empty[string]()), value)
}
func TestDependencyOnMultiProvider(t *testing.T) {
// define a multi token
injMulti := MakeMultiToken[string]("configs")
// provide some values
v1 := ConstProvider(injMulti.Item(), "Value1")
v2 := ConstProvider(injMulti.Item(), "Value2")
// mix in non-multi values
p1 := ConstProvider(INJ_KEY1, "Value3")
p2 := ConstProvider(INJ_KEY2, "Value4")
fromMulti := func(val string, multi []string) IOE.IOEither[error, string] {
return IOE.Of[error](fmt.Sprintf("Val: %s, Multi: %s", val, multi))
}
p3 := MakeProvider2(INJ_KEY3, INJ_KEY1.Identity(), injMulti.Container().Identity(), fromMulti)
// populate the injector
inj := DIE.MakeInjector(A.From(p1, p2, v1, v2, p3))
r3 := Resolve(INJ_KEY3)
v := r3(inj)()
assert.Equal(t, E.Of[error]("Val: Value3, Multi: [Value1 Value2]"), v)
}
func TestTokenWithDefaultProvider(t *testing.T) {
// token without a default
injToken1 := MakeToken[string]("Token1")
// token with a default
injToken2 := MakeTokenWithDefault0("Token2", F.Constant(IOE.Of[error]("Carsten")))
// dependency
injToken3 := MakeToken[string]("Token3")
p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOE.IOEither[error, string] {
return IOE.Of[error](fmt.Sprintf("Token: %s", data))
})
// populate the injector
inj := DIE.MakeInjector(A.From(p3))
// resolving injToken3 should work and use the default provider for injToken2
r1 := Resolve(injToken1)
r3 := Resolve(injToken3)
// inj1 should not be available
assert.True(t, E.IsLeft(r1(inj)()))
// r3 should work
assert.Equal(t, E.Of[error]("Token: Carsten"), r3(inj)())
}
func TestTokenWithDefaultProviderAndOverride(t *testing.T) {
// token with a default
injToken2 := MakeTokenWithDefault0("Token2", F.Constant(IOE.Of[error]("Carsten")))
// dependency
injToken3 := MakeToken[string]("Token3")
p2 := ConstProvider(injToken2, "Override")
p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOE.IOEither[error, string] {
return IOE.Of[error](fmt.Sprintf("Token: %s", data))
})
// populate the injector
inj := DIE.MakeInjector(A.From(p2, p3))
// resolving injToken3 should work and use the default provider for injToken2
r3 := Resolve(injToken3)
// r3 should work
assert.Equal(t, E.Of[error]("Token: Override"), r3(inj)())
}

196
di/token.go Normal file
View File

@@ -0,0 +1,196 @@
// 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 di
import (
"fmt"
"strconv"
"sync/atomic"
DIE "github.com/IBM/fp-go/di/erasure"
E "github.com/IBM/fp-go/either"
IO "github.com/IBM/fp-go/io"
IOE "github.com/IBM/fp-go/ioeither"
IOO "github.com/IBM/fp-go/iooption"
O "github.com/IBM/fp-go/option"
)
// Dependency describes the relationship to a service, that has a type and
// a behaviour such as required, option or lazy
type Dependency[T any] interface {
DIE.Dependency
// Unerase converts a value with erased type signature into a strongly typed value
Unerase(val any) E.Either[error, T]
}
// InjectionToken uniquely identifies a dependency by giving it an Id, Type and name
type InjectionToken[T any] interface {
Dependency[T]
// Identity idenifies this dependency as a mandatory, required dependency, it will be resolved eagerly and injected as `T`.
// If the dependency cannot be resolved, the resolution process fails
Identity() Dependency[T]
// Option identifies this dependency as optional, it will be resolved eagerly and injected as `O.Option[T]`.
// If the dependency cannot be resolved, the resolution process continues and the dependency is represented as `O.None[T]`
Option() Dependency[O.Option[T]]
// IOEither identifies this dependency as mandatory but it will be resolved lazily as a `IOE.IOEither[error, T]`. This
// value is memoized to make sure the dependency is a singleton.
// If the dependency cannot be resolved, the resolution process fails
IOEither() Dependency[IOE.IOEither[error, T]]
// IOOption identifies this dependency as optional but it will be resolved lazily as a `IOO.IOOption[T]`. This
// value is memoized to make sure the dependency is a singleton.
// If the dependency cannot be resolved, the resolution process continues and the dependency is represented as the none value.
IOOption() Dependency[IOO.IOOption[T]]
}
// MultiInjectionToken uniquely identifies a dependency by giving it an Id, Type and name.
type MultiInjectionToken[T any] interface {
// Container returns the injection token used to request an array of all provided items
Container() InjectionToken[[]T]
// Item returns the injection token used to provide an item
Item() InjectionToken[T]
}
// makeID creates a generator of unique string IDs
func makeId() IO.IO[string] {
var count atomic.Int64
return IO.MakeIO(func() string {
return strconv.FormatInt(count.Add(1), 16)
})
}
// genId is the common generator of unique string IDs
var genId = makeId()
type token[T any] struct {
name string
id string
flag int
toType func(val any) E.Either[error, T]
providerFactory O.Option[DIE.ProviderFactory]
}
func (t *token[T]) Id() string {
return t.id
}
func (t *token[T]) Flag() int {
return t.flag
}
func (t *token[T]) String() string {
return t.name
}
func (t *token[T]) Unerase(val any) E.Either[error, T] {
return t.toType(val)
}
func (t *token[T]) ProviderFactory() O.Option[DIE.ProviderFactory] {
return t.providerFactory
}
func makeToken[T any](name string, id string, typ int, unerase func(val any) E.Either[error, T], providerFactory O.Option[DIE.ProviderFactory]) Dependency[T] {
return &token[T]{name, id, typ, unerase, providerFactory}
}
type injectionToken[T any] struct {
token[T]
option Dependency[O.Option[T]]
ioeither Dependency[IOE.IOEither[error, T]]
iooption Dependency[IOO.IOOption[T]]
}
type multiInjectionToken[T any] struct {
container *injectionToken[[]T]
item *injectionToken[T]
}
func (i *injectionToken[T]) Identity() Dependency[T] {
return i
}
func (i *injectionToken[T]) Option() Dependency[O.Option[T]] {
return i.option
}
func (i *injectionToken[T]) IOEither() Dependency[IOE.IOEither[error, T]] {
return i.ioeither
}
func (i *injectionToken[T]) IOOption() Dependency[IOO.IOOption[T]] {
return i.iooption
}
func (i *injectionToken[T]) ProviderFactory() O.Option[DIE.ProviderFactory] {
return i.providerFactory
}
func (m *multiInjectionToken[T]) Container() InjectionToken[[]T] {
return m.container
}
func (m *multiInjectionToken[T]) Item() InjectionToken[T] {
return m.item
}
// makeToken create a unique `InjectionToken` for a specific type
func makeInjectionToken[T any](name string, providerFactory O.Option[DIE.ProviderFactory]) InjectionToken[T] {
id := genId()
toIdentity := toType[T]()
return &injectionToken[T]{
token[T]{name, id, DIE.Identity, toIdentity, providerFactory},
makeToken[O.Option[T]](fmt.Sprintf("Option[%s]", name), id, DIE.Option, toOptionType(toIdentity), providerFactory),
makeToken[IOE.IOEither[error, T]](fmt.Sprintf("IOEither[%s]", name), id, DIE.IOEither, toIOEitherType(toIdentity), providerFactory),
makeToken[IOO.IOOption[T]](fmt.Sprintf("IOOption[%s]", name), id, DIE.IOOption, toIOOptionType(toIdentity), providerFactory),
}
}
// MakeToken create a unique `InjectionToken` for a specific type
func MakeToken[T any](name string) InjectionToken[T] {
return makeInjectionToken[T](name, O.None[DIE.ProviderFactory]())
}
// MakeToken create a unique `InjectionToken` for a specific type
func MakeTokenWithDefault[T any](name string, providerFactory DIE.ProviderFactory) InjectionToken[T] {
return makeInjectionToken[T](name, O.Of(providerFactory))
}
// MakeMultiToken creates a [MultiInjectionToken]
func MakeMultiToken[T any](name string) MultiInjectionToken[T] {
id := genId()
toItem := toType[T]()
toContainer := toArrayType(toItem)
containerName := fmt.Sprintf("Container[%s]", name)
itemName := fmt.Sprintf("Item[%s]", name)
// empty factory
providerFactory := O.None[DIE.ProviderFactory]()
// container
container := &injectionToken[[]T]{
token[[]T]{containerName, id, DIE.Multi | DIE.Identity, toContainer, providerFactory},
makeToken[O.Option[[]T]](fmt.Sprintf("Option[%s]", containerName), id, DIE.Multi|DIE.Option, toOptionType(toContainer), providerFactory),
makeToken[IOE.IOEither[error, []T]](fmt.Sprintf("IOEither[%s]", containerName), id, DIE.Multi|DIE.IOEither, toIOEitherType(toContainer), providerFactory),
makeToken[IOO.IOOption[[]T]](fmt.Sprintf("IOOption[%s]", containerName), id, DIE.Multi|DIE.IOOption, toIOOptionType(toContainer), providerFactory),
}
// item
item := &injectionToken[T]{
token[T]{itemName, id, DIE.Item | DIE.Identity, toItem, providerFactory},
makeToken[O.Option[T]](fmt.Sprintf("Option[%s]", itemName), id, DIE.Item|DIE.Option, toOptionType(toItem), providerFactory),
makeToken[IOE.IOEither[error, T]](fmt.Sprintf("IOEither[%s]", itemName), id, DIE.Item|DIE.IOEither, toIOEitherType(toItem), providerFactory),
makeToken[IOO.IOOption[T]](fmt.Sprintf("IOOption[%s]", itemName), id, DIE.Item|DIE.IOOption, toIOOptionType(toItem), providerFactory),
}
// returns the token
return &multiInjectionToken[T]{container, item}
}

76
di/utils.go Normal file
View File

@@ -0,0 +1,76 @@
// 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 di
import (
DIE "github.com/IBM/fp-go/di/erasure"
E "github.com/IBM/fp-go/either"
"github.com/IBM/fp-go/errors"
F "github.com/IBM/fp-go/function"
IOE "github.com/IBM/fp-go/ioeither"
IOO "github.com/IBM/fp-go/iooption"
O "github.com/IBM/fp-go/option"
)
// asDependency converts a generic type to a [DIE.Dependency]
func asDependency[T DIE.Dependency](t T) DIE.Dependency {
return t
}
// toType converts an any to a T
func toType[T any]() func(t any) E.Either[error, T] {
return E.ToType[T](errors.OnSome[any]("Value of type [%T] cannot be converted."))
}
// toOptionType converts an any to an Option[any] and then to an Option[T]
func toOptionType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, O.Option[T]] {
return F.Flow2(
toType[O.Option[any]](),
E.Chain(O.Fold(
F.Nullary2(O.None[T], E.Of[error, O.Option[T]]),
F.Flow2(
item,
E.Map[error](O.Of[T]),
),
)),
)
}
// toIOEitherType converts an any to an IOEither[error, any] and then to an IOEither[error, T]
func toIOEitherType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, IOE.IOEither[error, T]] {
return F.Flow2(
toType[IOE.IOEither[error, any]](),
E.Map[error](IOE.ChainEitherK(item)),
)
}
// toIOOptionType converts an any to an IOOption[any] and then to an IOOption[T]
func toIOOptionType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, IOO.IOOption[T]] {
return F.Flow2(
toType[IOO.IOOption[any]](),
E.Map[error](IOO.ChainOptionK(F.Flow2(
item,
E.ToOption[error, T],
))),
)
}
// toArrayType converts an any to a []T
func toArrayType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, []T] {
return F.Flow2(
toType[[]any](),
E.Chain(E.TraverseArray(item)),
)
}

83
di/utils_test.go Normal file
View File

@@ -0,0 +1,83 @@
// 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 di
import (
"testing"
A "github.com/IBM/fp-go/array"
E "github.com/IBM/fp-go/either"
F "github.com/IBM/fp-go/function"
IOE "github.com/IBM/fp-go/ioeither"
O "github.com/IBM/fp-go/option"
"github.com/stretchr/testify/assert"
)
var (
toInt = toType[int]()
toString = toType[string]()
)
func TestToType(t *testing.T) {
// good cases
assert.Equal(t, E.Of[error](10), toInt(any(10)))
assert.Equal(t, E.Of[error]("Carsten"), toString(any("Carsten")))
assert.Equal(t, E.Of[error](O.Of("Carsten")), toType[O.Option[string]]()(any(O.Of("Carsten"))))
assert.Equal(t, E.Of[error](O.Of(any("Carsten"))), toType[O.Option[any]]()(any(O.Of(any("Carsten")))))
// failure
assert.False(t, E.IsRight(toInt(any("Carsten"))))
assert.False(t, E.IsRight(toType[O.Option[string]]()(O.Of(any("Carsten")))))
}
func TestToOptionType(t *testing.T) {
// shortcuts
toOptInt := toOptionType(toInt)
toOptString := toOptionType(toString)
// good cases
assert.Equal(t, E.Of[error](O.Of(10)), toOptInt(any(O.Of(any(10)))))
assert.Equal(t, E.Of[error](O.Of("Carsten")), toOptString(any(O.Of(any("Carsten")))))
// bad cases
assert.False(t, E.IsRight(toOptInt(any(10))))
assert.False(t, E.IsRight(toOptInt(any(O.Of(10)))))
}
func invokeIOEither[T any](e E.Either[error, IOE.IOEither[error, T]]) E.Either[error, T] {
return F.Pipe1(
e,
E.Chain(func(ioe IOE.IOEither[error, T]) E.Either[error, T] {
return ioe()
}),
)
}
func TestToIOEitherType(t *testing.T) {
// shortcuts
toIOEitherInt := toIOEitherType(toInt)
toIOEitherString := toIOEitherType(toString)
// good cases
assert.Equal(t, E.Of[error](10), invokeIOEither(toIOEitherInt(any(IOE.Of[error](any(10))))))
assert.Equal(t, E.Of[error]("Carsten"), invokeIOEither(toIOEitherString(any(IOE.Of[error](any("Carsten"))))))
// bad cases
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any(IOE.Of[error](any(10)))))))
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any(IOE.Of[error]("Carsten"))))))
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any("Carsten")))))
}
func TestToArrayType(t *testing.T) {
// shortcuts
toArrayString := toArrayType(toString)
// good cases
assert.Equal(t, E.Of[error](A.From("a", "b")), toArrayString(any(A.From(any("a"), any("b")))))
}

View File

@@ -16,6 +16,8 @@
package erasure
import (
E "github.com/IBM/fp-go/either"
"github.com/IBM/fp-go/errors"
F "github.com/IBM/fp-go/function"
)
@@ -29,6 +31,15 @@ func Unerase[T any](t any) T {
return *t.(*T)
}
// SafeUnerase converts an erased variable back to its original value
func SafeUnerase[T any](t any) E.Either[error, T] {
return F.Pipe2(
t,
E.ToType[*T](errors.OnSome[any]("Value of type [%T] is not erased")),
E.Map[error](F.Deref[T]),
)
}
// Erase0 converts a type safe function into an erased function
func Erase0[T1 any](f func() T1) func() any {
return F.Nullary2(f, Erase[T1])

View File

@@ -0,0 +1,28 @@
// 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 generic
// Switch applies a handler to different cases. The handers are stored in a map. A key function
// extracts the case from a value.
func Switch[HF ~func(T) R, N ~map[K]HF, KF ~func(T) K, K comparable, T, R any](kf KF, n N, d HF) HF {
return func(t T) R {
f, ok := n[kf(t)]
if ok {
return f(t)
}
return d(t)
}
}

26
function/switch.go Normal file
View File

@@ -0,0 +1,26 @@
// 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 function
import (
G "github.com/IBM/fp-go/function/generic"
)
// Switch applies a handler to different cases. The handers are stored in a map. A key function
// extracts the case from a value.
func Switch[K comparable, T, R any](kf func(T) K, n map[K]func(T) R, d func(T) R) func(T) R {
return G.Switch(kf, n, d)
}

View File

@@ -57,6 +57,6 @@ func MonadFlap[GAB ~func(A) B, A, B any](fab GAB, a A) B {
return FC.MonadFlap(MonadMap[func(GAB) B, GAB, B], fab, a)
}
func Flap[GAB ~func(A) B, A, B any](a A) func(GAB) B {
func Flap[GAB ~func(A) B, B, A any](a A) func(GAB) B {
return F.Bind2nd(MonadFlap[GAB, A, B], a)
}

View File

@@ -51,6 +51,24 @@ func ReduceWithIndex[GA ~[]A, A, B any](fa GA, f func(int, B, A) B, initial B) B
return current
}
func ReduceRight[GA ~[]A, A, B any](fa GA, f func(A, B) B, initial B) B {
current := initial
count := len(fa)
for i := count - 1; i >= 0; i-- {
current = f(fa[i], current)
}
return current
}
func ReduceRightWithIndex[GA ~[]A, A, B any](fa GA, f func(int, A, B) B, initial B) B {
current := initial
count := len(fa)
for i := count - 1; i >= 0; i-- {
current = f(i, fa[i], current)
}
return current
}
func Append[GA ~[]A, A any](as GA, a A) GA {
return append(as, a)
}
@@ -88,6 +106,15 @@ func MonadMap[GA ~[]A, GB ~[]B, A, B any](as GA, f func(a A) B) GB {
return bs
}
func MonadMapWithIndex[GA ~[]A, GB ~[]B, A, B any](as GA, f func(idx int, a A) B) GB {
count := len(as)
bs := make(GB, count)
for i := count - 1; i >= 0; i-- {
bs[i] = f(i, as[i])
}
return bs
}
func ConstNil[GA ~[]A, A any]() GA {
return (GA)(nil)
}

View File

@@ -57,7 +57,7 @@ func SequenceArray[GA ~func() A, GAS ~func() AAS, AAS ~[]A, GAAS ~[]GA, A any](t
}
// MonadTraverseRecord transforms a record using an IO transform an IO of a record
func MonadTraverseRecord[GB ~func() B, GBS ~func() MB, MA ~map[K]A, MB ~map[K]B, K comparable, A, B any](ma MA, f func(A) GB) GBS {
func MonadTraverseRecord[GBS ~func() MB, MA ~map[K]A, GB ~func() B, MB ~map[K]B, K comparable, A, B any](ma MA, f func(A) GB) GBS {
return RR.MonadTraverse[MA](
Of[GBS, MB],
Map[GBS, func() func(B) MB, MB, func(B) MB],
@@ -67,7 +67,7 @@ func MonadTraverseRecord[GB ~func() B, GBS ~func() MB, MA ~map[K]A, MB ~map[K]B,
}
// TraverseRecord transforms a record using an IO transform an IO of a record
func TraverseRecord[GB ~func() B, GBS ~func() MB, MA ~map[K]A, MB ~map[K]B, K comparable, A, B any](f func(A) GB) func(MA) GBS {
func TraverseRecord[GBS ~func() MB, MA ~map[K]A, GB ~func() B, MB ~map[K]B, K comparable, A, B any](f func(A) GB) func(MA) GBS {
return RR.Traverse[MA](
Of[GBS, MB],
Map[GBS, func() func(B) MB, MB, func(B) MB],
@@ -87,5 +87,5 @@ func TraverseRecordWithIndex[GB ~func() B, GBS ~func() MB, MA ~map[K]A, MB ~map[
}
func SequenceRecord[GA ~func() A, GAS ~func() AAS, AAS ~map[K]A, GAAS ~map[K]GA, K comparable, A any](tas GAAS) GAS {
return MonadTraverseRecord[GA, GAS](tas, F.Identity[GA])
return MonadTraverseRecord[GAS](tas, F.Identity[GA])
}

View File

@@ -41,13 +41,13 @@ func SequenceArray[A any](tas []IO[A]) IO[[]A] {
}
func MonadTraverseRecord[K comparable, A, B any](tas map[K]A, f func(A) IO[B]) IO[map[K]B] {
return G.MonadTraverseRecord[IO[B], IO[map[K]B]](tas, f)
return G.MonadTraverseRecord[IO[map[K]B]](tas, f)
}
// TraverseRecord applies a function returning an [IO] to all elements in a record and the
// transforms this into an [IO] of that record
func TraverseRecord[K comparable, A, B any](f func(A) IO[B]) func(map[K]A) IO[map[K]B] {
return G.TraverseRecord[IO[B], IO[map[K]B], map[K]A](f)
return G.TraverseRecord[IO[map[K]B], map[K]A, IO[B]](f)
}
// TraverseRecordWithIndex applies a function returning an [IO] to all elements in a record and the

View File

@@ -63,7 +63,7 @@ func TestRetryHttp(t *testing.T) {
check := E.Fold(
F.Flow2(
errors.As[*net.DNSError](),
O.Fold(F.Constant(false), F.Constant1[*net.DNSError](true)),
O.IsSome[*net.DNSError],
),
F.Constant1[*PostItem](false),
)

View File

@@ -26,6 +26,6 @@ func Any[GU ~func() O.Option[T.Tuple2[GU, U]], FCT ~func(U) bool, U any](pred FC
return F.Flow3(
Filter[GU](pred),
First[GU],
O.Fold(F.ConstFalse, F.Constant1[U](true)),
O.IsSome[U],
)
}

View File

@@ -41,13 +41,13 @@ func SequenceArray[A any](tas []Lazy[A]) Lazy[[]A] {
}
func MonadTraverseRecord[K comparable, A, B any](tas map[K]A, f func(A) Lazy[B]) Lazy[map[K]B] {
return G.MonadTraverseRecord[Lazy[B], Lazy[map[K]B]](tas, f)
return G.MonadTraverseRecord[Lazy[map[K]B]](tas, f)
}
// TraverseRecord applies a function returning an [IO] to all elements in a record and the
// transforms this into an [IO] of that record
func TraverseRecord[K comparable, A, B any](f func(A) Lazy[B]) func(map[K]A) Lazy[map[K]B] {
return G.TraverseRecord[Lazy[B], Lazy[map[K]B], map[K]A](f)
return G.TraverseRecord[Lazy[map[K]B], map[K]A, Lazy[B]](f)
}
// TraverseRecord applies a function returning an [IO] to all elements in a record and the

View File

@@ -82,6 +82,10 @@ func Fold[A, B any](onNone func() B, onSome func(a A) B) func(ma Option[A]) B {
}
}
func MonadGetOrElse[A any](fa Option[A], onNone func() A) A {
return MonadFold(fa, onNone, F.Identity[A])
}
func GetOrElse[A any](onNone func() A) func(Option[A]) A {
return Fold(onNone, F.Identity[A])
}

View File

@@ -99,6 +99,14 @@ func Collect[M ~map[K]V, GR ~[]R, K comparable, V, R any](f func(K, V) R) func(M
return F.Bind2nd(collect[M, GR, K, V, R], f)
}
func CollectOrd[M ~map[K]V, GR ~[]R, K comparable, V, R any](o ord.Ord[K]) func(f func(K, V) R) func(M) GR {
return func(f func(K, V) R) func(M) GR {
return func(r M) GR {
return collectOrd[M, GR](o, r, f)
}
}
}
func Reduce[M ~map[K]V, K comparable, V, R any](f func(R, V) R, initial R) func(M) R {
return func(r M) R {
return G.Reduce(r, f, initial)

View File

@@ -49,6 +49,11 @@ 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)
}
// CollectOrd applies a collector function to the key value pairs in a map and returns the result as an array
func CollectOrd[V, R any, K comparable](o ord.Ord[K]) func(func(K, V) R) func(map[K]V) []R {
return G.CollectOrd[map[K]V, []R](o)
}
func Reduce[K comparable, V, R any](f func(R, V) R, initial R) func(map[K]V) R {
return G.Reduce[map[K]V](f, initial)
}