1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-08-24 19:29:11 +02:00

Compare commits

...

44 Commits

Author SHA1 Message Date
Dr. Carsten Leue
813b83b423 fix: add WithQueryArg to request builder
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2024-01-08 21:35:39 +01:00
Dr. Carsten Leue
9139dedbbe fix: add lazy support for iterators
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2024-01-08 14:22:08 +01:00
Elliot Chen
5e15119653 run go mod tidy (#97)
Co-authored-by: zichang.chen <zichange.chen@grabtaxi.com>
2024-01-08 08:08:59 +01:00
Elliot Chen
986aa21055 add idea filter in gitignore (#94)
Co-authored-by: zichang.chen <zichange.chen@grabtaxi.com>
2024-01-02 21:56:40 +01:00
renovate[bot]
3a4c46ec1e fix(deps): update module github.com/urfave/cli/v2 to v2.27.1 2023-12-30 18:29:29 +00:00
renovate[bot]
96c3ee20ff fix(deps): update module github.com/urfave/cli/v2 to v2.27.0 2023-12-27 02:11:30 +00:00
Dr. Carsten Leue
7af9acfd99 fix: Chain for Endomorphism
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-22 16:43:19 +01:00
Dr. Carsten Leue
36eefbcd27 fix: name the endomorphism for the Y combinator
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-22 14:41:55 +01:00
Dr. Carsten Leue
973138c822 fix: add prepend method
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-21 16:46:47 +01:00
Dr. Carsten Leue
12ef79184b fix: typing for Y combinator
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-21 16:25:35 +01:00
Dr. Carsten Leue
5ac47440a1 fix: add a single element cache
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-21 14:10:46 +01:00
Dr. Carsten Leue
3aa55c74d4 fix: add WithBearer
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-19 14:33:52 +01:00
Dr. Carsten Leue
a6f55a199c fix: experiment with doc links
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-19 12:18:07 +01:00
Dr. Carsten Leue
2b500d15da Merge branch 'main' of github.com:IBM/fp-go 2023-12-18 21:40:39 +01:00
Dr. Carsten Leue
599b8256b6 fix: generate DI variations
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-18 21:40:30 +01:00
renovate[bot]
cf70b47984 chore(deps): update actions/setup-node action to v4.0.1 2023-12-18 15:59:48 +00:00
Dr. Carsten Leue
7bceb856f8 fix: move DI to separate package
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-18 10:06:47 +01:00
Dr. Carsten Leue
49e89de783 fix: make Curry operations a bit more generic
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-18 09:46:46 +01:00
Dr. Carsten Leue
a87de2f644 fix: use endomorphism in optics
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-18 09:28:36 +01:00
Dr. Carsten Leue
6d043d2752 fix: common functions for endomorphism
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-17 22:59:25 +01:00
Dr. Carsten Leue
1d02f21ff5 fix: rename FormEndomorphism and FormBuilder
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-17 22:38:55 +01:00
Dr. Carsten Leue
e82575fe08 fix: consistent endomorphism
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-17 13:24:05 +01:00
Dr. Carsten Leue
5fcd0b1595 fix: use endomorphism
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-17 12:34:15 +01:00
Dr. Carsten Leue
5caabf478c fix: add Lens for FormData
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-16 23:38:03 +01:00
Dr. Carsten Leue
b7ec18c83e fix: Content-Length header in Requester
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-16 22:06:40 +01:00
Dr. Carsten Leue
96686425fb fix: add WithFormData and WithJson
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-16 19:29:20 +01:00
Dr. Carsten Leue
1f675e08fa fix: add support for request builder
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-16 16:49:57 +01:00
Dr. Carsten Leue
4d2f410c98 fix: add MakeBodyRequest
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-15 16:00:58 +01:00
Dr. Carsten Leue
8f49c1328c fix: add provider factories with more dependencies
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-15 15:31:01 +01:00
Dr. Carsten Leue
2a1d5821db fix: expose http client as injection token
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-15 15:21:07 +01:00
renovate[bot]
6abbdc5ee1 chore(deps): update actions/setup-go action to v5 2023-12-06 19:24:14 +00:00
renovate[bot]
5fea9858a9 fix(deps): update module github.com/urfave/cli/v2 to v2.26.0 2023-12-03 00:19:17 +00:00
Carsten Leue
e6426c90c0 fix: add WithResource to IO and IOOption (#90)
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-12-01 09:38:30 +01:00
Carsten Leue
54ce59105e fix: add ChainFirstIOK (#89)
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-11-30 14:50:29 +01:00
Carsten Leue
8bb006c741 fix: add a uniq method to arrays (#88)
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-11-30 09:03:16 +01:00
Carsten Leue
b6efa35b03 fix: bug in compact array (#87)
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-11-29 22:13:46 +01:00
Carsten Leue
35848900c0 fix: generic parameter order for ChainTo (#86)
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-11-28 09:26:19 +01:00
Dr. Carsten Leue
3d54f99739 fix: add missing TraverseArraySeq
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-11-27 20:52:02 +01:00
Carsten Leue
ffd9418cac fix: support Alt for IOOption (#84)
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-11-27 17:00:59 +01:00
Carsten Leue
acfcea59f4 fix: break cyclic dependencies between IOOption and IOEither (#83)
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-11-27 14:55:10 +01:00
Carsten Leue
b4bf511f03 fix: http interface of IOEither (#82)
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-11-27 12:12:44 +01:00
Carsten Leue
211340952b Dependency injection (#81)
* fix: checkin

Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>

* fix: add initial DI implementation

Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>

* fix: add multi provider

Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>

* fix: simplify DI implementation

Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>

* fix: simplify provider

Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>

* fix: add Switch to function package

Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>

* fix: add DI

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>

---------

Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-11-25 16:56:39 +01:00
Carsten Leue
56860425c4 fix: add Flip function (#80)
Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>
2023-11-12 13:34:27 +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
109 changed files with 5860 additions and 254 deletions

View File

@@ -33,7 +33,7 @@ jobs:
with:
fetch-depth: 0
- name: Set up go ${{ matrix.go-version }}
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
-
@@ -60,12 +60,12 @@ jobs:
fetch-depth: 0
- name: Set up Node.js ${{ env.NODE_VERSION }}
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
with:
node-version: ${{ env.NODE_VERSION }}
- name: Set up go ${{env.GO_VERSION}}
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ${{env.GO_VERSION}}

3
.gitignore vendored
View File

@@ -1,3 +1,4 @@
fp-go.exe
main.exe
build/
build/
.idea

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

@@ -17,6 +17,7 @@ package array
import (
G "github.com/IBM/fp-go/array/generic"
EM "github.com/IBM/fp-go/endomorphism"
F "github.com/IBM/fp-go/function"
"github.com/IBM/fp-go/internal/array"
M "github.com/IBM/fp-go/monoid"
@@ -52,6 +53,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 +65,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 +89,38 @@ func filterMapRef[A, B any](fa []A, pred func(a *A) bool, f func(a *A) B) []B {
return result
}
func Filter[A any](pred func(A) bool) func([]A) []A {
return F.Bind2nd(filter[A], pred)
// Filter returns a new array with all elements from the original array that match a predicate
func Filter[A any](pred func(A) bool) EM.Endomorphism[[]A] {
return G.Filter[[]A](pred)
}
func FilterRef[A any](pred func(*A) bool) func([]A) []A {
// 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) EM.Endomorphism[[]A] {
return G.FilterWithIndex[[]A](pred)
}
func FilterRef[A any](pred func(*A) bool) EM.Endomorphism[[]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 +142,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 {
@@ -210,7 +228,7 @@ func Last[A any](as []A) O.Option[A] {
return G.Last(as)
}
func PrependAll[A any](middle A) func([]A) []A {
func PrependAll[A any](middle A) EM.Endomorphism[[]A] {
return func(as []A) []A {
count := len(as)
dst := count * 2
@@ -225,7 +243,7 @@ func PrependAll[A any](middle A) func([]A) []A {
}
}
func Intersperse[A any](middle A) func([]A) []A {
func Intersperse[A any](middle A) EM.Endomorphism[[]A] {
prepend := PrependAll(middle)
return func(as []A) []A {
if IsEmpty(as) {
@@ -254,7 +272,7 @@ func Lookup[A any](idx int) func([]A) O.Option[A] {
return G.Lookup[[]A](idx)
}
func UpsertAt[A any](a A) func([]A) []A {
func UpsertAt[A any](a A) EM.Endomorphism[[]A] {
return G.UpsertAt[[]A](a)
}
@@ -287,26 +305,37 @@ func ConstNil[A any]() []A {
return array.ConstNil[[]A]()
}
func SliceRight[A any](start int) func([]A) []A {
func SliceRight[A any](start int) EM.Endomorphism[[]A] {
return G.SliceRight[[]A](start)
}
// Copy creates a shallow copy of the array
func Copy[A any](b []A) []A {
return G.Copy(b)
}
// Clone creates a deep copy of the array using the provided endomorphism to clone the values
func Clone[A any](f func(A) A) func(as []A) []A {
return G.Clone[[]A](f)
}
// FoldMap maps and folds an array. Map the Array passing each value to the iterating function. Then fold the results using the provided Monoid.
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)
}
func Push[A any](a A) func([]A) []A {
return G.Push[[]A](a)
func Push[A any](a A) EM.Endomorphism[[]A] {
return G.Push[EM.Endomorphism[[]A]](a)
}
func MonadFlap[B, A any](fab []func(A) B, a A) []B {
@@ -316,3 +345,7 @@ func MonadFlap[B, A any](fab []func(A) B, a A) []B {
func Flap[B, A any](a A) func([]func(A) B) []B {
return G.Flap[func(A) B, []func(A) B, []B, A, B](a)
}
func Prepend[A any](head A) EM.Endomorphism[[]A] {
return G.Prepend[EM.Endomorphism[[]A]](head)
}

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]()
@@ -232,6 +304,11 @@ func Copy[AS ~[]A, A any](b AS) AS {
return buf
}
func Clone[AS ~[]A, A any](f func(A) A) func(as AS) AS {
// implementation assumes that map does not optimize for the empty array
return Map[AS, AS](f)
}
func FoldMap[AS ~[]A, A, B any](m M.Monoid[B]) func(func(A) B) func(AS) B {
return func(f func(A) B) func(AS) B {
return func(as AS) B {
@@ -242,13 +319,23 @@ 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())
}
}
func Push[GA ~[]A, A any](a A) func(GA) GA {
func Push[ENDO ~func(GA) GA, GA ~[]A, A any](a A) ENDO {
return F.Bind2nd(array.Push[GA, A], a)
}
@@ -259,3 +346,7 @@ func MonadFlap[FAB ~func(A) B, GFAB ~[]FAB, GB ~[]B, A, B any](fab GFAB, a A) GB
func Flap[FAB ~func(A) B, GFAB ~[]FAB, GB ~[]B, A, B any](a A) func(GFAB) GB {
return F.Bind2nd(MonadFlap[FAB, GFAB, GB, A, B], a)
}
func Prepend[ENDO ~func(AS) AS, AS []A, A any](head A) ENDO {
return array.Prepend[ENDO](head)
}

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))
}

32
array/generic/uniq.go Normal file
View File

@@ -0,0 +1,32 @@
package generic
import F "github.com/IBM/fp-go/function"
// StrictUniq converts an array of arbitrary items into an array or unique items
// where uniqueness is determined by the built-in uniqueness constraint
func StrictUniq[AS ~[]A, A comparable](as AS) AS {
return Uniq[AS](F.Identity[A])(as)
}
// uniquePredUnsafe returns a predicate on a map for uniqueness
func uniquePredUnsafe[PRED ~func(A) K, A any, K comparable](f PRED) func(int, A) bool {
lookup := make(map[K]bool)
return func(_ int, a A) bool {
k := f(a)
_, has := lookup[k]
if has {
return false
}
lookup[k] = true
return true
}
}
// Uniq converts an array of arbitrary items into an array or unique items
// where uniqueness is determined based on a key extractor function
func Uniq[AS ~[]A, PRED ~func(A) K, A any, K comparable](f PRED) func(as AS) AS {
return func(as AS) AS {
// we need to create a new predicate for each iteration
return filterWithIndex(as, uniquePredUnsafe(f))
}
}

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)
}

View File

@@ -17,6 +17,7 @@ package nonempty
import (
G "github.com/IBM/fp-go/array/generic"
EM "github.com/IBM/fp-go/endomorphism"
F "github.com/IBM/fp-go/function"
"github.com/IBM/fp-go/internal/array"
S "github.com/IBM/fp-go/semigroup"
@@ -65,6 +66,12 @@ func Reduce[A, B any](f func(B, A) B, initial B) func(NonEmptyArray[A]) B {
}
}
func ReduceRight[A, B any](f func(A, B) B, initial B) func(NonEmptyArray[A]) B {
return func(as NonEmptyArray[A]) B {
return array.ReduceRight(as, f, initial)
}
}
func Tail[A any](as NonEmptyArray[A]) []A {
return as[1:]
}
@@ -122,3 +129,8 @@ func Fold[A any](s S.Semigroup[A]) func(NonEmptyArray[A]) A {
return array.Reduce(Tail(as), s.Concat, Head(as))
}
}
// Prepend prepends a single value to an array
func Prepend[A any](head A) EM.Endomorphism[NonEmptyArray[A]] {
return array.Prepend[EM.Endomorphism[NonEmptyArray[A]]](head)
}

17
array/uniq.go Normal file
View File

@@ -0,0 +1,17 @@
package array
import (
G "github.com/IBM/fp-go/array/generic"
)
// StrictUniq converts an array of arbitrary items into an array or unique items
// where uniqueness is determined by the built-in uniqueness constraint
func StrictUniq[A comparable](as []A) []A {
return G.StrictUniq[[]A](as)
}
// Uniq converts an array of arbitrary items into an array or unique items
// where uniqueness is determined based on a key extractor function
func Uniq[A any, K comparable](f func(A) K) func(as []A) []A {
return G.Uniq[[]A](f)
}

14
array/uniq_test.go Normal file
View File

@@ -0,0 +1,14 @@
package array
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestUniq(t *testing.T) {
data := From(1, 2, 3, 2, 4, 1)
uniq := StrictUniq(data)
assert.Equal(t, From(1, 2, 3, 4), uniq)
}

View File

@@ -34,5 +34,6 @@ func Commands() []*C.Command {
IOEitherCommand(),
IOCommand(),
IOOptionCommand(),
DICommand(),
}
}

231
cli/di.go Normal file
View File

@@ -0,0 +1,231 @@
// 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 cli
import (
"fmt"
"log"
"os"
"path/filepath"
"time"
C "github.com/urfave/cli/v2"
)
func generateMakeProvider(f *os.File, i int) {
// non generic version
fmt.Fprintf(f, "\n// MakeProvider%d creates a [DIE.Provider] for an [InjectionToken] from a function with %d dependencies\n", i, i)
fmt.Fprintf(f, "func MakeProvider%d[", i)
for j := 0; j < i; j++ {
if j > 0 {
fmt.Fprintf(f, ", ")
}
fmt.Fprintf(f, "T%d", j+1)
}
fmt.Fprintf(f, " any, R any](\n")
fmt.Fprintf(f, " token InjectionToken[R],\n")
for j := 0; j < i; j++ {
fmt.Fprintf(f, " d%d Dependency[T%d],\n", j+1, j+1)
}
fmt.Fprintf(f, " f func(")
for j := 0; j < i; j++ {
if j > 0 {
fmt.Fprintf(f, ", ")
}
fmt.Fprintf(f, "T%d", j+1)
}
fmt.Fprintf(f, ") IOE.IOEither[error, R],\n")
fmt.Fprintf(f, ") DIE.Provider {\n")
fmt.Fprint(f, " return DIE.MakeProvider(\n")
fmt.Fprint(f, " token,\n")
fmt.Fprintf(f, " MakeProviderFactory%d(\n", i)
for j := 0; j < i; j++ {
fmt.Fprintf(f, " d%d,\n", j+1)
}
fmt.Fprint(f, " f,\n")
fmt.Fprint(f, " ))\n")
fmt.Fprintf(f, "}\n")
}
func generateMakeTokenWithDefault(f *os.File, i int) {
// non generic version
fmt.Fprintf(f, "\n// MakeTokenWithDefault%d creates an [InjectionToken] with a default implementation with %d dependencies\n", i, i)
fmt.Fprintf(f, "func MakeTokenWithDefault%d[", i)
for j := 0; j < i; j++ {
if j > 0 {
fmt.Fprintf(f, ", ")
}
fmt.Fprintf(f, "T%d", j+1)
}
fmt.Fprintf(f, " any, R any](\n")
fmt.Fprintf(f, " name string,\n")
for j := 0; j < i; j++ {
fmt.Fprintf(f, " d%d Dependency[T%d],\n", j+1, j+1)
}
fmt.Fprintf(f, " f func(")
for j := 0; j < i; j++ {
if j > 0 {
fmt.Fprintf(f, ", ")
}
fmt.Fprintf(f, "T%d", j+1)
}
fmt.Fprintf(f, ") IOE.IOEither[error, R],\n")
fmt.Fprintf(f, ") InjectionToken[R] {\n")
fmt.Fprintf(f, " return MakeTokenWithDefault[R](name, MakeProviderFactory%d(\n", i)
for j := 0; j < i; j++ {
fmt.Fprintf(f, " d%d,\n", j+1)
}
fmt.Fprint(f, " f,\n")
fmt.Fprint(f, " ))\n")
fmt.Fprintf(f, "}\n")
}
func generateMakeProviderFactory(f *os.File, i int) {
// non generic version
fmt.Fprintf(f, "\n// MakeProviderFactory%d creates a [DIE.ProviderFactory] from a function with %d arguments and %d dependencies\n", i, i, i)
fmt.Fprintf(f, "func MakeProviderFactory%d[", i)
for j := 0; j < i; j++ {
if j > 0 {
fmt.Fprintf(f, ", ")
}
fmt.Fprintf(f, "T%d", j+1)
}
fmt.Fprintf(f, " any, R any](\n")
for j := 0; j < i; j++ {
fmt.Fprintf(f, " d%d Dependency[T%d],\n", j+1, j+1)
}
fmt.Fprintf(f, " f func(")
for j := 0; j < i; j++ {
if j > 0 {
fmt.Fprintf(f, ", ")
}
fmt.Fprintf(f, "T%d", j+1)
}
fmt.Fprintf(f, ") IOE.IOEither[error, R],\n")
fmt.Fprintf(f, ") DIE.ProviderFactory {\n")
fmt.Fprint(f, " return DIE.MakeProviderFactory(\n")
fmt.Fprint(f, " A.From[DIE.Dependency](\n")
for j := 0; j < i; j++ {
fmt.Fprintf(f, " d%d,\n", j+1)
}
fmt.Fprint(f, " ),\n")
fmt.Fprintf(f, " eraseProviderFactory%d(\n", i)
for j := 0; j < i; j++ {
fmt.Fprintf(f, " d%d,\n", j+1)
}
fmt.Fprint(f, " f,\n")
fmt.Fprint(f, " ),\n")
fmt.Fprint(f, " )\n")
fmt.Fprintf(f, "}\n")
}
func generateEraseProviderFactory(f *os.File, i int) {
// non generic version
fmt.Fprintf(f, "\n// eraseProviderFactory%d creates a function that takes a variadic number of untyped arguments and from a function of %d strongly typed arguments and %d dependencies\n", i, i, i)
fmt.Fprintf(f, "func eraseProviderFactory%d[", i)
for j := 0; j < i; j++ {
if j > 0 {
fmt.Fprintf(f, ", ")
}
fmt.Fprintf(f, "T%d", j+1)
}
fmt.Fprintf(f, " any, R any](\n")
for j := 0; j < i; j++ {
fmt.Fprintf(f, " d%d Dependency[T%d],\n", j+1, j+1)
}
fmt.Fprintf(f, " f func(")
for j := 0; j < i; j++ {
if j > 0 {
fmt.Fprintf(f, ", ")
}
fmt.Fprintf(f, "T%d", j+1)
}
fmt.Fprintf(f, ") IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] {\n")
fmt.Fprintf(f, " ft := eraseTuple(T.Tupled%d(f))\n", i)
for j := 0; j < i; j++ {
fmt.Fprintf(f, " t%d := lookupAt[T%d](%d, d%d)\n", j+1, j+1, j, j+1)
}
fmt.Fprint(f, " return func(params ...any) IOE.IOEither[error, any] {\n")
fmt.Fprintf(f, " return ft(E.SequenceT%d(\n", i)
for j := 0; j < i; j++ {
fmt.Fprintf(f, " t%d(params),\n", j+1)
}
fmt.Fprint(f, " ))\n")
fmt.Fprint(f, " }\n")
fmt.Fprintf(f, "}\n")
}
func generateDIHelpers(filename string, count int) error {
dir, err := os.Getwd()
if err != nil {
return err
}
absDir, err := filepath.Abs(dir)
if err != nil {
return err
}
pkg := filepath.Base(absDir)
f, err := os.Create(filepath.Clean(filename))
if err != nil {
return err
}
defer f.Close()
// log
log.Printf("Generating code in [%s] for package [%s] with [%d] repetitions ...", filename, pkg, count)
// some header
fmt.Fprintln(f, "// Code generated by go generate; DO NOT EDIT.")
fmt.Fprintln(f, "// This file was generated by robots at")
fmt.Fprintf(f, "// %s\n\n", time.Now())
fmt.Fprintf(f, "package %s\n\n", pkg)
fmt.Fprint(f, `
import (
E "github.com/IBM/fp-go/either"
IOE "github.com/IBM/fp-go/ioeither"
T "github.com/IBM/fp-go/tuple"
A "github.com/IBM/fp-go/array"
DIE "github.com/IBM/fp-go/di/erasure"
)
`)
for i := 1; i <= count; i++ {
generateEraseProviderFactory(f, i)
generateMakeProviderFactory(f, i)
generateMakeTokenWithDefault(f, i)
generateMakeProvider(f, i)
}
return nil
}
func DICommand() *C.Command {
return &C.Command{
Name: "di",
Usage: "generate code for the dependency injection package",
Flags: []C.Flag{
flagCount,
flagFilename,
},
Action: func(ctx *C.Context) error {
return generateDIHelpers(
ctx.String(keyFilename),
ctx.Int(keyCount),
)
},
}
}

View File

@@ -295,15 +295,16 @@ func recurseCurry(f *os.File, indent string, total, count int) {
func generateCurry(f *os.File, i int) {
// Create the curry version
fmt.Fprintf(f, "\n// Curry%d takes a function with %d parameters and returns a cascade of functions each taking only one parameter.\n// The inverse function is [Uncurry%d]\n", i, i, i)
fmt.Fprintf(f, "func Curry%d[T0", i)
for j := 1; j <= i; j++ {
fmt.Fprintf(f, "func Curry%d[FCT ~func(T0", i)
for j := 1; j < i; j++ {
fmt.Fprintf(f, ", T%d", j)
}
fmt.Fprintf(f, " any](f func(T0")
for j := 2; j <= i; j++ {
fmt.Fprintf(f, ", T%d", j-1)
fmt.Fprintf(f, ") T%d", i)
// type arguments
for j := 0; j <= i; j++ {
fmt.Fprintf(f, ", T%d", j)
}
fmt.Fprintf(f, ") T%d) func(T0)", i)
fmt.Fprintf(f, " any](f FCT) func(T0)")
for j := 2; j <= i; j++ {
fmt.Fprintf(f, " func(T%d)", j-1)
}
@@ -315,15 +316,16 @@ func generateCurry(f *os.File, i int) {
func generateUncurry(f *os.File, i int) {
// Create the uncurry version
fmt.Fprintf(f, "\n// Uncurry%d takes a cascade of %d functions each taking only one parameter and returns a function with %d parameters .\n// The inverse function is [Curry%d]\n", i, i, i, i)
fmt.Fprintf(f, "func Uncurry%d[T0", i)
for j := 1; j <= i; j++ {
fmt.Fprintf(f, "func Uncurry%d[FCT ~func(T0)", i)
for j := 1; j < i; j++ {
fmt.Fprintf(f, " func(T%d)", j)
}
fmt.Fprintf(f, " T%d", i)
// the type parameters
for j := 0; j <= i; j++ {
fmt.Fprintf(f, ", T%d", j)
}
fmt.Fprintf(f, " any](f")
for j := 1; j <= i; j++ {
fmt.Fprintf(f, " func(T%d)", j-1)
}
fmt.Fprintf(f, " T%d) func(", i)
fmt.Fprintf(f, " any](f FCT) func(")
for j := 1; j <= i; j++ {
if j > 1 {
fmt.Fprintf(f, ", ")

View File

@@ -166,3 +166,289 @@ func SequenceRecord[K comparable,
return MonadTraverseRecord[K, GAS, GRAS](ma, F.Identity[GRA])
}
// MonadTraverseArraySeq transforms an array
func MonadTraverseArraySeq[
AS ~[]A,
GRBS ~func(context.Context) GIOBS,
GRB ~func(context.Context) GIOB,
GIOBS ~func() E.Either[error, BS],
GIOB ~func() E.Either[error, B],
BS ~[]B,
A, B any](as AS, f func(A) GRB) GRBS {
return RA.MonadTraverse[AS](
Of[GRBS, GIOBS, BS],
Map[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GIOBS, func() E.Either[error, func(B) BS], BS, func(B) BS],
ApSeq[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GRB],
as, f,
)
}
// TraverseArraySeq transforms an array
func TraverseArraySeq[
AS ~[]A,
GRBS ~func(context.Context) GIOBS,
GRB ~func(context.Context) GIOB,
GIOBS ~func() E.Either[error, BS],
GIOB ~func() E.Either[error, B],
BS ~[]B,
A, B any](f func(A) GRB) func(AS) GRBS {
return RA.Traverse[AS](
Of[GRBS, GIOBS, BS],
Map[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GIOBS, func() E.Either[error, func(B) BS], BS, func(B) BS],
ApSeq[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GRB],
f,
)
}
// TraverseArrayWithIndexSeq transforms an array
func TraverseArrayWithIndexSeq[
AS ~[]A,
GRBS ~func(context.Context) GIOBS,
GRB ~func(context.Context) GIOB,
GIOBS ~func() E.Either[error, BS],
GIOB ~func() E.Either[error, B],
BS ~[]B,
A, B any](f func(int, A) GRB) func(AS) GRBS {
return RA.TraverseWithIndex[AS](
Of[GRBS, GIOBS, BS],
Map[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GIOBS, func() E.Either[error, func(B) BS], BS, func(B) BS],
ApSeq[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GRB],
f,
)
}
// SequenceArraySeq converts a homogeneous sequence of either into an either of sequence
func SequenceArraySeq[
AS ~[]A,
GAS ~[]GRA,
GRAS ~func(context.Context) GIOAS,
GRA ~func(context.Context) GIOA,
GIOAS ~func() E.Either[error, AS],
GIOA ~func() E.Either[error, A],
A any](ma GAS) GRAS {
return MonadTraverseArraySeq[GAS, GRAS](ma, F.Identity[GRA])
}
// MonadTraverseRecordSeq transforms a record
func MonadTraverseRecordSeq[K comparable,
AS ~map[K]A,
GRBS ~func(context.Context) GIOBS,
GRB ~func(context.Context) GIOB,
GIOBS ~func() E.Either[error, BS],
GIOB ~func() E.Either[error, B],
BS ~map[K]B,
A, B any](ma AS, f func(A) GRB) GRBS {
return RR.MonadTraverse[AS](
Of[GRBS, GIOBS, BS],
Map[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GIOBS, func() E.Either[error, func(B) BS], BS, func(B) BS],
ApSeq[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GRB],
ma, f,
)
}
// TraverseRecordSeq transforms a record
func TraverseRecordSeq[K comparable,
AS ~map[K]A,
GRBS ~func(context.Context) GIOBS,
GRB ~func(context.Context) GIOB,
GIOBS ~func() E.Either[error, BS],
GIOB ~func() E.Either[error, B],
BS ~map[K]B,
A, B any](f func(A) GRB) func(AS) GRBS {
return RR.Traverse[AS](
Of[GRBS, GIOBS, BS],
Map[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GIOBS, func() E.Either[error, func(B) BS], BS, func(B) BS],
ApSeq[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GRB],
f,
)
}
// TraverseRecordWithIndexSeq transforms a record
func TraverseRecordWithIndexSeq[K comparable,
AS ~map[K]A,
GRBS ~func(context.Context) GIOBS,
GRB ~func(context.Context) GIOB,
GIOBS ~func() E.Either[error, BS],
GIOB ~func() E.Either[error, B],
BS ~map[K]B,
A, B any](f func(K, A) GRB) func(AS) GRBS {
return RR.TraverseWithIndex[AS](
Of[GRBS, GIOBS, BS],
Map[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GIOBS, func() E.Either[error, func(B) BS], BS, func(B) BS],
ApSeq[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GRB],
f,
)
}
// SequenceRecordSeq converts a homogeneous sequence of either into an either of sequence
func SequenceRecordSeq[K comparable,
AS ~map[K]A,
GAS ~map[K]GRA,
GRAS ~func(context.Context) GIOAS,
GRA ~func(context.Context) GIOA,
GIOAS ~func() E.Either[error, AS],
GIOA ~func() E.Either[error, A],
A any](ma GAS) GRAS {
return MonadTraverseRecordSeq[K, GAS, GRAS](ma, F.Identity[GRA])
}
// MonadTraverseArrayPar transforms an array
func MonadTraverseArrayPar[
AS ~[]A,
GRBS ~func(context.Context) GIOBS,
GRB ~func(context.Context) GIOB,
GIOBS ~func() E.Either[error, BS],
GIOB ~func() E.Either[error, B],
BS ~[]B,
A, B any](as AS, f func(A) GRB) GRBS {
return RA.MonadTraverse[AS](
Of[GRBS, GIOBS, BS],
Map[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GIOBS, func() E.Either[error, func(B) BS], BS, func(B) BS],
ApPar[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GRB],
as, f,
)
}
// TraverseArrayPar transforms an array
func TraverseArrayPar[
AS ~[]A,
GRBS ~func(context.Context) GIOBS,
GRB ~func(context.Context) GIOB,
GIOBS ~func() E.Either[error, BS],
GIOB ~func() E.Either[error, B],
BS ~[]B,
A, B any](f func(A) GRB) func(AS) GRBS {
return RA.Traverse[AS](
Of[GRBS, GIOBS, BS],
Map[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GIOBS, func() E.Either[error, func(B) BS], BS, func(B) BS],
ApPar[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GRB],
f,
)
}
// TraverseArrayWithIndexPar transforms an array
func TraverseArrayWithIndexPar[
AS ~[]A,
GRBS ~func(context.Context) GIOBS,
GRB ~func(context.Context) GIOB,
GIOBS ~func() E.Either[error, BS],
GIOB ~func() E.Either[error, B],
BS ~[]B,
A, B any](f func(int, A) GRB) func(AS) GRBS {
return RA.TraverseWithIndex[AS](
Of[GRBS, GIOBS, BS],
Map[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GIOBS, func() E.Either[error, func(B) BS], BS, func(B) BS],
ApPar[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GRB],
f,
)
}
// SequenceArrayPar converts a homogeneous sequence of either into an either of sequence
func SequenceArrayPar[
AS ~[]A,
GAS ~[]GRA,
GRAS ~func(context.Context) GIOAS,
GRA ~func(context.Context) GIOA,
GIOAS ~func() E.Either[error, AS],
GIOA ~func() E.Either[error, A],
A any](ma GAS) GRAS {
return MonadTraverseArrayPar[GAS, GRAS](ma, F.Identity[GRA])
}
// MonadTraverseRecordPar transforms a record
func MonadTraverseRecordPar[K comparable,
AS ~map[K]A,
GRBS ~func(context.Context) GIOBS,
GRB ~func(context.Context) GIOB,
GIOBS ~func() E.Either[error, BS],
GIOB ~func() E.Either[error, B],
BS ~map[K]B,
A, B any](ma AS, f func(A) GRB) GRBS {
return RR.MonadTraverse[AS](
Of[GRBS, GIOBS, BS],
Map[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GIOBS, func() E.Either[error, func(B) BS], BS, func(B) BS],
ApPar[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GRB],
ma, f,
)
}
// TraverseRecordPar transforms a record
func TraverseRecordPar[K comparable,
AS ~map[K]A,
GRBS ~func(context.Context) GIOBS,
GRB ~func(context.Context) GIOB,
GIOBS ~func() E.Either[error, BS],
GIOB ~func() E.Either[error, B],
BS ~map[K]B,
A, B any](f func(A) GRB) func(AS) GRBS {
return RR.Traverse[AS](
Of[GRBS, GIOBS, BS],
Map[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GIOBS, func() E.Either[error, func(B) BS], BS, func(B) BS],
ApPar[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GRB],
f,
)
}
// TraverseRecordWithIndexPar transforms a record
func TraverseRecordWithIndexPar[K comparable,
AS ~map[K]A,
GRBS ~func(context.Context) GIOBS,
GRB ~func(context.Context) GIOB,
GIOBS ~func() E.Either[error, BS],
GIOB ~func() E.Either[error, B],
BS ~map[K]B,
A, B any](f func(K, A) GRB) func(AS) GRBS {
return RR.TraverseWithIndex[AS](
Of[GRBS, GIOBS, BS],
Map[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GIOBS, func() E.Either[error, func(B) BS], BS, func(B) BS],
ApPar[GRBS, func(context.Context) func() E.Either[error, func(B) BS], GRB],
f,
)
}
// SequenceRecordPar converts a homogeneous sequence of either into an either of sequence
func SequenceRecordPar[K comparable,
AS ~map[K]A,
GAS ~map[K]GRA,
GRAS ~func(context.Context) GIOAS,
GRA ~func(context.Context) GIOA,
GIOAS ~func() E.Either[error, AS],
GIOA ~func() E.Either[error, A],
A any](ma GAS) GRAS {
return MonadTraverseRecordPar[K, GAS, GRAS](ma, F.Identity[GRA])
}

View File

@@ -48,3 +48,63 @@ func TraverseRecordWithIndex[K comparable, A, B any](f func(K, A) ReaderIOEither
func SequenceRecord[K comparable, A any](ma map[K]ReaderIOEither[A]) ReaderIOEither[map[K]A] {
return G.SequenceRecord[K, map[K]A, map[K]ReaderIOEither[A], ReaderIOEither[map[K]A]](ma)
}
// TraverseArraySeq uses transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]]
func TraverseArraySeq[A, B any](f func(A) ReaderIOEither[B]) func([]A) ReaderIOEither[[]B] {
return G.TraverseArraySeq[[]A, ReaderIOEither[[]B]](f)
}
// TraverseArrayWithIndexSeq uses transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]]
func TraverseArrayWithIndexSeq[A, B any](f func(int, A) ReaderIOEither[B]) func([]A) ReaderIOEither[[]B] {
return G.TraverseArrayWithIndexSeq[[]A, ReaderIOEither[[]B]](f)
}
// SequenceArraySeq converts a homogeneous sequence of either into an either of sequence
func SequenceArraySeq[A any](ma []ReaderIOEither[A]) ReaderIOEither[[]A] {
return G.SequenceArraySeq[[]A, []ReaderIOEither[A], ReaderIOEither[[]A]](ma)
}
// TraverseRecordSeq uses transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]]
func TraverseRecordSeq[K comparable, A, B any](f func(A) ReaderIOEither[B]) func(map[K]A) ReaderIOEither[map[K]B] {
return G.TraverseRecordSeq[K, map[K]A, ReaderIOEither[map[K]B]](f)
}
// TraverseRecordWithIndexSeq uses transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]]
func TraverseRecordWithIndexSeq[K comparable, A, B any](f func(K, A) ReaderIOEither[B]) func(map[K]A) ReaderIOEither[map[K]B] {
return G.TraverseRecordWithIndexSeq[K, map[K]A, ReaderIOEither[map[K]B]](f)
}
// SequenceRecordSeq converts a homogeneous sequence of either into an either of sequence
func SequenceRecordSeq[K comparable, A any](ma map[K]ReaderIOEither[A]) ReaderIOEither[map[K]A] {
return G.SequenceRecordSeq[K, map[K]A, map[K]ReaderIOEither[A], ReaderIOEither[map[K]A]](ma)
}
// TraverseArrayPar uses transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]]
func TraverseArrayPar[A, B any](f func(A) ReaderIOEither[B]) func([]A) ReaderIOEither[[]B] {
return G.TraverseArrayPar[[]A, ReaderIOEither[[]B]](f)
}
// TraverseArrayWithIndexPar uses transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]]
func TraverseArrayWithIndexPar[A, B any](f func(int, A) ReaderIOEither[B]) func([]A) ReaderIOEither[[]B] {
return G.TraverseArrayWithIndexPar[[]A, ReaderIOEither[[]B]](f)
}
// SequenceArrayPar converts a homogeneous sequence of either into an either of sequence
func SequenceArrayPar[A any](ma []ReaderIOEither[A]) ReaderIOEither[[]A] {
return G.SequenceArrayPar[[]A, []ReaderIOEither[A], ReaderIOEither[[]A]](ma)
}
// TraverseRecordPar uses transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]]
func TraverseRecordPar[K comparable, A, B any](f func(A) ReaderIOEither[B]) func(map[K]A) ReaderIOEither[map[K]B] {
return G.TraverseRecordPar[K, map[K]A, ReaderIOEither[map[K]B]](f)
}
// TraverseRecordWithIndexPar uses transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]]
func TraverseRecordWithIndexPar[K comparable, A, B any](f func(K, A) ReaderIOEither[B]) func(map[K]A) ReaderIOEither[map[K]B] {
return G.TraverseRecordWithIndexPar[K, map[K]A, ReaderIOEither[map[K]B]](f)
}
// SequenceRecordPar converts a homogeneous sequence of either into an either of sequence
func SequenceRecordPar[K comparable, A any](ma map[K]ReaderIOEither[A]) ReaderIOEither[map[K]A] {
return G.SequenceRecordPar[K, map[K]A, map[K]ReaderIOEither[A], ReaderIOEither[map[K]A]](ma)
}

38
di/app.go Normal file
View File

@@ -0,0 +1,38 @@
// 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"
IO "github.com/IBM/fp-go/io"
IOE "github.com/IBM/fp-go/ioeither"
)
var (
// InjMain is the [InjectionToken] for the main application
InjMain = MakeToken[any]("APP")
// Main is the resolver for the main application
Main = Resolve(InjMain)
)
// RunMain runs the main application from a set of [DIE.Provider]s
var RunMain = F.Flow3(
DIE.MakeInjector,
Main,
IOE.Fold(IO.Of[error], F.Constant1[any](IO.Of[error](nil))),
)

43
di/doc.go Normal file
View File

@@ -0,0 +1,43 @@
// 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 implements functions and data types supporting dependency injection patterns
//
// The fundamental building block is the concept of a [Dependency]. This describes the abstract concept of a function, service or value together with its type.
// Examples for dependencies can be as simple as configuration values such as the API URL for a service, the current username, a [Dependency] could be the map
// of the configuration environment, an http client or as complex as a service interface. Important is that a [Dependency] only defines the concept but
// not the implementation.
//
// The implementation of a [Dependency] is called a [Provider], the dependency of an `API URL` could e.g. be realized by a provider that consults the environment to read the information
// or a config file or simply hardcode it.
// In many cases the implementation of a [Provider] depends in turn on other [Dependency] (but never directly on other [Provider]s), a provider for an `API URL` that reads
// the information from the environment would e.g. depend on a [Dependency] that represents this environment.
//
// It is the resposibility of the [InjectableFactory] to create an instance of a [Dependency]. All instances are considered singletons. Create an [InjectableFactory] via the [MakeInjector] method. Use [Resolve] to create a strongly typed
// factory for a particular [InjectionToken].
//
// In most cases it is not required to use [InjectableFactory] directly, instead you would create a [Provider] via the [MakeProvider2] method (suffix indicates the number of dependencies). In this call
// you give a number of (strongly typed) [Dependency] identifiers and a (strongly typed) factory function for the implementation. The dependency injection framework makes
// sure to resolve the dependencies before calling the factory method.
//
// For convenience purposes it can be helpful to attach a default implementation of a [Dependency]. In this case use the [MakeTokenWithDefault2] method (suffix indicates the number of dependencies)
// to define the [InjectionToken].
//
// [Provider]: [github.com/IBM/fp-go/di/erasure.Provider]
// [InjectableFactory]: [github.com/IBM/fp-go/di/erasure.InjectableFactory]
// [MakeInjector]: [github.com/IBM/fp-go/di/erasure.MakeInjector]
package di
//go:generate go run .. di --count 10 --filename gen.go

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

@@ -0,0 +1,170 @@
// 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
//
// The resulting [InjectableFactory] can then be used to retrieve service instances given their [Dependency]. The implementation
// makes sure to transitively resolve the required dependencies.
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
}

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

@@ -0,0 +1,181 @@
// 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 is a factory function that can create an untyped instance of a service based on its [Dependency] identifier
InjectableFactory = func(Dependency) IOE.IOEither[error, any]
ProviderFactory = func(InjectableFactory) IOE.IOEither[error, any]
paramIndex = map[int]int
paramValue = map[int]any
handler = func(paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue]
mapping = map[int]paramIndex
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
}
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 (
// Empty is the empty array of providers
Empty = A.Empty[Provider]()
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])
mapDeps = F.Curry2(A.MonadMap[Dependency, IOE.IOEither[error, 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),
IOE.ToIOOption[error, any],
F.ToAny[IOO.IOOption[any]],
)),
IOE.Of[error, paramValue],
)
}
},
}
)
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(
mapDeps(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]
}

1013
di/gen.go Normal file

File diff suppressed because it is too large Load Diff

32
di/injector.go Normal file
View File

@@ -0,0 +1,32 @@
// 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),
)
}

79
di/provider.go Normal file
View File

@@ -0,0 +1,79 @@
// 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"
)
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 eraseTuple[A, R any](f func(A) IOE.IOEither[error, R]) func(E.Either[error, A]) IOE.IOEither[error, any] {
return F.Flow3(
IOE.FromEither[error, A],
IOE.Chain(f),
IOE.Map[error](F.ToAny[R]),
)
}
func eraseProviderFactory0[R any](f 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 MakeProviderFactory0[R any](
fct IOE.IOEither[error, R],
) DIE.ProviderFactory {
return DIE.MakeProviderFactory(
A.Empty[DIE.Dependency](),
eraseProviderFactory0(fct),
)
}
// MakeTokenWithDefault0 creates a unique [InjectionToken] for a specific type with an attached default [DIE.Provider]
func MakeTokenWithDefault0[R any](name string, fct IOE.IOEither[error, R]) InjectionToken[R] {
return MakeTokenWithDefault[R](name, MakeProviderFactory0(fct))
}
func MakeProvider0[R any](
token InjectionToken[R],
fct IOE.IOEither[error, R],
) DIE.Provider {
return DIE.MakeProvider(
token,
MakeProviderFactory0(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, IOE.Of[error](value))
}

346
di/provider_test.go Normal file
View File

@@ -0,0 +1,346 @@
// 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) 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) 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) 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", 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", 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)())
}

197
di/token.go Normal file
View File

@@ -0,0 +1,197 @@
// 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 that can have multiple implementations.
// Implementations are provided via the [MultiInjectionToken.Item] injection token.
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}
}

77
di/utils.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 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)),
)
}

84
di/utils_test.go Normal file
View File

@@ -0,0 +1,84 @@
// 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

@@ -61,14 +61,14 @@ func SequenceArray[E, A any](ma []Either[E, A]) Either[E, []A] {
return SequenceArrayG[[]A](ma)
}
// CompactArrayG discards the none values and keeps the some values
// CompactArrayG discards the none values and keeps the right values
func CompactArrayG[A1 ~[]Either[E, A], A2 ~[]A, E, A any](fa A1) A2 {
return RA.Reduce(fa, func(out A2, value Either[E, A]) A2 {
return MonadFold(value, F.Constant1[E](out), F.Bind1st(RA.Append[A2, A], out))
}, make(A2, len(fa)))
}, make(A2, 0, len(fa)))
}
// CompactArray discards the none values and keeps the some values
// CompactArray discards the none values and keeps the right values
func CompactArray[E, A any](fa []Either[E, A]) []A {
return CompactArrayG[[]Either[E, A], []A](fa)
}

18
either/array_test.go Normal file
View File

@@ -0,0 +1,18 @@
package either
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCompactArray(t *testing.T) {
ar := []Either[string, string]{
Of[string]("ok"),
Left[string]("err"),
Of[string]("ok"),
}
res := CompactArray(ar)
assert.Equal(t, 2, len(res))
}

View File

@@ -89,7 +89,7 @@ func MonadChainFirst[E, A, B any](ma Either[E, A], f func(a A) Either[E, B]) Eit
})
}
func MonadChainTo[E, A, B any](ma Either[E, A], mb Either[E, B]) Either[E, B] {
func MonadChainTo[A, E, B any](ma Either[E, A], mb Either[E, B]) Either[E, B] {
return mb
}
@@ -104,8 +104,8 @@ func ChainOptionK[A, B, E any](onNone func() E) func(func(A) O.Option[B]) func(E
}
}
func ChainTo[E, A, B any](mb Either[E, B]) func(Either[E, A]) Either[E, B] {
return F.Bind2nd(MonadChainTo[E, A, B], mb)
func ChainTo[A, E, B any](mb Either[E, B]) func(Either[E, A]) Either[E, B] {
return F.Bind2nd(MonadChainTo[A, E, B], mb)
}
func Chain[E, A, B any](f func(a A) Either[E, B]) func(Either[E, A]) Either[E, B] {

View File

@@ -41,7 +41,7 @@ func Logger[E, A any](loggers ...*log.Logger) func(string) func(Either[E, A]) Ei
return func(ma Either[E, A]) Either[E, A] {
return F.Pipe1(
delegate(ma),
ChainTo[E, A](ma),
ChainTo[A](ma),
)
}
}

30
endomorphism/curry.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 endomorphism
import (
G "github.com/IBM/fp-go/endomorphism/generic"
)
// Curry2 curries a binary function
func Curry2[FCT ~func(T0, T1) T1, T0, T1 any](f FCT) func(T0) Endomorphism[T1] {
return G.Curry2[Endomorphism[T1]](f)
}
// Curry3 curries a ternary function
func Curry3[FCT ~func(T0, T1, T2) T2, T0, T1, T2 any](f FCT) func(T0) func(T1) Endomorphism[T2] {
return G.Curry3[Endomorphism[T2]](f)
}

36
endomorphism/endo.go Normal file
View File

@@ -0,0 +1,36 @@
// 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 endomorphism
import (
G "github.com/IBM/fp-go/endomorphism/generic"
)
func MonadAp[A any](fab Endomorphism[A], fa A) A {
return G.MonadAp[Endomorphism[A]](fab, fa)
}
func Ap[A any](fa A) func(Endomorphism[A]) A {
return G.Ap[Endomorphism[A]](fa)
}
func MonadChain[A any](ma Endomorphism[A], f Endomorphism[A]) Endomorphism[A] {
return G.MonadChain[Endomorphism[A]](ma, f)
}
func Chain[A any](f Endomorphism[A]) Endomorphism[Endomorphism[A]] {
return G.Chain[Endomorphism[Endomorphism[A]], Endomorphism[A], A](f)
}

View File

@@ -0,0 +1,36 @@
// 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
// Curry2 is a duplicate of [F.Curry2] but because of the type system it's not compatible otherwise
func Curry2[GT1 ~func(T1) T1, FCT ~func(T0, T1) T1, T0, T1 any](f FCT) func(T0) GT1 {
return func(t0 T0) GT1 {
return func(t1 T1) T1 {
return f(t0, t1)
}
}
}
// Curry2 is a duplicate of [F.Curry2] but because of the type system it's not compatible otherwise
func Curry3[GT2 ~func(T2) T2, FCT ~func(T0, T1, T2) T2, T0, T1, T2 any](f FCT) func(T0) func(T1) GT2 {
return func(t0 T0) func(T1) GT2 {
return func(t1 T1) GT2 {
return func(t2 T2) T2 {
return f(t0, t1, t2)
}
}
}
}

View File

@@ -0,0 +1,37 @@
// 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"
I "github.com/IBM/fp-go/identity/generic"
)
func MonadAp[GA ~func(A) A, A any](fab GA, fa A) A {
return I.MonadAp[GA, A, A](fab, fa)
}
func Ap[GA ~func(A) A, A any](fa A) func(GA) A {
return I.Ap[GA, A, A](fa)
}
func MonadChain[GA ~func(A) A, A any](ma GA, f GA) GA {
return Compose(ma, f)
}
func Chain[ENDO ~func(GA) GA, GA ~func(A) A, A any](f GA) ENDO {
return Of[ENDO](F.Bind2nd(Compose[GA], f))
}

View File

@@ -0,0 +1,47 @@
// 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"
M "github.com/IBM/fp-go/monoid"
S "github.com/IBM/fp-go/semigroup"
)
// Of converts any function to an [Endomorphism]
func Of[ENDO ~func(A) A, F ~func(A) A, A any](f F) ENDO {
return func(a A) A {
return f(a)
}
}
func Identity[ENDO ~func(A) A, A any]() ENDO {
return Of[ENDO](F.Identity[A])
}
func Compose[ENDO ~func(A) A, A any](f1, f2 ENDO) ENDO {
return Of[ENDO](F.Flow2(f1, f2))
}
// Semigroup for the Endomorphism where the `concat` operation is the usual function composition.
func Semigroup[ENDO ~func(A) A, A any]() S.Semigroup[ENDO] {
return S.MakeSemigroup(Compose[ENDO])
}
// Monoid for the Endomorphism where the `concat` operation is the usual function composition.
func Monoid[ENDO ~func(A) A, A any]() M.Monoid[ENDO] {
return M.MakeMonoid(Compose[ENDO], Identity[ENDO]())
}

View File

@@ -16,17 +16,30 @@
package endomorphism
import (
F "github.com/IBM/fp-go/function"
G "github.com/IBM/fp-go/endomorphism/generic"
M "github.com/IBM/fp-go/monoid"
S "github.com/IBM/fp-go/semigroup"
)
// Endomorphism is a function that
type Endomorphism[A any] func(A) A
// Of converts any function to an [Endomorphism]
func Of[F ~func(A) A, A any](f F) Endomorphism[A] {
return G.Of[Endomorphism[A]](f)
}
// Identity returns the identity [Endomorphism]
func Identity[A any]() Endomorphism[A] {
return G.Identity[Endomorphism[A]]()
}
// 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(F.Flow2[func(A) A, func(A) A])
func Semigroup[A any]() S.Semigroup[Endomorphism[A]] {
return G.Semigroup[Endomorphism[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(F.Flow2[func(A) A, func(A) A], F.Identity[A])
func Monoid[A any]() M.Monoid[Endomorphism[A]] {
return G.Monoid[Endomorphism[A]]()
}

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

@@ -15,7 +15,10 @@
package file
import "path/filepath"
import (
"io"
"path/filepath"
)
// Join appends a filename to a root path
func Join(name string) func(root string) string {
@@ -23,3 +26,13 @@ func Join(name string) func(root string) string {
return filepath.Join(root, name)
}
}
// ToReader converts a [io.Reader]
func ToReader[R io.Reader](r R) io.Reader {
return r
}
// ToCloser converts a [io.Closer]
func ToCloser[C io.Closer](c C) io.Closer {
return c
}

View File

@@ -1,6 +1,6 @@
// Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at
// 2023-10-23 08:30:44.6474482 +0200 CEST m=+0.150851901
// 2023-12-18 09:38:59.1616876 +0100 CET m=+0.008641801
package function

View File

@@ -28,3 +28,14 @@ func Memoize[K comparable, T any](f func(K) T) func(K) T {
func ContramapMemoize[A any, K comparable, T any](kf func(A) K) func(func(A) T) func(A) T {
return G.ContramapMemoize[func(A) T](kf)
}
// CacheCallback converts a unary function into a unary function that caches the value depending on the parameter
func CacheCallback[
A any, K comparable, T any](kf func(A) K, getOrCreate func(K, func() func() T) func() T) func(func(A) T) func(A) T {
return G.CacheCallback[func(func(A) T) func(A) T](kf, getOrCreate)
}
// SingleElementCache creates a cache function for use with the [CacheCallback] method that has a maximum capacity of one single item
func SingleElementCache[K comparable, T any]() func(K, func() func() T) func() T {
return G.SingleElementCache[func() func() T, K]()
}

View File

@@ -16,6 +16,8 @@
package function
import (
"fmt"
"math/rand"
"testing"
"github.com/stretchr/testify/assert"
@@ -48,3 +50,21 @@ func TestCache(t *testing.T) {
assert.Equal(t, 10, cached(10))
assert.Equal(t, 2, count)
}
func TestSingleElementCache(t *testing.T) {
f := func(key string) string {
return fmt.Sprintf("%s: %d", key, rand.Int())
}
cb := CacheCallback(func(s string) string { return s }, SingleElementCache[string, string]())
cf := cb(f)
v1 := cf("1")
v2 := cf("1")
v3 := cf("2")
v4 := cf("1")
assert.Equal(t, v1, v2)
assert.NotEqual(t, v2, v3)
assert.NotEqual(t, v3, v4)
assert.NotEqual(t, v1, v4)
}

25
function/flip.go Normal file
View File

@@ -0,0 +1,25 @@
// 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
// Flip reverses the order of parameters of a curried function
func Flip[T1, T2, R any](f func(T1) func(T2) R) func(T2) func(T1) R {
return func(t2 T2) func(T1) R {
return func(t1 T1) R {
return f(t1)(t2)
}
}
}

36
function/flip_test.go Normal file
View File

@@ -0,0 +1,36 @@
// 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 (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFlip(t *testing.T) {
x := Curry2(func(a, b string) string {
return fmt.Sprintf("%s:%s", a, b)
})
assert.Equal(t, "a:b", x("a")("b"))
y := Flip(x)
assert.Equal(t, "b:a", y("a")("b"))
}

View File

@@ -1,6 +1,6 @@
// Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at
// 2023-10-23 08:30:41.7972101 +0200 CEST m=+0.008029101
// 2023-12-18 09:38:51.4946446 +0100 CET m=+0.008838401
package function
@@ -55,7 +55,7 @@ func Nullary1[F1 ~func() T1, T1 any](f1 F1) func() T1 {
// Curry1 takes a function with 1 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry1]
func Curry1[T0, T1 any](f func(T0) T1) func(T0) T1 {
func Curry1[FCT ~func(T0) T1, T0, T1 any](f FCT) func(T0) T1 {
return func(t0 T0) T1 {
return f(t0)
}
@@ -63,7 +63,7 @@ func Curry1[T0, T1 any](f func(T0) T1) func(T0) T1 {
// Uncurry1 takes a cascade of 1 functions each taking only one parameter and returns a function with 1 parameters .
// The inverse function is [Curry1]
func Uncurry1[T0, T1 any](f func(T0) T1) func(T0) T1 {
func Uncurry1[FCT ~func(T0) T1, T0, T1 any](f FCT) func(T0) T1 {
return func(t0 T0) T1 {
return f(t0)
}
@@ -115,7 +115,7 @@ func Nullary2[F1 ~func() T1, F2 ~func(T1) T2, T1, T2 any](f1 F1, f2 F2) func() T
// Curry2 takes a function with 2 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry2]
func Curry2[T0, T1, T2 any](f func(T0, T1) T2) func(T0) func(T1) T2 {
func Curry2[FCT ~func(T0, T1) T2, T0, T1, T2 any](f FCT) func(T0) func(T1) T2 {
return func(t0 T0) func(t1 T1) T2 {
return func(t1 T1) T2 {
return f(t0, t1)
@@ -125,7 +125,7 @@ func Curry2[T0, T1, T2 any](f func(T0, T1) T2) func(T0) func(T1) T2 {
// Uncurry2 takes a cascade of 2 functions each taking only one parameter and returns a function with 2 parameters .
// The inverse function is [Curry2]
func Uncurry2[T0, T1, T2 any](f func(T0) func(T1) T2) func(T0, T1) T2 {
func Uncurry2[FCT ~func(T0) func(T1) T2, T0, T1, T2 any](f FCT) func(T0, T1) T2 {
return func(t0 T0, t1 T1) T2 {
return f(t0)(t1)
}
@@ -178,7 +178,7 @@ func Nullary3[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, T1, T2, T3 any](f
// Curry3 takes a function with 3 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry3]
func Curry3[T0, T1, T2, T3 any](f func(T0, T1, T2) T3) func(T0) func(T1) func(T2) T3 {
func Curry3[FCT ~func(T0, T1, T2) T3, T0, T1, T2, T3 any](f FCT) func(T0) func(T1) func(T2) T3 {
return func(t0 T0) func(t1 T1) func(t2 T2) T3 {
return func(t1 T1) func(t2 T2) T3 {
return func(t2 T2) T3 {
@@ -190,7 +190,7 @@ func Curry3[T0, T1, T2, T3 any](f func(T0, T1, T2) T3) func(T0) func(T1) func(T2
// Uncurry3 takes a cascade of 3 functions each taking only one parameter and returns a function with 3 parameters .
// The inverse function is [Curry3]
func Uncurry3[T0, T1, T2, T3 any](f func(T0) func(T1) func(T2) T3) func(T0, T1, T2) T3 {
func Uncurry3[FCT ~func(T0) func(T1) func(T2) T3, T0, T1, T2, T3 any](f FCT) func(T0, T1, T2) T3 {
return func(t0 T0, t1 T1, t2 T2) T3 {
return f(t0)(t1)(t2)
}
@@ -244,7 +244,7 @@ func Nullary4[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
// Curry4 takes a function with 4 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry4]
func Curry4[T0, T1, T2, T3, T4 any](f func(T0, T1, T2, T3) T4) func(T0) func(T1) func(T2) func(T3) T4 {
func Curry4[FCT ~func(T0, T1, T2, T3) T4, T0, T1, T2, T3, T4 any](f FCT) func(T0) func(T1) func(T2) func(T3) T4 {
return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) T4 {
return func(t1 T1) func(t2 T2) func(t3 T3) T4 {
return func(t2 T2) func(t3 T3) T4 {
@@ -258,7 +258,7 @@ func Curry4[T0, T1, T2, T3, T4 any](f func(T0, T1, T2, T3) T4) func(T0) func(T1)
// Uncurry4 takes a cascade of 4 functions each taking only one parameter and returns a function with 4 parameters .
// The inverse function is [Curry4]
func Uncurry4[T0, T1, T2, T3, T4 any](f func(T0) func(T1) func(T2) func(T3) T4) func(T0, T1, T2, T3) T4 {
func Uncurry4[FCT ~func(T0) func(T1) func(T2) func(T3) T4, T0, T1, T2, T3, T4 any](f FCT) func(T0, T1, T2, T3) T4 {
return func(t0 T0, t1 T1, t2 T2, t3 T3) T4 {
return f(t0)(t1)(t2)(t3)
}
@@ -313,7 +313,7 @@ func Nullary5[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
// Curry5 takes a function with 5 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry5]
func Curry5[T0, T1, T2, T3, T4, T5 any](f func(T0, T1, T2, T3, T4) T5) func(T0) func(T1) func(T2) func(T3) func(T4) T5 {
func Curry5[FCT ~func(T0, T1, T2, T3, T4) T5, T0, T1, T2, T3, T4, T5 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) T5 {
return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) T5 {
return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) T5 {
return func(t2 T2) func(t3 T3) func(t4 T4) T5 {
@@ -329,7 +329,7 @@ func Curry5[T0, T1, T2, T3, T4, T5 any](f func(T0, T1, T2, T3, T4) T5) func(T0)
// Uncurry5 takes a cascade of 5 functions each taking only one parameter and returns a function with 5 parameters .
// The inverse function is [Curry5]
func Uncurry5[T0, T1, T2, T3, T4, T5 any](f func(T0) func(T1) func(T2) func(T3) func(T4) T5) func(T0, T1, T2, T3, T4) T5 {
func Uncurry5[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) T5, T0, T1, T2, T3, T4, T5 any](f FCT) func(T0, T1, T2, T3, T4) T5 {
return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4) T5 {
return f(t0)(t1)(t2)(t3)(t4)
}
@@ -385,7 +385,7 @@ func Nullary6[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
// Curry6 takes a function with 6 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry6]
func Curry6[T0, T1, T2, T3, T4, T5, T6 any](f func(T0, T1, T2, T3, T4, T5) T6) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) T6 {
func Curry6[FCT ~func(T0, T1, T2, T3, T4, T5) T6, T0, T1, T2, T3, T4, T5, T6 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) T6 {
return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) T6 {
return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) T6 {
return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) T6 {
@@ -403,7 +403,7 @@ func Curry6[T0, T1, T2, T3, T4, T5, T6 any](f func(T0, T1, T2, T3, T4, T5) T6) f
// Uncurry6 takes a cascade of 6 functions each taking only one parameter and returns a function with 6 parameters .
// The inverse function is [Curry6]
func Uncurry6[T0, T1, T2, T3, T4, T5, T6 any](f func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) T6) func(T0, T1, T2, T3, T4, T5) T6 {
func Uncurry6[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) T6, T0, T1, T2, T3, T4, T5, T6 any](f FCT) func(T0, T1, T2, T3, T4, T5) T6 {
return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5) T6 {
return f(t0)(t1)(t2)(t3)(t4)(t5)
}
@@ -460,7 +460,7 @@ func Nullary7[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
// Curry7 takes a function with 7 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry7]
func Curry7[T0, T1, T2, T3, T4, T5, T6, T7 any](f func(T0, T1, T2, T3, T4, T5, T6) T7) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) T7 {
func Curry7[FCT ~func(T0, T1, T2, T3, T4, T5, T6) T7, T0, T1, T2, T3, T4, T5, T6, T7 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) T7 {
return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) T7 {
return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) T7 {
return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) T7 {
@@ -480,7 +480,7 @@ func Curry7[T0, T1, T2, T3, T4, T5, T6, T7 any](f func(T0, T1, T2, T3, T4, T5, T
// Uncurry7 takes a cascade of 7 functions each taking only one parameter and returns a function with 7 parameters .
// The inverse function is [Curry7]
func Uncurry7[T0, T1, T2, T3, T4, T5, T6, T7 any](f func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) T7) func(T0, T1, T2, T3, T4, T5, T6) T7 {
func Uncurry7[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) T7, T0, T1, T2, T3, T4, T5, T6, T7 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6) T7 {
return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) T7 {
return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)
}
@@ -538,7 +538,7 @@ func Nullary8[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
// Curry8 takes a function with 8 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry8]
func Curry8[T0, T1, T2, T3, T4, T5, T6, T7, T8 any](f func(T0, T1, T2, T3, T4, T5, T6, T7) T8) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T8 {
func Curry8[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7) T8, T0, T1, T2, T3, T4, T5, T6, T7, T8 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T8 {
return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) T8 {
return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) T8 {
return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) T8 {
@@ -560,7 +560,7 @@ func Curry8[T0, T1, T2, T3, T4, T5, T6, T7, T8 any](f func(T0, T1, T2, T3, T4, T
// Uncurry8 takes a cascade of 8 functions each taking only one parameter and returns a function with 8 parameters .
// The inverse function is [Curry8]
func Uncurry8[T0, T1, T2, T3, T4, T5, T6, T7, T8 any](f func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T8) func(T0, T1, T2, T3, T4, T5, T6, T7) T8 {
func Uncurry8[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T8, T0, T1, T2, T3, T4, T5, T6, T7, T8 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7) T8 {
return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7) T8 {
return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)
}
@@ -619,7 +619,7 @@ func Nullary9[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
// Curry9 takes a function with 9 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry9]
func Curry9[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](f func(T0, T1, T2, T3, T4, T5, T6, T7, T8) T9) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T9 {
func Curry9[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8) T9, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T9 {
return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) T9 {
return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) T9 {
return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) T9 {
@@ -643,7 +643,7 @@ func Curry9[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](f func(T0, T1, T2, T3, T
// Uncurry9 takes a cascade of 9 functions each taking only one parameter and returns a function with 9 parameters .
// The inverse function is [Curry9]
func Uncurry9[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](f func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T9) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) T9 {
func Uncurry9[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T9, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) T9 {
return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8) T9 {
return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)
}
@@ -703,7 +703,7 @@ func Nullary10[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
// Curry10 takes a function with 10 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry10]
func Curry10[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](f func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) T10) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T10 {
func Curry10[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) T10, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T10 {
return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) T10 {
return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) T10 {
return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) T10 {
@@ -729,7 +729,7 @@ func Curry10[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](f func(T0, T1, T2,
// Uncurry10 takes a cascade of 10 functions each taking only one parameter and returns a function with 10 parameters .
// The inverse function is [Curry10]
func Uncurry10[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](f func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T10) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) T10 {
func Uncurry10[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T10, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) T10 {
return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9) T10 {
return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)
}
@@ -790,7 +790,7 @@ func Nullary11[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
// Curry11 takes a function with 11 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry11]
func Curry11[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](f func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) T11) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T11 {
func Curry11[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) T11, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T11 {
return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) T11 {
return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) T11 {
return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) T11 {
@@ -818,7 +818,7 @@ func Curry11[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](f func(T0, T1
// Uncurry11 takes a cascade of 11 functions each taking only one parameter and returns a function with 11 parameters .
// The inverse function is [Curry11]
func Uncurry11[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](f func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T11) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) T11 {
func Uncurry11[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T11, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) T11 {
return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10) T11 {
return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)
}
@@ -880,7 +880,7 @@ func Nullary12[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
// Curry12 takes a function with 12 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry12]
func Curry12[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](f func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) T12) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T12 {
func Curry12[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) T12, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T12 {
return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) T12 {
return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) T12 {
return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) T12 {
@@ -910,7 +910,7 @@ func Curry12[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](f func(T
// Uncurry12 takes a cascade of 12 functions each taking only one parameter and returns a function with 12 parameters .
// The inverse function is [Curry12]
func Uncurry12[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](f func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T12) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) T12 {
func Uncurry12[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T12, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) T12 {
return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11) T12 {
return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11)
}
@@ -973,7 +973,7 @@ func Nullary13[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
// Curry13 takes a function with 13 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry13]
func Curry13[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](f func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) T13) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T13 {
func Curry13[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) T13, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T13 {
return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) T13 {
return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) T13 {
return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) T13 {
@@ -1005,7 +1005,7 @@ func Curry13[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](f f
// Uncurry13 takes a cascade of 13 functions each taking only one parameter and returns a function with 13 parameters .
// The inverse function is [Curry13]
func Uncurry13[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](f func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T13) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) T13 {
func Uncurry13[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T13, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) T13 {
return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12) T13 {
return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11)(t12)
}
@@ -1069,7 +1069,7 @@ func Nullary14[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
// Curry14 takes a function with 14 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry14]
func Curry14[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any](f func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) T14) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T14 {
func Curry14[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) T14, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T14 {
return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) T14 {
return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) T14 {
return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) T14 {
@@ -1103,7 +1103,7 @@ func Curry14[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any
// Uncurry14 takes a cascade of 14 functions each taking only one parameter and returns a function with 14 parameters .
// The inverse function is [Curry14]
func Uncurry14[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any](f func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T14) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) T14 {
func Uncurry14[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T14, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) T14 {
return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13) T14 {
return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11)(t12)(t13)
}
@@ -1168,7 +1168,7 @@ func Nullary15[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
// Curry15 takes a function with 15 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry15]
func Curry15[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any](f func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) T15) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T15 {
func Curry15[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) T15, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T15 {
return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) T15 {
return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) T15 {
return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) T15 {
@@ -1204,7 +1204,7 @@ func Curry15[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T1
// Uncurry15 takes a cascade of 15 functions each taking only one parameter and returns a function with 15 parameters .
// The inverse function is [Curry15]
func Uncurry15[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any](f func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T15) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) T15 {
func Uncurry15[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T15, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) T15 {
return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14) T15 {
return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11)(t12)(t13)(t14)
}
@@ -1270,7 +1270,7 @@ func Nullary16[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
// Curry16 takes a function with 16 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry16]
func Curry16[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16 any](f func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) T16) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T16 {
func Curry16[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) T16, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T16 {
return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) T16 {
return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) T16 {
return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) T16 {
@@ -1308,7 +1308,7 @@ func Curry16[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T1
// Uncurry16 takes a cascade of 16 functions each taking only one parameter and returns a function with 16 parameters .
// The inverse function is [Curry16]
func Uncurry16[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16 any](f func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T16) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) T16 {
func Uncurry16[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T16, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) T16 {
return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15) T16 {
return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11)(t12)(t13)(t14)(t15)
}
@@ -1375,7 +1375,7 @@ func Nullary17[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
// Curry17 takes a function with 17 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry17]
func Curry17[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17 any](f func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) T17) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) T17 {
func Curry17[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) T17, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) T17 {
return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) T17 {
return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) T17 {
return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) T17 {
@@ -1415,7 +1415,7 @@ func Curry17[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T1
// Uncurry17 takes a cascade of 17 functions each taking only one parameter and returns a function with 17 parameters .
// The inverse function is [Curry17]
func Uncurry17[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17 any](f func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) T17) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) T17 {
func Uncurry17[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) T17, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) T17 {
return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16) T17 {
return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11)(t12)(t13)(t14)(t15)(t16)
}
@@ -1483,7 +1483,7 @@ func Nullary18[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
// Curry18 takes a function with 18 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry18]
func Curry18[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18 any](f func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17) T18) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) T18 {
func Curry18[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17) T18, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) T18 {
return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 {
return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 {
return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 {
@@ -1525,7 +1525,7 @@ func Curry18[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T1
// Uncurry18 takes a cascade of 18 functions each taking only one parameter and returns a function with 18 parameters .
// The inverse function is [Curry18]
func Uncurry18[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18 any](f func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) T18) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17) T18 {
func Uncurry18[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) T18, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17) T18 {
return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16, t17 T17) T18 {
return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11)(t12)(t13)(t14)(t15)(t16)(t17)
}
@@ -1594,7 +1594,7 @@ func Nullary19[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
// Curry19 takes a function with 19 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry19]
func Curry19[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19 any](f func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18) T19) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) func(T18) T19 {
func Curry19[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18) T19, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) func(T18) T19 {
return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 {
return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 {
return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 {
@@ -1638,7 +1638,7 @@ func Curry19[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T1
// Uncurry19 takes a cascade of 19 functions each taking only one parameter and returns a function with 19 parameters .
// The inverse function is [Curry19]
func Uncurry19[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19 any](f func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) func(T18) T19) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18) T19 {
func Uncurry19[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) func(T18) T19, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18) T19 {
return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16, t17 T17, t18 T18) T19 {
return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11)(t12)(t13)(t14)(t15)(t16)(t17)(t18)
}
@@ -1708,7 +1708,7 @@ func Nullary20[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
// Curry20 takes a function with 20 parameters and returns a cascade of functions each taking only one parameter.
// The inverse function is [Uncurry20]
func Curry20[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20 any](f func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19) T20) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) func(T18) func(T19) T20 {
func Curry20[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19) T20, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) func(T18) func(T19) T20 {
return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 {
return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 {
return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 {
@@ -1754,7 +1754,7 @@ func Curry20[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T1
// Uncurry20 takes a cascade of 20 functions each taking only one parameter and returns a function with 20 parameters .
// The inverse function is [Curry20]
func Uncurry20[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20 any](f func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) func(T18) func(T19) T20) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19) T20 {
func Uncurry20[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) func(T18) func(T19) T20, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19) T20 {
return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16, t17 T17, t18 T18, t19 T19) T20 {
return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11)(t12)(t13)(t14)(t15)(t16)(t17)(t18)(t19)
}

View File

@@ -28,7 +28,7 @@ func Memoize[F ~func(K) T, K comparable, T any](f F) F {
// ContramapMemoize converts a unary function into a unary function that caches the value depending on the parameter
func ContramapMemoize[F ~func(A) T, KF func(A) K, A any, K comparable, T any](kf KF) func(F) F {
return CacheCallback[F](kf, getOrCreate[K, T]())
return CacheCallback[func(F) F, func() func() T](kf, getOrCreate[K, T]())
}
// getOrCreate is a naive implementation of a cache, without bounds
@@ -50,13 +50,51 @@ func getOrCreate[K comparable, T any]() func(K, func() func() T) func() T {
}
}
// SingleElementCache is a cache with a capacity of a single element
func SingleElementCache[
LLT ~func() LT, // generator of the generator
K comparable, // key into the cache
LT ~func() T, // generator of a value
T any, // the cached data type
]() func(K, LLT) LT {
var l sync.Mutex
var key K
var value LT
hasKey := false
return func(k K, gen LLT) LT {
l.Lock()
existing := value
if !hasKey || key != k {
existing = gen()
// update state
key = k
value = existing
hasKey = true
}
l.Unlock()
return existing
}
}
// CacheCallback converts a unary function into a unary function that caches the value depending on the parameter
func CacheCallback[F ~func(A) T, KF func(A) K, C ~func(K, func() func() T) func() T, A any, K comparable, T any](kf KF, getOrCreate C) func(F) F {
func CacheCallback[
EM ~func(F) F, // endomorphism of the function
LLT ~func() LT, // generator of the generator
LT ~func() T, // generator of a value
F ~func(A) T, // function to actually cache
KF func(A) K, // extracts the cache key from the input
C ~func(K, LLT) LT, // the cache callback function
A any, K comparable, T any](kf KF, getOrCreate C) EM {
return func(f F) F {
return func(a A) T {
// cache entry
return getOrCreate(kf(a), func() func() T {
return L.Memoize[func() T](func() T {
return getOrCreate(kf(a), func() LT {
return L.Memoize[LT](func() T {
return f(a)
})
})()

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)
}

4
go.mod
View File

@@ -4,7 +4,7 @@ go 1.20
require (
github.com/stretchr/testify v1.8.4
github.com/urfave/cli/v2 v2.25.7
github.com/urfave/cli/v2 v2.27.1
)
require (
@@ -12,6 +12,6 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

8
go.sum
View File

@@ -8,10 +8,10 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI=
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

22
http/content/content.go Normal file
View File

@@ -0,0 +1,22 @@
// 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 content
const (
TextPlain = "text/plain"
Json = "application/json"
FormEncoded = "application/x-www-form-urlencoded"
)

74
http/form/form.go Normal file
View File

@@ -0,0 +1,74 @@
// 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 form
import (
"net/url"
A "github.com/IBM/fp-go/array"
ENDO "github.com/IBM/fp-go/endomorphism"
F "github.com/IBM/fp-go/function"
L "github.com/IBM/fp-go/optics/lens"
LA "github.com/IBM/fp-go/optics/lens/array"
LRG "github.com/IBM/fp-go/optics/lens/record/generic"
O "github.com/IBM/fp-go/option"
RG "github.com/IBM/fp-go/record/generic"
)
type (
// Endomorphism returns an [ENDO.Endomorphism] that transforms a form
Endomorphism = ENDO.Endomorphism[url.Values]
)
var (
// Default is the default form field
Default = make(url.Values)
noField = O.None[string]()
// FormMonoid is the [M.Monoid] for the [Endomorphism]
Monoid = ENDO.Monoid[url.Values]()
// ValuesMonoid is a [M.Monoid] to concatenate [url.Values] maps
ValuesMonoid = RG.UnionMonoid[url.Values](A.Semigroup[string]())
// AtValues is a [L.Lens] that focusses on the values of a form field
AtValues = LRG.AtRecord[url.Values, []string]
composeHead = F.Pipe1(
LA.AtHead[string](),
L.ComposeOptions[url.Values, string](A.Empty[string]()),
)
// AtValue is a [L.Lens] that focusses on first value in form fields
AtValue = F.Flow2(
AtValues,
composeHead,
)
)
// WithValue creates a [FormBuilder] for a certain field
func WithValue(name string) func(value string) Endomorphism {
return F.Flow2(
O.Of[string],
AtValue(name).Set,
)
}
// WithoutValue creates a [FormBuilder] that removes a field
func WithoutValue(name string) Endomorphism {
return AtValue(name).Set(noField)
}

93
http/form/form_test.go Normal file
View File

@@ -0,0 +1,93 @@
// 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 form
import (
"net/url"
"testing"
A "github.com/IBM/fp-go/array"
"github.com/IBM/fp-go/eq"
F "github.com/IBM/fp-go/function"
LT "github.com/IBM/fp-go/optics/lens/testing"
O "github.com/IBM/fp-go/option"
RG "github.com/IBM/fp-go/record/generic"
S "github.com/IBM/fp-go/string"
"github.com/stretchr/testify/assert"
)
var (
sEq = eq.FromEquals(S.Eq)
valuesEq = RG.Eq[url.Values](A.Eq(sEq))
)
func TestLaws(t *testing.T) {
name := "Content-Type"
fieldLaws := LT.AssertLaws[url.Values, O.Option[string]](t, O.Eq(sEq), valuesEq)(AtValue(name))
n := O.None[string]()
s1 := O.Some("s1")
v1 := F.Pipe1(
Default,
WithValue(name)("v1"),
)
v2 := F.Pipe1(
Default,
WithValue("Other-Header")("v2"),
)
assert.True(t, fieldLaws(Default, n))
assert.True(t, fieldLaws(v1, n))
assert.True(t, fieldLaws(v2, n))
assert.True(t, fieldLaws(Default, s1))
assert.True(t, fieldLaws(v1, s1))
assert.True(t, fieldLaws(v2, s1))
}
func TestFormField(t *testing.T) {
v1 := F.Pipe1(
Default,
WithValue("h1")("v1"),
)
v2 := F.Pipe1(
v1,
WithValue("h2")("v2"),
)
// make sure the code does not change structures
assert.False(t, valuesEq.Equals(Default, v1))
assert.False(t, valuesEq.Equals(Default, v2))
assert.False(t, valuesEq.Equals(v1, v2))
// check for existence of values
assert.Equal(t, "v1", v1.Get("h1"))
assert.Equal(t, "v1", v2.Get("h1"))
assert.Equal(t, "v2", v2.Get("h2"))
// check getter on lens
l1 := AtValue("h1")
l2 := AtValue("h2")
assert.Equal(t, O.Of("v1"), l1.Get(v1))
assert.Equal(t, O.Of("v1"), l1.Get(v2))
assert.Equal(t, O.Of("v2"), l2.Get(v2))
}

58
http/headers/headers.go Normal file
View File

@@ -0,0 +1,58 @@
// 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 headers
import (
"net/http"
"net/textproto"
A "github.com/IBM/fp-go/array"
F "github.com/IBM/fp-go/function"
L "github.com/IBM/fp-go/optics/lens"
LA "github.com/IBM/fp-go/optics/lens/array"
LRG "github.com/IBM/fp-go/optics/lens/record/generic"
RG "github.com/IBM/fp-go/record/generic"
)
// HTTP headers
const (
Accept = "Accept"
Authorization = "Authorization"
ContentType = "Content-Type"
ContentLength = "Content-Length"
)
var (
// Monoid is a [M.Monoid] to concatenate [http.Header] maps
Monoid = RG.UnionMonoid[http.Header](A.Semigroup[string]())
// AtValues is a [L.Lens] that focusses on the values of a header
AtValues = F.Flow2(
textproto.CanonicalMIMEHeaderKey,
LRG.AtRecord[http.Header, []string],
)
composeHead = F.Pipe1(
LA.AtHead[string](),
L.ComposeOptions[http.Header, string](A.Empty[string]()),
)
// AtValue is a [L.Lens] that focusses on first value of a header
AtValue = F.Flow2(
AtValues,
composeHead,
)
)

View File

@@ -0,0 +1,58 @@
// 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 headers
import (
"net/http"
"testing"
A "github.com/IBM/fp-go/array"
"github.com/IBM/fp-go/eq"
LT "github.com/IBM/fp-go/optics/lens/testing"
O "github.com/IBM/fp-go/option"
RG "github.com/IBM/fp-go/record/generic"
S "github.com/IBM/fp-go/string"
"github.com/stretchr/testify/assert"
)
var (
sEq = eq.FromEquals(S.Eq)
valuesEq = RG.Eq[http.Header](A.Eq(sEq))
)
func TestLaws(t *testing.T) {
name := "Content-Type"
fieldLaws := LT.AssertLaws[http.Header, O.Option[string]](t, O.Eq(sEq), valuesEq)(AtValue(name))
n := O.None[string]()
s1 := O.Some("s1")
def := make(http.Header)
v1 := make(http.Header)
v1.Set(name, "v1")
v2 := make(http.Header)
v2.Set("Other-Header", "v2")
assert.True(t, fieldLaws(def, n))
assert.True(t, fieldLaws(v1, n))
assert.True(t, fieldLaws(v2, n))
assert.True(t, fieldLaws(def, s1))
assert.True(t, fieldLaws(v1, s1))
assert.True(t, fieldLaws(v2, s1))
}

View File

@@ -20,6 +20,7 @@ import (
E "github.com/IBM/fp-go/either"
F "github.com/IBM/fp-go/function"
C "github.com/IBM/fp-go/http/content"
"github.com/stretchr/testify/assert"
)
@@ -38,7 +39,7 @@ func Error[A any](t *testing.T) func(E.Either[error, A]) bool {
func TestValidateJsonContentTypeString(t *testing.T) {
res := F.Pipe1(
validateJsonContentTypeString("application/json"),
validateJsonContentTypeString(C.Json),
NoError[ParsedMediaType](t),
)

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)
}

28
internal/array/prepend.go Normal file
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 array
// Prepend prepends a single value to an array
func Prepend[ENDO ~func(AS) AS, AS ~[]A, A any](head A) ENDO {
return func(as AS) AS {
l := len(as)
cpy := make(AS, l+1)
copy(cpy[1:], as)
cpy[0] = head
return cpy
}
}

View File

@@ -32,6 +32,17 @@ func MonadAlt[LAZY ~func() HKTFA, E, A, HKTFA any](
return fchain(first, ET.Fold(F.Ignore1of1[E](second), F.Flow2(ET.Of[E, A], fof)))
}
func Alt[LAZY ~func() HKTFA, E, A, HKTFA any](
fof func(ET.Either[E, A]) HKTFA,
fchain func(HKTFA, func(ET.Either[E, A]) HKTFA) HKTFA,
second LAZY) func(HKTFA) HKTFA {
return func(fa HKTFA) HKTFA {
return MonadAlt(fof, fchain, fa, second)
}
}
// HKTFA = HKT<F, Either<E, A>>
// HKTFB = HKT<F, Either<E, B>>
func MonadMap[E, A, B, HKTFA, HKTFB any](fmap func(HKTFA, func(ET.Either[E, A]) ET.Either[E, B]) HKTFB, fa HKTFA, f func(A) B) HKTFB {

View File

@@ -49,6 +49,16 @@ func MonadChain[A, B, HKTFA, HKTFB any](
return fchain(ma, O.Fold(F.Nullary2(O.None[B], fof), f))
}
func Chain[A, B, HKTFA, HKTFB any](
fchain func(HKTFA, func(O.Option[A]) HKTFB) HKTFB,
fof func(O.Option[B]) HKTFB,
f func(A) HKTFB) func(ma HKTFA) HKTFB {
// dispatch to the even more generic implementation
return func(ma HKTFA) HKTFB {
return MonadChain(fchain, fof, ma, f)
}
}
func MonadAp[A, B, HKTFAB, HKTFGAB, HKTFA, HKTFB any](
fap func(HKTFGAB, HKTFA) HKTFB,
fmap func(HKTFAB, func(O.Option[func(A) B]) func(O.Option[A]) O.Option[B]) HKTFGAB,
@@ -78,3 +88,24 @@ func MonadChainOptionK[A, B, HKTA, HKTB any](
) HKTB {
return MonadChain(fchain, fof, ma, FromOptionK(fof, f))
}
func MonadAlt[LAZY ~func() HKTFA, A, HKTFA any](
fof func(O.Option[A]) HKTFA,
fchain func(HKTFA, func(O.Option[A]) HKTFA) HKTFA,
first HKTFA,
second LAZY) HKTFA {
return fchain(first, O.Fold(second, F.Flow2(O.Of[A], fof)))
}
func Alt[LAZY ~func() HKTFA, A, HKTFA any](
fof func(O.Option[A]) HKTFA,
fchain func(HKTFA, func(O.Option[A]) HKTFA) HKTFA,
second LAZY) func(HKTFA) HKTFA {
return func(fa HKTFA) HKTFA {
return MonadAlt(fof, fchain, fa, second)
}
}

View File

@@ -16,6 +16,7 @@
package generic
import (
F "github.com/IBM/fp-go/function"
G "github.com/IBM/fp-go/internal/apply"
)
@@ -27,7 +28,10 @@ const (
// MonadApSeq implements the applicative on a single thread by first executing mab and the ma
func MonadApSeq[GA ~func() A, GB ~func() B, GAB ~func() func(A) B, A, B any](mab GAB, ma GA) GB {
return MakeIO[GB](func() B {
return mab()(ma())
return F.Pipe1(
ma(),
mab(),
)
})
}

30
io/generic/resource.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 generic
import (
F "github.com/IBM/fp-go/function"
)
// WithResource constructs a function that creates a resource, then operates on it and then releases the resource
func WithResource[
GA ~func() A,
GR ~func() R,
GANY ~func() ANY,
R, A, ANY any](onCreate GR, onRelease func(R) GANY) func(func(R) GA) GA {
// simply map to implementation of bracket
return F.Bind13of3(Bracket[GR, GA, GANY, R, A, ANY])(onCreate, F.Ignore2of2[A](onRelease))
}

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])
}

27
io/resource.go Normal file
View File

@@ -0,0 +1,27 @@
// 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 io
import (
G "github.com/IBM/fp-go/io/generic"
)
// WithResource constructs a function that creates a resource, then operates on it and then releases the resource
func WithResource[
R, A, ANY any](onCreate IO[R], onRelease func(R) IO[ANY]) func(func(R) IO[A]) IO[A] {
// just dispatch
return G.WithResource[IO[A], IO[R], IO[ANY]](onCreate, onRelease)
}

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

@@ -275,6 +275,27 @@ func ChainFirstIOK[GA ~func() ET.Either[E, A], GIOB ~func() B, E, A, B any](f fu
)
}
// MonadChainFirstEitherK runs the monad returned by the function but returns the result of the original monad
func MonadChainFirstEitherK[GA ~func() ET.Either[E, A], E, A, B any](first GA, f func(A) ET.Either[E, B]) GA {
return FE.MonadChainFirstEitherK(
MonadChain[GA, GA, E, A, A],
MonadMap[func() ET.Either[E, B], GA, E, B, A],
FromEither[func() ET.Either[E, B], E, B],
first,
f,
)
}
// ChainFirstEitherK runs the monad returned by the function but returns the result of the original monad
func ChainFirstEitherK[GA ~func() ET.Either[E, A], E, A, B any](f func(A) ET.Either[E, B]) func(GA) GA {
return FE.ChainFirstEitherK(
MonadChain[GA, GA, E, A, A],
MonadMap[func() ET.Either[E, B], GA, E, B, A],
FromEither[func() ET.Either[E, B], E, B],
f,
)
}
// Swap changes the order of type parameters
func Swap[GEA ~func() ET.Either[E, A], GAE ~func() ET.Either[A, E], E, A any](val GEA) GAE {
return MonadFold(val, Right[GAE], Left[GAE])
@@ -315,3 +336,14 @@ func MonadFlap[GEAB ~func() ET.Either[E, func(A) B], GEB ~func() ET.Either[E, B]
func Flap[GEAB ~func() ET.Either[E, func(A) B], GEB ~func() ET.Either[E, B], E, B, A any](a A) func(GEAB) GEB {
return FC.Flap(MonadMap[GEAB, GEB], a)
}
func ToIOOption[GA ~func() O.Option[A], GEA ~func() ET.Either[E, A], E, A any](ioe GEA) GA {
return F.Pipe1(
ioe,
IO.Map[GEA, GA](ET.ToOption[E, A]),
)
}
func FromIOOption[GEA ~func() ET.Either[E, A], GA ~func() O.Option[A], E, A any](onNone func() E) func(ioo GA) GEA {
return IO.Map[GA, GEA](ET.FromOption[A](onNone))
}

View File

@@ -111,3 +111,183 @@ func TraverseRecordWithIndex[GB ~func() ET.Either[E, B], GBS ~func() ET.Either[E
func SequenceRecord[GA ~func() ET.Either[E, A], GAS ~func() ET.Either[E, AAS], AAS ~map[K]A, GAAS ~map[K]GA, K comparable, E, A any](tas GAAS) GAS {
return MonadTraverseRecord[GA, GAS](tas, F.Identity[GA])
}
// MonadTraverseArraySeq transforms an array
func MonadTraverseArraySeq[GB ~func() ET.Either[E, B], GBS ~func() ET.Either[E, BBS], AAS ~[]A, BBS ~[]B, E, A, B any](tas AAS, f func(A) GB) GBS {
return RA.MonadTraverse[AAS](
Of[GBS, E, BBS],
Map[GBS, func() ET.Either[E, func(B) BBS], E, BBS, func(B) BBS],
ApSeq[GBS, func() ET.Either[E, func(B) BBS], GB],
tas,
f,
)
}
// TraverseArraySeq transforms an array
func TraverseArraySeq[GB ~func() ET.Either[E, B], GBS ~func() ET.Either[E, BBS], AAS ~[]A, BBS ~[]B, E, A, B any](f func(A) GB) func(AAS) GBS {
return RA.Traverse[AAS](
Of[GBS, E, BBS],
Map[GBS, func() ET.Either[E, func(B) BBS], E, BBS, func(B) BBS],
ApSeq[GBS, func() ET.Either[E, func(B) BBS], GB],
f,
)
}
// MonadTraverseArrayWithIndexSeq transforms an array
func MonadTraverseArrayWithIndexSeq[GB ~func() ET.Either[E, B], GBS ~func() ET.Either[E, BBS], AAS ~[]A, BBS ~[]B, E, A, B any](tas AAS, f func(int, A) GB) GBS {
return RA.MonadTraverseWithIndex[AAS](
Of[GBS, E, BBS],
Map[GBS, func() ET.Either[E, func(B) BBS], E, BBS, func(B) BBS],
ApSeq[GBS, func() ET.Either[E, func(B) BBS], GB],
tas,
f,
)
}
// TraverseArrayWithIndexSeq transforms an array
func TraverseArrayWithIndexSeq[GB ~func() ET.Either[E, B], GBS ~func() ET.Either[E, BBS], AAS ~[]A, BBS ~[]B, E, A, B any](f func(int, A) GB) func(AAS) GBS {
return RA.TraverseWithIndex[AAS](
Of[GBS, E, BBS],
Map[GBS, func() ET.Either[E, func(B) BBS], E, BBS, func(B) BBS],
ApSeq[GBS, func() ET.Either[E, func(B) BBS], GB],
f,
)
}
// SequenceArraySeq converts a homogeneous sequence of either into an either of sequence
func SequenceArraySeq[GA ~func() ET.Either[E, A], GAS ~func() ET.Either[E, AAS], AAS ~[]A, GAAS ~[]GA, E, A any](tas GAAS) GAS {
return MonadTraverseArraySeq[GA, GAS](tas, F.Identity[GA])
}
// MonadTraverseRecordSeq transforms an array
func MonadTraverseRecordSeq[GB ~func() ET.Either[E, B], GBS ~func() ET.Either[E, BBS], AAS ~map[K]A, BBS ~map[K]B, K comparable, E, A, B any](tas AAS, f func(A) GB) GBS {
return RR.MonadTraverse[AAS](
Of[GBS, E, BBS],
Map[GBS, func() ET.Either[E, func(B) BBS], E, BBS, func(B) BBS],
ApSeq[GBS, func() ET.Either[E, func(B) BBS], GB],
tas,
f,
)
}
// TraverseRecordSeq transforms an array
func TraverseRecordSeq[GB ~func() ET.Either[E, B], GBS ~func() ET.Either[E, BBS], AAS ~map[K]A, BBS ~map[K]B, K comparable, E, A, B any](f func(A) GB) func(AAS) GBS {
return RR.Traverse[AAS](
Of[GBS, E, BBS],
Map[GBS, func() ET.Either[E, func(B) BBS], E, BBS, func(B) BBS],
ApSeq[GBS, func() ET.Either[E, func(B) BBS], GB],
f,
)
}
// TraverseRecordWithIndexSeq transforms an array
func TraverseRecordWithIndexSeq[GB ~func() ET.Either[E, B], GBS ~func() ET.Either[E, BBS], AAS ~map[K]A, BBS ~map[K]B, K comparable, E, A, B any](f func(K, A) GB) func(AAS) GBS {
return RR.TraverseWithIndex[AAS](
Of[GBS, E, BBS],
Map[GBS, func() ET.Either[E, func(B) BBS], E, BBS, func(B) BBS],
ApSeq[GBS, func() ET.Either[E, func(B) BBS], GB],
f,
)
}
// SequenceRecordSeq converts a homogeneous sequence of either into an either of sequence
func SequenceRecordSeq[GA ~func() ET.Either[E, A], GAS ~func() ET.Either[E, AAS], AAS ~map[K]A, GAAS ~map[K]GA, K comparable, E, A any](tas GAAS) GAS {
return MonadTraverseRecordSeq[GA, GAS](tas, F.Identity[GA])
}
// MonadTraverseArrayPar transforms an array
func MonadTraverseArrayPar[GB ~func() ET.Either[E, B], GBS ~func() ET.Either[E, BBS], AAS ~[]A, BBS ~[]B, E, A, B any](tas AAS, f func(A) GB) GBS {
return RA.MonadTraverse[AAS](
Of[GBS, E, BBS],
Map[GBS, func() ET.Either[E, func(B) BBS], E, BBS, func(B) BBS],
ApPar[GBS, func() ET.Either[E, func(B) BBS], GB],
tas,
f,
)
}
// TraverseArrayPar transforms an array
func TraverseArrayPar[GB ~func() ET.Either[E, B], GBS ~func() ET.Either[E, BBS], AAS ~[]A, BBS ~[]B, E, A, B any](f func(A) GB) func(AAS) GBS {
return RA.Traverse[AAS](
Of[GBS, E, BBS],
Map[GBS, func() ET.Either[E, func(B) BBS], E, BBS, func(B) BBS],
ApPar[GBS, func() ET.Either[E, func(B) BBS], GB],
f,
)
}
// MonadTraverseArrayWithIndexPar transforms an array
func MonadTraverseArrayWithIndexPar[GB ~func() ET.Either[E, B], GBS ~func() ET.Either[E, BBS], AAS ~[]A, BBS ~[]B, E, A, B any](tas AAS, f func(int, A) GB) GBS {
return RA.MonadTraverseWithIndex[AAS](
Of[GBS, E, BBS],
Map[GBS, func() ET.Either[E, func(B) BBS], E, BBS, func(B) BBS],
ApPar[GBS, func() ET.Either[E, func(B) BBS], GB],
tas,
f,
)
}
// TraverseArrayWithIndexPar transforms an array
func TraverseArrayWithIndexPar[GB ~func() ET.Either[E, B], GBS ~func() ET.Either[E, BBS], AAS ~[]A, BBS ~[]B, E, A, B any](f func(int, A) GB) func(AAS) GBS {
return RA.TraverseWithIndex[AAS](
Of[GBS, E, BBS],
Map[GBS, func() ET.Either[E, func(B) BBS], E, BBS, func(B) BBS],
ApPar[GBS, func() ET.Either[E, func(B) BBS], GB],
f,
)
}
// SequenceArrayPar converts a homogeneous sequence of either into an either of sequence
func SequenceArrayPar[GA ~func() ET.Either[E, A], GAS ~func() ET.Either[E, AAS], AAS ~[]A, GAAS ~[]GA, E, A any](tas GAAS) GAS {
return MonadTraverseArrayPar[GA, GAS](tas, F.Identity[GA])
}
// MonadTraverseRecordPar transforms an array
func MonadTraverseRecordPar[GB ~func() ET.Either[E, B], GBS ~func() ET.Either[E, BBS], AAS ~map[K]A, BBS ~map[K]B, K comparable, E, A, B any](tas AAS, f func(A) GB) GBS {
return RR.MonadTraverse[AAS](
Of[GBS, E, BBS],
Map[GBS, func() ET.Either[E, func(B) BBS], E, BBS, func(B) BBS],
ApPar[GBS, func() ET.Either[E, func(B) BBS], GB],
tas,
f,
)
}
// TraverseRecordPar transforms an array
func TraverseRecordPar[GB ~func() ET.Either[E, B], GBS ~func() ET.Either[E, BBS], AAS ~map[K]A, BBS ~map[K]B, K comparable, E, A, B any](f func(A) GB) func(AAS) GBS {
return RR.Traverse[AAS](
Of[GBS, E, BBS],
Map[GBS, func() ET.Either[E, func(B) BBS], E, BBS, func(B) BBS],
ApPar[GBS, func() ET.Either[E, func(B) BBS], GB],
f,
)
}
// TraverseRecordWithIndexPar transforms an array
func TraverseRecordWithIndexPar[GB ~func() ET.Either[E, B], GBS ~func() ET.Either[E, BBS], AAS ~map[K]A, BBS ~map[K]B, K comparable, E, A, B any](f func(K, A) GB) func(AAS) GBS {
return RR.TraverseWithIndex[AAS](
Of[GBS, E, BBS],
Map[GBS, func() ET.Either[E, func(B) BBS], E, BBS, func(B) BBS],
ApPar[GBS, func() ET.Either[E, func(B) BBS], GB],
f,
)
}
// SequenceRecordPar converts a homogeneous sequence of either into an either of sequence
func SequenceRecordPar[GA ~func() ET.Either[E, A], GAS ~func() ET.Either[E, AAS], AAS ~map[K]A, GAAS ~map[K]GA, K comparable, E, A any](tas GAAS) GAS {
return MonadTraverseRecordPar[GA, GAS](tas, F.Identity[GA])
}

View File

@@ -0,0 +1,356 @@
// 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 builder
import (
"bytes"
"net/http"
"net/url"
"strconv"
E "github.com/IBM/fp-go/either"
ENDO "github.com/IBM/fp-go/endomorphism"
F "github.com/IBM/fp-go/function"
C "github.com/IBM/fp-go/http/content"
FM "github.com/IBM/fp-go/http/form"
H "github.com/IBM/fp-go/http/headers"
IOG "github.com/IBM/fp-go/io/generic"
IOE "github.com/IBM/fp-go/ioeither"
IOEH "github.com/IBM/fp-go/ioeither/http"
J "github.com/IBM/fp-go/json"
LZ "github.com/IBM/fp-go/lazy"
L "github.com/IBM/fp-go/optics/lens"
O "github.com/IBM/fp-go/option"
S "github.com/IBM/fp-go/string"
T "github.com/IBM/fp-go/tuple"
)
type (
Builder struct {
method O.Option[string]
url string
headers http.Header
body O.Option[IOE.IOEither[error, []byte]]
query url.Values
}
// Endomorphism returns an [ENDO.Endomorphism] that transforms a builder
Endomorphism = ENDO.Endomorphism[*Builder]
)
var (
// Default is the default builder
Default = &Builder{method: O.Some(defaultMethod()), headers: make(http.Header), body: noBody}
defaultMethod = F.Constant(http.MethodGet)
// Monoid is the [M.Monoid] for the [Endomorphism]
Monoid = ENDO.Monoid[*Builder]()
// Url is a [L.Lens] for the URL
Url = L.MakeLensRef((*Builder).GetUrl, (*Builder).SetUrl)
// Method is a [L.Lens] for the HTTP method
Method = L.MakeLensRef((*Builder).GetMethod, (*Builder).SetMethod)
// Body is a [L.Lens] for the request body
Body = L.MakeLensRef((*Builder).GetBody, (*Builder).SetBody)
// Headers is a [L.Lens] for the complete set of request headers
Headers = L.MakeLensRef((*Builder).GetHeaders, (*Builder).SetHeaders)
// Query is a [L.Lens] for the set of query parameters
Query = L.MakeLensRef((*Builder).GetQuery, (*Builder).SetQuery)
rawQuery = L.MakeLensRef(getRawQuery, setRawQuery)
getHeader = F.Bind2of2((*Builder).GetHeader)
delHeader = F.Bind2of2((*Builder).DelHeader)
setHeader = F.Bind2of3((*Builder).SetHeader)
noHeader = O.None[string]()
noBody = O.None[IOE.IOEither[error, []byte]]()
noQueryArg = O.None[string]()
parseUrl = E.Eitherize1(url.Parse)
parseQuery = E.Eitherize1(url.ParseQuery)
// WithQuery creates a [Endomorphism] for a complete set of query parameters
WithQuery = Query.Set
// WithMethod creates a [Endomorphism] for a certain method
WithMethod = Method.Set
// WithUrl creates a [Endomorphism] for a certain method
WithUrl = Url.Set
// WithHeaders creates a [Endomorphism] for a set of headers
WithHeaders = Headers.Set
// WithBody creates a [Endomorphism] for a request body
WithBody = F.Flow2(
O.Of[IOE.IOEither[error, []byte]],
Body.Set,
)
// WithBytes creates a [Endomorphism] for a request body using bytes
WithBytes = F.Flow2(
IOE.Of[error, []byte],
WithBody,
)
// WithContentType adds the [H.ContentType] header
WithContentType = WithHeader(H.ContentType)
// WithAuthorization adds the [H.Authorization] header
WithAuthorization = WithHeader(H.Authorization)
// WithGet adds the [http.MethodGet] method
WithGet = WithMethod(http.MethodGet)
// WithPost adds the [http.MethodPost] method
WithPost = WithMethod(http.MethodPost)
// WithPut adds the [http.MethodPut] method
WithPut = WithMethod(http.MethodPut)
// WithDelete adds the [http.MethodDelete] method
WithDelete = WithMethod(http.MethodDelete)
// WithBearer creates a [Endomorphism] to add a Bearer [H.Authorization] header
WithBearer = F.Flow2(
S.Format[string]("Bearer %s"),
WithAuthorization,
)
// Requester creates a requester from a builder
Requester = (*Builder).Requester
// WithoutBody creates a [Endomorphism] to remove the body
WithoutBody = F.Pipe1(
noBody,
Body.Set,
)
)
func setRawQuery(u *url.URL, raw string) *url.URL {
u.RawQuery = raw
return u
}
func getRawQuery(u *url.URL) string {
return u.RawQuery
}
func (builder *Builder) clone() *Builder {
cpy := *builder
cpy.headers = cpy.headers.Clone()
return &cpy
}
func (builder *Builder) GetUrl() string {
return builder.url
}
func (builder *Builder) GetMethod() string {
return F.Pipe1(
builder.method,
O.GetOrElse(defaultMethod),
)
}
func (builder *Builder) GetHeaders() http.Header {
return builder.headers
}
func (builder *Builder) GetQuery() url.Values {
return builder.query
}
func (builder *Builder) SetQuery(query url.Values) *Builder {
builder.query = query
return builder
}
func (builder *Builder) GetBody() O.Option[IOE.IOEither[error, []byte]] {
return builder.body
}
func (builder *Builder) SetMethod(method string) *Builder {
builder.method = O.Some(method)
return builder
}
func (builder *Builder) SetUrl(url string) *Builder {
builder.url = url
return builder
}
func (builder *Builder) SetHeaders(headers http.Header) *Builder {
builder.headers = headers
return builder
}
func (builder *Builder) SetBody(body O.Option[IOE.IOEither[error, []byte]]) *Builder {
builder.body = body
return builder
}
func (builder *Builder) SetHeader(name, value string) *Builder {
builder.headers.Set(name, value)
return builder
}
func (builder *Builder) DelHeader(name string) *Builder {
builder.headers.Del(name)
return builder
}
func (builder *Builder) GetHeader(name string) O.Option[string] {
return F.Pipe2(
name,
builder.headers.Get,
O.FromPredicate(S.IsNonEmpty),
)
}
func (builder *Builder) GetHeaderValues(name string) []string {
return builder.headers.Values(name)
}
func (builder *Builder) Requester() IOEH.Requester {
withBody := F.Curry3(func(data []byte, url string, method string) IOE.IOEither[error, *http.Request] {
return IOE.TryCatchError(func() (*http.Request, error) {
req, err := http.NewRequest(method, url, bytes.NewReader(data))
if err == nil {
req.Header.Set(H.ContentLength, strconv.Itoa(len(data)))
H.Monoid.Concat(req.Header, builder.headers)
}
return req, err
})
})
withoutBody := F.Curry2(func(url string, method string) IOE.IOEither[error, *http.Request] {
return IOE.TryCatchError(func() (*http.Request, error) {
req, err := http.NewRequest(method, url, nil)
if err == nil {
H.Monoid.Concat(req.Header, builder.headers)
}
return req, err
})
})
// construct the final URL
targetUrl := F.Pipe3(
builder,
Url.Get,
parseUrl,
E.Chain(F.Flow4(
T.Replicate2[*url.URL],
T.Map2(
F.Flow2(
F.Curry2(setRawQuery),
E.Of[error, func(string) *url.URL],
),
F.Flow3(
rawQuery.Get,
parseQuery,
E.Map[error](F.Flow2(
F.Curry2(FM.ValuesMonoid.Concat)(builder.GetQuery()),
(url.Values).Encode,
)),
),
),
T.Tupled2(E.MonadAp[*url.URL, error, string]),
E.Map[error]((*url.URL).String),
)),
)
return F.Pipe5(
builder,
Body.Get,
O.Fold(LZ.Of(IOE.Of[error](withoutBody)), IOE.Map[error](withBody)),
IOG.Map[IOE.IOEither[error, func(string) func(string) IOE.IOEither[error, *http.Request]], IOE.IOEither[error, func(string) IOE.IOEither[error, *http.Request]]](E.Ap[func(string) IOE.IOEither[error, *http.Request]](targetUrl)),
IOE.Flap[error, IOE.IOEither[error, *http.Request]](builder.GetMethod()),
IOE.Flatten[error, *http.Request],
)
}
// Header returns a [L.Lens] for a single header
func Header(name string) L.Lens[*Builder, O.Option[string]] {
get := getHeader(name)
set := F.Bind1of2(setHeader(name))
del := F.Flow2(
LZ.Of[*Builder],
LZ.Map(delHeader(name)),
)
return L.MakeLens(get, func(b *Builder, value O.Option[string]) *Builder {
cpy := b.clone()
return F.Pipe1(
value,
O.Fold(del(cpy), set(cpy)),
)
})
}
// WithHeader creates a [Endomorphism] for a certain header
func WithHeader(name string) func(value string) Endomorphism {
return F.Flow2(
O.Of[string],
Header(name).Set,
)
}
// WithoutHeader creates a [Endomorphism] to remove a certain header
func WithoutHeader(name string) Endomorphism {
return Header(name).Set(noHeader)
}
// WithFormData creates a [Endomorphism] to send form data payload
func WithFormData(value url.Values) Endomorphism {
return F.Flow2(
F.Pipe4(
value,
url.Values.Encode,
S.ToBytes,
IOE.Of[error, []byte],
WithBody,
),
WithContentType(C.FormEncoded),
)
}
// WithJson creates a [Endomorphism] to send JSON payload
func WithJson[T any](data T) Endomorphism {
return F.Flow2(
F.Pipe3(
data,
J.Marshal[T],
IOE.FromEither[error, []byte],
WithBody,
),
WithContentType(C.Json),
)
}
// QueryArg is a [L.Lens] for the first value of a query argument
func QueryArg(name string) L.Lens[*Builder, O.Option[string]] {
return F.Pipe1(
Query,
L.Compose[*Builder](FM.AtValue(name)),
)
}
// WithQueryArg creates a [Endomorphism] for a certain query argument
func WithQueryArg(name string) func(value string) Endomorphism {
return F.Flow2(
O.Of[string],
QueryArg(name).Set,
)
}
// WithoutQueryArg creates a [Endomorphism] that removes a query argument
func WithoutQueryArg(name string) Endomorphism {
return QueryArg(name).Set(noQueryArg)
}

View File

@@ -0,0 +1,86 @@
// 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 builder
import (
"net/http"
"net/url"
"testing"
E "github.com/IBM/fp-go/either"
F "github.com/IBM/fp-go/function"
C "github.com/IBM/fp-go/http/content"
H "github.com/IBM/fp-go/http/headers"
IO "github.com/IBM/fp-go/io"
IOE "github.com/IBM/fp-go/ioeither"
O "github.com/IBM/fp-go/option"
"github.com/stretchr/testify/assert"
)
func TestBuilder(t *testing.T) {
name := H.ContentType
withContentType := WithHeader(name)
withoutContentType := WithoutHeader(name)
b1 := F.Pipe1(
Default,
withContentType(C.Json),
)
b2 := F.Pipe1(
b1,
withContentType(C.TextPlain),
)
b3 := F.Pipe1(
b2,
withoutContentType,
)
assert.Equal(t, O.None[string](), Default.GetHeader(name))
assert.Equal(t, O.Of(C.Json), b1.GetHeader(name))
assert.Equal(t, O.Of(C.TextPlain), b2.GetHeader(name))
assert.Equal(t, O.None[string](), b3.GetHeader(name))
}
func TestBuilderWithQuery(t *testing.T) {
// add some query
withLimit := WithQueryArg("limit")("10")
withUrl := WithUrl("http://www.example.org?a=b")
b := F.Pipe2(
Default,
withLimit,
withUrl,
)
req := F.Pipe2(
b.Requester(),
IOE.Map[error](func(r *http.Request) *url.URL {
return r.URL
}),
IOE.ChainFirstIOK[error](func(u *url.URL) IO.IO[any] {
return IO.FromImpure(func() {
q := u.Query()
assert.Equal(t, "10", q.Get("limit"))
assert.Equal(t, "b", q.Get("a"))
})
}),
)
assert.True(t, E.IsRight(req()))
}

32
ioeither/http/di/di.go Normal file
View File

@@ -0,0 +1,32 @@
// 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 (
"net/http"
DI "github.com/IBM/fp-go/di"
IOE "github.com/IBM/fp-go/ioeither"
IOEH "github.com/IBM/fp-go/ioeither/http"
)
var (
// InjHttpClient is the [DI.InjectionToken] for the [http.DefaultClient]
InjHttpClient = DI.MakeTokenWithDefault0("HTTP_CLIENT", IOE.Of[error](http.DefaultClient))
// InjClient is the [DI.InjectionToken] for the default [IOEH.Client]
InjClient = DI.MakeTokenWithDefault1("CLIENT", InjHttpClient.IOEither(), IOE.Map[error](IOEH.MakeClient))
)

View File

@@ -16,58 +16,118 @@
package http
import (
"bytes"
"io"
"net/http"
B "github.com/IBM/fp-go/bytes"
ER "github.com/IBM/fp-go/errors"
FL "github.com/IBM/fp-go/file"
F "github.com/IBM/fp-go/function"
H "github.com/IBM/fp-go/http"
IOE "github.com/IBM/fp-go/ioeither"
IOEF "github.com/IBM/fp-go/ioeither/file"
J "github.com/IBM/fp-go/json"
T "github.com/IBM/fp-go/tuple"
)
type Client interface {
Do(req *http.Request) IOE.IOEither[error, *http.Response]
type (
// Requester is a reader that constructs a request
Requester = IOE.IOEither[error, *http.Request]
Client interface {
Do(Requester) IOE.IOEither[error, *http.Response]
}
client struct {
delegate *http.Client
doIOE func(*http.Request) IOE.IOEither[error, *http.Response]
}
)
var (
// MakeRequest is an eitherized version of [http.NewRequest]
MakeRequest = IOE.Eitherize3(http.NewRequest)
makeRequest = F.Bind13of3(MakeRequest)
// specialize
MakeGetRequest = makeRequest("GET", nil)
)
// MakeBodyRequest creates a request that carries a body
func MakeBodyRequest(method string, body IOE.IOEither[error, []byte]) func(url string) IOE.IOEither[error, *http.Request] {
onBody := F.Pipe1(
body,
IOE.Map[error](F.Flow2(
bytes.NewReader,
FL.ToReader[*bytes.Reader],
)),
)
onRelease := IOE.Of[error, io.Reader]
withMethod := F.Bind1of3(MakeRequest)(method)
return F.Flow2(
F.Bind1of2(withMethod),
IOE.WithResource[*http.Request](onBody, onRelease),
)
}
type client struct {
delegate *http.Client
}
func (client client) Do(req *http.Request) IOE.IOEither[error, *http.Response] {
return IOE.TryCatch(func() (*http.Response, error) {
return client.delegate.Do(req)
}, ER.IdentityError)
func (client client) Do(req Requester) IOE.IOEither[error, *http.Response] {
return F.Pipe1(
req,
IOE.Chain(client.doIOE),
)
}
func MakeClient(httpClient *http.Client) Client {
return client{delegate: httpClient}
return client{delegate: httpClient, doIOE: IOE.Eitherize1(httpClient.Do)}
}
func ReadAll(client Client) func(*http.Request) IOE.IOEither[error, []byte] {
return func(req *http.Request) IOE.IOEither[error, []byte] {
return IOEF.ReadAll(F.Pipe2(
req,
client.Do,
IOE.Map[error](func(resp *http.Response) io.ReadCloser {
return resp.Body
}),
),
)
}
// ReadFullResponse sends a request, reads the response as a byte array and represents the result as a tuple
func ReadFullResponse(client Client) func(Requester) IOE.IOEither[error, H.FullResponse] {
return F.Flow3(
client.Do,
IOE.ChainEitherK(H.ValidateResponse),
IOE.Chain(func(resp *http.Response) IOE.IOEither[error, H.FullResponse] {
return F.Pipe1(
F.Pipe3(
resp,
H.GetBody,
IOE.Of[error, io.ReadCloser],
IOEF.ReadAll[io.ReadCloser],
),
IOE.Map[error](F.Bind1st(T.MakeTuple2[*http.Response, []byte], resp)),
)
}),
)
}
func ReadText(client Client) func(*http.Request) IOE.IOEither[error, string] {
// ReadAll sends a request and reads the response as bytes
func ReadAll(client Client) func(Requester) IOE.IOEither[error, []byte] {
return F.Flow2(
ReadFullResponse(client),
IOE.Map[error](H.Body),
)
}
// ReadText sends a request, reads the response and represents the response as a text string
func ReadText(client Client) func(Requester) IOE.IOEither[error, string] {
return F.Flow2(
ReadAll(client),
IOE.Map[error](B.ToString),
)
}
func ReadJson[A any](client Client) func(*http.Request) IOE.IOEither[error, A] {
return F.Flow2(
ReadAll(client),
IOE.ChainEitherK(J.Unmarshal[A]),
// ReadJson sends a request, reads the response and parses the response as JSON
func ReadJson[A any](client Client) func(Requester) IOE.IOEither[error, A] {
return F.Flow3(
ReadFullResponse(client),
IOE.ChainFirstEitherK(F.Flow2(
H.Response,
H.ValidateJsonResponse,
)),
IOE.ChainEitherK(F.Flow2(
H.Body,
J.Unmarshal[A],
)),
)
}

View File

@@ -23,7 +23,6 @@ import (
AR "github.com/IBM/fp-go/array"
E "github.com/IBM/fp-go/either"
HE "github.com/IBM/fp-go/either/http"
"github.com/IBM/fp-go/errors"
F "github.com/IBM/fp-go/function"
IOE "github.com/IBM/fp-go/ioeither"
@@ -53,17 +52,16 @@ func TestRetryHttp(t *testing.T) {
client := MakeClient(&http.Client{})
action := func(status R.RetryStatus) IOE.IOEither[error, *PostItem] {
return F.Pipe2(
HE.GetRequest(urls[status.IterNumber]),
IOE.FromEither[error, *http.Request],
IOE.Chain(ReadJson[*PostItem](client)),
return F.Pipe1(
MakeGetRequest(urls[status.IterNumber]),
ReadJson[*PostItem](client),
)
}
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

@@ -19,6 +19,7 @@ import (
ET "github.com/IBM/fp-go/either"
I "github.com/IBM/fp-go/io"
G "github.com/IBM/fp-go/ioeither/generic"
IOO "github.com/IBM/fp-go/iooption"
L "github.com/IBM/fp-go/lazy"
O "github.com/IBM/fp-go/option"
)
@@ -63,6 +64,10 @@ func FromOption[A, E any](onNone func() E) func(o O.Option[A]) IOEither[E, A] {
return G.FromOption[IOEither[E, A]](onNone)
}
func FromIOOption[A, E any](onNone func() E) func(o IOO.IOOption[A]) IOEither[E, A] {
return G.FromIOOption[IOEither[E, A], IOO.IOOption[A]](onNone)
}
func ChainOptionK[A, B, E any](onNone func() E) func(func(A) O.Option[B]) func(IOEither[E, A]) IOEither[E, B] {
return G.ChainOptionK[IOEither[E, A], IOEither[E, B]](onNone)
}
@@ -192,12 +197,12 @@ func GetOrElse[E, A any](onLeft func(E) I.IO[A]) func(IOEither[E, A]) I.IO[A] {
}
// MonadChainTo composes to the second monad ignoring the return value of the first
func MonadChainTo[E, A, B any](fa IOEither[E, A], fb IOEither[E, B]) IOEither[E, B] {
func MonadChainTo[A, E, B any](fa IOEither[E, A], fb IOEither[E, B]) IOEither[E, B] {
return G.MonadChainTo(fa, fb)
}
// ChainTo composes to the second [IOEither] monad ignoring the return value of the first
func ChainTo[E, A, B any](fb IOEither[E, B]) func(IOEither[E, A]) IOEither[E, B] {
func ChainTo[A, E, B any](fb IOEither[E, B]) func(IOEither[E, A]) IOEither[E, B] {
return G.ChainTo[IOEither[E, A]](fb)
}
@@ -211,6 +216,14 @@ func ChainFirst[E, A, B any](f func(A) IOEither[E, B]) func(IOEither[E, A]) IOEi
return G.ChainFirst[IOEither[E, A]](f)
}
func MonadChainFirstEitherK[A, E, B any](ma IOEither[E, A], f func(A) ET.Either[E, B]) IOEither[E, A] {
return G.MonadChainFirstEitherK[IOEither[E, A]](ma, f)
}
func ChainFirstEitherK[A, E, B any](f func(A) ET.Either[E, B]) func(ma IOEither[E, A]) IOEither[E, A] {
return G.ChainFirstEitherK[IOEither[E, A]](f)
}
// MonadChainFirstIOK runs [IO] the monad returned by the function but returns the result of the original monad
func MonadChainFirstIOK[E, A, B any](ma IOEither[E, A], f func(A) I.IO[B]) IOEither[E, A] {
return G.MonadChainFirstIOK(ma, f)
@@ -258,3 +271,8 @@ func MonadFlap[E, B, A any](fab IOEither[E, func(A) B], a A) IOEither[E, B] {
func Flap[E, B, A any](a A) func(IOEither[E, func(A) B]) IOEither[E, B] {
return G.Flap[IOEither[E, func(A) B], IOEither[E, B]](a)
}
// ToIOOption converts an [IOEither] to an [IOO.IOOption]
func ToIOOption[E, A any](ioe IOEither[E, A]) IOO.IOOption[A] {
return G.ToIOOption[IOO.IOOption[A]](ioe)
}

View File

@@ -48,3 +48,63 @@ func TraverseRecordWithIndex[K comparable, E, A, B any](f func(K, A) IOEither[E,
func SequenceRecord[K comparable, E, A any](ma map[K]IOEither[E, A]) IOEither[E, map[K]A] {
return G.SequenceRecord[IOEither[E, A], IOEither[E, map[K]A]](ma)
}
// TraverseArraySeq transforms an array
func TraverseArraySeq[E, A, B any](f func(A) IOEither[E, B]) func([]A) IOEither[E, []B] {
return G.TraverseArraySeq[IOEither[E, B], IOEither[E, []B], []A](f)
}
// TraverseArrayWithIndexSeq transforms an array
func TraverseArrayWithIndexSeq[E, A, B any](f func(int, A) IOEither[E, B]) func([]A) IOEither[E, []B] {
return G.TraverseArrayWithIndexSeq[IOEither[E, B], IOEither[E, []B], []A](f)
}
// SequenceArraySeq converts a homogeneous sequence of either into an either of sequence
func SequenceArraySeq[E, A any](ma []IOEither[E, A]) IOEither[E, []A] {
return G.SequenceArraySeq[IOEither[E, A], IOEither[E, []A]](ma)
}
// TraverseRecordSeq transforms a record
func TraverseRecordSeq[K comparable, E, A, B any](f func(A) IOEither[E, B]) func(map[K]A) IOEither[E, map[K]B] {
return G.TraverseRecordSeq[IOEither[E, B], IOEither[E, map[K]B], map[K]A](f)
}
// TraverseRecordWithIndexSeq transforms a record
func TraverseRecordWithIndexSeq[K comparable, E, A, B any](f func(K, A) IOEither[E, B]) func(map[K]A) IOEither[E, map[K]B] {
return G.TraverseRecordWithIndexSeq[IOEither[E, B], IOEither[E, map[K]B], map[K]A](f)
}
// SequenceRecordSeq converts a homogeneous sequence of either into an either of sequence
func SequenceRecordSeq[K comparable, E, A any](ma map[K]IOEither[E, A]) IOEither[E, map[K]A] {
return G.SequenceRecordSeq[IOEither[E, A], IOEither[E, map[K]A]](ma)
}
// TraverseArrayPar transforms an array
func TraverseArrayPar[E, A, B any](f func(A) IOEither[E, B]) func([]A) IOEither[E, []B] {
return G.TraverseArrayPar[IOEither[E, B], IOEither[E, []B], []A](f)
}
// TraverseArrayWithIndexPar transforms an array
func TraverseArrayWithIndexPar[E, A, B any](f func(int, A) IOEither[E, B]) func([]A) IOEither[E, []B] {
return G.TraverseArrayWithIndexPar[IOEither[E, B], IOEither[E, []B], []A](f)
}
// SequenceArrayPar converts a homogeneous Paruence of either into an either of Paruence
func SequenceArrayPar[E, A any](ma []IOEither[E, A]) IOEither[E, []A] {
return G.SequenceArrayPar[IOEither[E, A], IOEither[E, []A]](ma)
}
// TraverseRecordPar transforms a record
func TraverseRecordPar[K comparable, E, A, B any](f func(A) IOEither[E, B]) func(map[K]A) IOEither[E, map[K]B] {
return G.TraverseRecordPar[IOEither[E, B], IOEither[E, map[K]B], map[K]A](f)
}
// TraverseRecordWithIndexPar transforms a record
func TraverseRecordWithIndexPar[K comparable, E, A, B any](f func(K, A) IOEither[E, B]) func(map[K]A) IOEither[E, map[K]B] {
return G.TraverseRecordWithIndexPar[IOEither[E, B], IOEither[E, map[K]B], map[K]A](f)
}
// SequenceRecordPar converts a homogeneous Paruence of either into an either of Paruence
func SequenceRecordPar[K comparable, E, A any](ma map[K]IOEither[E, A]) IOEither[E, map[K]A] {
return G.SequenceRecordPar[IOEither[E, A], IOEither[E, map[K]A]](ma)
}

View File

@@ -20,6 +20,7 @@ import (
ET "github.com/IBM/fp-go/either"
F "github.com/IBM/fp-go/function"
C "github.com/IBM/fp-go/internal/chain"
FI "github.com/IBM/fp-go/internal/fromio"
"github.com/IBM/fp-go/internal/optiont"
IO "github.com/IBM/fp-go/io/generic"
@@ -64,13 +65,6 @@ func FromEither[GA ~func() O.Option[A], E, A any](e ET.Either[E, A]) GA {
)
}
func FromIOEither[GA ~func() O.Option[A], GEA ~func() ET.Either[E, A], E, A any](ioe GEA) GA {
return F.Pipe1(
ioe,
IO.Map[GEA, GA](ET.ToOption[E, A]),
)
}
func MonadMap[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](fa GA, f func(A) B) GB {
return optiont.MonadMap(IO.MonadMap[GA, GB, O.Option[A], O.Option[B]], fa, f)
}
@@ -83,8 +77,48 @@ func MonadChain[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](fa GA,
return optiont.MonadChain(IO.MonadChain[GA, GB, O.Option[A], O.Option[B]], IO.MonadOf[GB, O.Option[B]], fa, f)
}
// MonadChainFirst runs the monad returned by the function but returns the result of the original monad
func MonadChainFirst[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](ma GA, f func(A) GB) GA {
return C.MonadChainFirst(
MonadChain[GA, GA, A, A],
MonadMap[GB, GA, B, A],
ma,
f,
)
}
// ChainFirst runs the monad returned by the function but returns the result of the original monad
func ChainFirst[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](f func(A) GB) func(GA) GA {
return C.ChainFirst(
MonadChain[GA, GA, A, A],
MonadMap[GB, GA, B, A],
f,
)
}
// MonadChainFirstIOK runs the monad returned by the function but returns the result of the original monad
func MonadChainFirstIOK[GA ~func() O.Option[A], GIOB ~func() B, A, B any](first GA, f func(A) GIOB) GA {
return FI.MonadChainFirstIOK(
MonadChain[GA, GA, A, A],
MonadMap[func() O.Option[B], GA, B, A],
FromIO[func() O.Option[B], GIOB, B],
first,
f,
)
}
// ChainFirstIOK runs the monad returned by the function but returns the result of the original monad
func ChainFirstIOK[GA ~func() O.Option[A], GIOB ~func() B, A, B any](f func(A) GIOB) func(GA) GA {
return FI.ChainFirstIOK(
MonadChain[GA, GA, A, A],
MonadMap[func() O.Option[B], GA, B, A],
FromIO[func() O.Option[B], GIOB, B],
f,
)
}
func Chain[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](f func(A) GB) func(GA) GB {
return F.Bind2nd(MonadChain[GA, GB, A, B], f)
return optiont.Chain(IO.MonadChain[GA, GB, O.Option[A], O.Option[B]], IO.MonadOf[GB, O.Option[B]], f)
}
func MonadChainOptionK[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](ma GA, f func(A) O.Option[B]) GB {
@@ -194,3 +228,17 @@ func Fold[GA ~func() O.Option[A], GB ~func() B, A, B any](onNone func() GB, onSo
func Defer[GA ~func() O.Option[A], A any](gen func() GA) GA {
return IO.Defer[GA](gen)
}
func MonadAlt[LAZY ~func() GIOA, GIOA ~func() O.Option[A], A any](first GIOA, second LAZY) GIOA {
return optiont.MonadAlt(
IO.Of[GIOA],
IO.MonadChain[GIOA, GIOA],
first,
second,
)
}
func Alt[LAZY ~func() GIOA, GIOA ~func() O.Option[A], A any](second LAZY) func(GIOA) GIOA {
return F.Bind2nd(MonadAlt[LAZY], second)
}

View File

@@ -0,0 +1,31 @@
// 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"
)
// WithResource constructs a function that creates a resource, then operates on it and then releases the resource
func WithResource[
GA ~func() O.Option[A],
GR ~func() O.Option[R],
GANY ~func() O.Option[ANY],
R, A, ANY any](onCreate GR, onRelease func(R) GANY) func(func(R) GA) GA {
// simply map to implementation of bracket
return F.Bind13of3(Bracket[GR, GA, GANY, R, A, ANY])(onCreate, F.Ignore2of2[O.Option[A]](onRelease))
}

View File

@@ -18,8 +18,9 @@ package iooption
import (
ET "github.com/IBM/fp-go/either"
I "github.com/IBM/fp-go/io"
IOE "github.com/IBM/fp-go/ioeither"
IO "github.com/IBM/fp-go/io"
G "github.com/IBM/fp-go/iooption/generic"
L "github.com/IBM/fp-go/lazy"
O "github.com/IBM/fp-go/option"
)
@@ -129,12 +130,37 @@ func Defer[A any](gen func() IOOption[A]) IOOption[A] {
return G.Defer[IOOption[A]](gen)
}
// FromIOEither converts an [IOEither] into an [IOOption]
func FromIOEither[E, A any](ioe IOE.IOEither[E, A]) IOOption[A] {
return G.FromIOEither[IOOption[A]](ioe)
}
// FromEither converts an [Either] into an [IOOption]
func FromEither[E, A any](e ET.Either[E, A]) IOOption[A] {
return G.FromEither[IOOption[A]](e)
}
// MonadAlt identifies an associative operation on a type constructor
func MonadAlt[A any](first IOOption[A], second L.Lazy[IOOption[A]]) IOOption[A] {
return G.MonadAlt(first, second)
}
// Alt identifies an associative operation on a type constructor
func Alt[A any](second L.Lazy[IOOption[A]]) func(IOOption[A]) IOOption[A] {
return G.Alt(second)
}
// MonadChainFirst runs the monad returned by the function but returns the result of the original monad
func MonadChainFirst[A, B any](ma IOOption[A], f func(A) IOOption[B]) IOOption[A] {
return G.MonadChainFirst[IOOption[A], IOOption[B]](ma, f)
}
// ChainFirst runs the monad returned by the function but returns the result of the original monad
func ChainFirst[A, B any](f func(A) IOOption[B]) func(IOOption[A]) IOOption[A] {
return G.ChainFirst[IOOption[A], IOOption[B]](f)
}
// MonadChainFirstIOK runs the monad returned by the function but returns the result of the original monad
func MonadChainFirstIOK[A, B any](first IOOption[A], f func(A) IO.IO[B]) IOOption[A] {
return G.MonadChainFirstIOK[IOOption[A], IO.IO[B]](first, f)
}
// ChainFirstIOK runs the monad returned by the function but returns the result of the original monad
func ChainFirstIOK[A, B any](f func(A) IO.IO[B]) func(IOOption[A]) IOOption[A] {
return G.ChainFirstIOK[IOOption[A], IO.IO[B]](f)
}

27
iooption/resource.go Normal file
View File

@@ -0,0 +1,27 @@
// 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 iooption
import (
G "github.com/IBM/fp-go/iooption/generic"
)
// WithResource constructs a function that creates a resource, then operates on it and then releases the resource
func WithResource[
R, A, ANY any](onCreate IOOption[R], onRelease func(R) IOOption[ANY]) func(func(R) IOOption[A]) IOOption[A] {
// just dispatch
return G.WithResource[IOOption[A], IOOption[R], IOOption[ANY]](onCreate, onRelease)
}

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

@@ -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"
L "github.com/IBM/fp-go/io/generic"
O "github.com/IBM/fp-go/option"
T "github.com/IBM/fp-go/tuple"
)
// FromLazy returns an iterator on top of a lazy function
func FromLazy[GU ~func() O.Option[T.Tuple2[GU, U]], LZ ~func() U, U any](l LZ) GU {
return F.Pipe1(
l,
L.Map[LZ, GU](F.Flow2(
F.Bind1st(T.MakeTuple2[GU, U], Empty[GU]()),
O.Of[T.Tuple2[GU, U]],
)),
)
}

32
iterator/stateless/io.go Normal file
View File

@@ -0,0 +1,32 @@
// 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 stateless
import (
IO "github.com/IBM/fp-go/io"
G "github.com/IBM/fp-go/iterator/stateless/generic"
L "github.com/IBM/fp-go/lazy"
)
// FromLazy returns an [Iterator] on top of a lazy function
func FromLazy[U any](l L.Lazy[U]) Iterator[U] {
return G.FromLazy[Iterator[U], L.Lazy[U]](l)
}
// FromIO returns an [Iterator] on top of an IO function
func FromIO[U any](io IO.IO[U]) Iterator[U] {
return G.FromLazy[Iterator[U], IO.IO[U]](io)
}

View File

@@ -0,0 +1,38 @@
// 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 stateless
import (
"math/rand"
"testing"
"github.com/stretchr/testify/assert"
)
func TestIteratorFromLazy(t *testing.T) {
num := rand.Int
cit := FromLazy(num)
// create arrays twice
c1 := ToArray(cit)
c2 := ToArray(cit)
assert.Equal(t, 1, len(c1))
assert.Equal(t, 1, len(c2))
assert.NotEqual(t, c1, c2)
}

View File

@@ -19,13 +19,13 @@ import (
G "github.com/IBM/fp-go/iterator/stateless/generic"
)
// StrictUniq converts an [Iterator] or arbitrary items into an [Iterator] or unique items
// StrictUniq converts an [Iterator] of arbitrary items into an [Iterator] or unique items
// where uniqueness is determined by the built-in uniqueness constraint
func StrictUniq[A comparable](as Iterator[A]) Iterator[A] {
return G.StrictUniq[Iterator[A]](as)
}
// Uniq converts an [Iterator] or arbitrary items into an [Iterator] or unique items
// Uniq converts an [Iterator] of arbitrary items into an [Iterator] or unique items
// where uniqueness is determined based on a key extractor function
func Uniq[A any, K comparable](f func(A) K) func(as Iterator[A]) Iterator[A] {
return G.Uniq[Iterator[A], K](f)

View File

@@ -15,16 +15,12 @@
package lambda
type (
// RecFct is the function called recursively
RecFct[T, R any] func(T) R
internalCombinator[T, R any] func(internalCombinator[T, R]) RecFct[T, R]
)
// Y is the Y-combinator based on https://dreamsongs.com/Files/WhyOfY.pdf
func Y[TRFRM ~func(RecFct[T, R]) RecFct[T, R], T, R any](f TRFRM) RecFct[T, R] {
g := func(h internalCombinator[T, R]) RecFct[T, R] {
func Y[Endo ~func(RecFct) RecFct, RecFct ~func(T) R, T, R any](f Endo) RecFct {
type internal[RecFct ~func(T) R, T, R any] func(internal[RecFct, T, R]) RecFct
g := func(h internal[RecFct, T, R]) RecFct {
return func(t T) R {
return f(h(h))(t)
}

View File

@@ -16,12 +16,13 @@
package lambda
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFactorial(t *testing.T) {
fct := Y(func(r RecFct[int, int]) RecFct[int, int] {
fct := Y(func(r func(int) int) func(int) int {
return func(n int) int {
if n <= 0 {
return 1
@@ -29,6 +30,5 @@ func TestFactorial(t *testing.T) {
return n * r(n-1)
}
})
fmt.Println(fct(10))
assert.Equal(t, 3628800, fct(10))
}

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

@@ -17,6 +17,7 @@
package iso
import (
EM "github.com/IBM/fp-go/endomorphism"
F "github.com/IBM/fp-go/function"
)
@@ -52,7 +53,7 @@ func Reverse[S, A any](sa Iso[S, A]) Iso[A, S] {
)
}
func modify[S, A any](f func(A) A, sa Iso[S, A], s S) S {
func modify[FCT ~func(A) A, S, A any](f FCT, sa Iso[S, A], s S) S {
return F.Pipe3(
s,
sa.Get,
@@ -62,8 +63,8 @@ func modify[S, A any](f func(A) A, sa Iso[S, A], s S) S {
}
// Modify applies a transformation
func Modify[S, A any](f func(A) A) func(Iso[S, A]) func(S) S {
return F.Curry3(modify[S, A])(f)
func Modify[S any, FCT ~func(A) A, A any](f FCT) func(Iso[S, A]) EM.Endomorphism[S] {
return EM.Curry3(modify[FCT, S, A])(f)
}
// Wrap wraps the value

View File

@@ -16,6 +16,7 @@
package lens
import (
EM "github.com/IBM/fp-go/endomorphism"
F "github.com/IBM/fp-go/function"
I "github.com/IBM/fp-go/optics/iso"
L "github.com/IBM/fp-go/optics/lens"
@@ -23,10 +24,10 @@ import (
// IsoAsLens converts an `Iso` to a `Lens`
func IsoAsLens[S, A any](sa I.Iso[S, A]) L.Lens[S, A] {
return L.MakeLensCurried(sa.Get, F.Flow2(sa.ReverseGet, F.Constant1[S, S]))
return L.MakeLensCurried(sa.Get, F.Flow2(sa.ReverseGet, F.Flow2(F.Constant1[S, S], EM.Of[func(S) S])))
}
// IsoAsLensRef converts an `Iso` to a `Lens`
func IsoAsLensRef[S, A any](sa I.Iso[*S, A]) L.Lens[*S, A] {
return L.MakeLensRefCurried(sa.Get, F.Flow2(sa.ReverseGet, F.Constant1[*S, *S]))
return L.MakeLensRefCurried(sa.Get, F.Flow2(sa.ReverseGet, F.Flow2(F.Constant1[*S, *S], EM.Of[func(*S) *S])))
}

View File

@@ -0,0 +1,39 @@
// 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 (
AA "github.com/IBM/fp-go/array/generic"
L "github.com/IBM/fp-go/optics/lens"
O "github.com/IBM/fp-go/option"
)
// AtHead focusses on the head of an array. The setter works as follows
// - if the new value is none, the result will be an empty array
// - if the new value is some and the array is empty, it creates a new array with one element
// - if the new value is some and the array is not empty, it replaces the head
func AtHead[AS []A, A any]() L.Lens[AS, O.Option[A]] {
return L.MakeLens(AA.Head[AS, A], func(as AS, a O.Option[A]) AS {
return O.MonadFold(a, AA.Empty[AS], func(v A) AS {
if AA.IsEmpty(as) {
return AA.Of[AS, A](v)
}
cpy := AA.Copy(as)
cpy[0] = v
return cpy
})
})
}

30
optics/lens/array/head.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 (
L "github.com/IBM/fp-go/optics/lens"
G "github.com/IBM/fp-go/optics/lens/array/generic"
O "github.com/IBM/fp-go/option"
)
// AtHead focusses on the head of an array. The setter works as follows
// - if the new value is none, the result will be an empty array
// - if the new value is some and the array is empty, it creates a new array with one element
// - if the new value is some and the array is not empty, it replaces the head
func AtHead[A any]() L.Lens[[]A, O.Option[A]] {
return G.AtHead[[]A]()
}

View File

@@ -0,0 +1,40 @@
// 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"
A "github.com/IBM/fp-go/array"
"github.com/IBM/fp-go/eq"
LT "github.com/IBM/fp-go/optics/lens/testing"
O "github.com/IBM/fp-go/option"
S "github.com/IBM/fp-go/string"
"github.com/stretchr/testify/assert"
)
var (
sEq = eq.FromEquals(S.Eq)
)
func TestLaws(t *testing.T) {
headLaws := LT.AssertLaws(t, O.Eq(sEq), A.Eq(sEq))(AtHead[string]())
assert.True(t, headLaws(A.Empty[string](), O.None[string]()))
assert.True(t, headLaws(A.Empty[string](), O.Of("a")))
assert.True(t, headLaws(A.From("a", "b"), O.None[string]()))
assert.True(t, headLaws(A.From("a", "b"), O.Of("c")))
}

View File

@@ -17,8 +17,8 @@
package lens
import (
EM "github.com/IBM/fp-go/endomorphism"
F "github.com/IBM/fp-go/function"
I "github.com/IBM/fp-go/identity"
O "github.com/IBM/fp-go/option"
)
@@ -26,13 +26,13 @@ type (
// Lens is a reference to a subpart of a data type
Lens[S, A any] struct {
Get func(s S) A
Set func(a A) func(S) S
Set func(a A) EM.Endomorphism[S]
}
)
// setCopy wraps a setter for a pointer into a setter that first creates a copy before
// modifying that copy
func setCopy[S, A any](setter func(*S, A) *S) func(s *S, a A) *S {
func setCopy[SET ~func(*S, A) *S, S, A any](setter SET) func(s *S, a A) *S {
return func(s *S, a A) *S {
copy := *s
return setter(&copy, a)
@@ -41,8 +41,8 @@ func setCopy[S, A any](setter func(*S, A) *S) func(s *S, a A) *S {
// setCopyCurried wraps a setter for a pointer into a setter that first creates a copy before
// modifying that copy
func setCopyCurried[S, A any](setter func(A) func(*S) *S) func(a A) func(*S) *S {
return func(a A) func(*S) *S {
func setCopyCurried[SET ~func(A) EM.Endomorphism[*S], S, A any](setter SET) func(a A) EM.Endomorphism[*S] {
return func(a A) EM.Endomorphism[*S] {
seta := setter(a)
return func(s *S) *S {
copy := *s
@@ -51,53 +51,53 @@ func setCopyCurried[S, A any](setter func(A) func(*S) *S) func(a A) func(*S) *S
}
}
// MakeLens creates a lens based on a getter and a setter function. Make sure that the setter creates a (shallow) copy of the
// MakeLens creates a [Lens] based on a getter and a setter function. Make sure that the setter creates a (shallow) copy of the
// data. This happens automatically if the data is passed by value. For pointers consider to use `MakeLensRef`
// and for other kinds of data structures that are copied by reference make sure the setter creates the copy.
func MakeLens[S, A any](get func(S) A, set func(S, A) S) Lens[S, A] {
return MakeLensCurried(get, F.Curry2(F.Swap(set)))
func MakeLens[GET ~func(S) A, SET ~func(S, A) S, S, A any](get GET, set SET) Lens[S, A] {
return MakeLensCurried(get, EM.Curry2(F.Swap(set)))
}
// MakeLensCurried creates a lens based on a getter and a setter function. Make sure that the setter creates a (shallow) copy of the
// MakeLensCurried creates a [Lens] based on a getter and a setter function. Make sure that the setter creates a (shallow) copy of the
// data. This happens automatically if the data is passed by value. For pointers consider to use `MakeLensRef`
// and for other kinds of data structures that are copied by reference make sure the setter creates the copy.
func MakeLensCurried[S, A any](get func(S) A, set func(A) func(S) S) Lens[S, A] {
func MakeLensCurried[GET ~func(S) A, SET ~func(A) EM.Endomorphism[S], S, A any](get GET, set SET) Lens[S, A] {
return Lens[S, A]{Get: get, Set: set}
}
// MakeLensRef creates a lens based on a getter and a setter function. The setter passed in does not have to create a shallow
// MakeLensRef creates a [Lens] based on a getter and a setter function. The setter passed in does not have to create a shallow
// copy, the implementation wraps the setter into one that copies the pointer before modifying it
//
// Such a lens assumes that property A of S always exists
func MakeLensRef[S, A any](get func(*S) A, set func(*S, A) *S) Lens[*S, A] {
// Such a [Lens] assumes that property A of S always exists
func MakeLensRef[GET ~func(*S) A, SET func(*S, A) *S, S, A any](get GET, set SET) Lens[*S, A] {
return MakeLens(get, setCopy(set))
}
// MakeLensRefCurried creates a lens based on a getter and a setter function. The setter passed in does not have to create a shallow
// MakeLensRefCurried creates a [Lens] based on a getter and a setter function. The setter passed in does not have to create a shallow
// copy, the implementation wraps the setter into one that copies the pointer before modifying it
//
// Such a lens assumes that property A of S always exists
func MakeLensRefCurried[S, A any](get func(*S) A, set func(A) func(*S) *S) Lens[*S, A] {
// Such a [Lens] assumes that property A of S always exists
func MakeLensRefCurried[S, A any](get func(*S) A, set func(A) EM.Endomorphism[*S]) Lens[*S, A] {
return MakeLensCurried(get, setCopyCurried(set))
}
// Id returns a lens implementing the identity operation
func id[S any](creator func(get func(S) S, set func(S, S) S) Lens[S, S]) Lens[S, S] {
// id returns a [Lens] implementing the identity operation
func id[GET ~func(S) S, SET ~func(S, S) S, S any](creator func(get GET, set SET) Lens[S, S]) Lens[S, S] {
return creator(F.Identity[S], F.Second[S, S])
}
// Id returns a lens implementing the identity operation
// Id returns a [Lens] implementing the identity operation
func Id[S any]() Lens[S, S] {
return id(MakeLens[S, S])
return id(MakeLens[EM.Endomorphism[S], func(S, S) S])
}
// IdRef returns a lens implementing the identity operation
// IdRef returns a [Lens] implementing the identity operation
func IdRef[S any]() Lens[*S, *S] {
return id(MakeLensRef[S, *S])
return id(MakeLensRef[EM.Endomorphism[*S], func(*S, *S) *S])
}
// Compose combines two lenses and allows to narrow down the focus to a sub-lens
func compose[S, A, B any](creator func(get func(S) B, set func(S, B) S) Lens[S, B], ab Lens[A, B]) func(Lens[S, A]) Lens[S, B] {
func compose[GET ~func(S) B, SET ~func(S, B) S, S, A, B any](creator func(get GET, set SET) Lens[S, B], ab Lens[A, B]) func(Lens[S, A]) Lens[S, B] {
abget := ab.Get
abset := ab.Set
return func(sa Lens[S, A]) Lens[S, B] {
@@ -114,7 +114,7 @@ func compose[S, A, B any](creator func(get func(S) B, set func(S, B) S) Lens[S,
// Compose combines two lenses and allows to narrow down the focus to a sub-lens
func Compose[S, A, B any](ab Lens[A, B]) func(Lens[S, A]) Lens[S, B] {
return compose(MakeLens[S, B], ab)
return compose(MakeLens[func(S) B, func(S, B) S], ab)
}
// ComposeOption combines a `Lens` that returns an optional value with a `Lens` that returns a definite value
@@ -141,7 +141,7 @@ func ComposeOption[S, B, A any](defaultA A) func(ab Lens[A, B]) func(Lens[S, O.O
func(s S, ob O.Option[B]) S {
return F.Pipe2(
ob,
O.Fold(unseta, func(b B) func(S) S {
O.Fold(unseta, func(b B) EM.Endomorphism[S] {
setbona := F.Flow2(
ab.Set(b),
seta,
@@ -158,7 +158,7 @@ func ComposeOption[S, B, A any](defaultA A) func(ab Lens[A, B]) func(Lens[S, O.O
),
)
}),
I.Ap[S, S](s),
EM.Ap(s),
)
},
)
@@ -174,7 +174,7 @@ func ComposeOption[S, B, A any](defaultA A) func(ab Lens[A, B]) func(Lens[S, O.O
// if the setter is called with `None[B]` and `A` does exist, 'B' is removed from 'A'
func ComposeOptions[S, B, A any](defaultA A) func(ab Lens[A, O.Option[B]]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
defa := F.Constant(defaultA)
noops := F.Constant(F.Identity[S])
noops := EM.Identity[S]
noneb := O.None[B]()
return func(ab Lens[A, O.Option[B]]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
unsetb := ab.Set(noneb)
@@ -189,15 +189,15 @@ func ComposeOptions[S, B, A any](defaultA A) func(ab Lens[A, O.Option[B]]) func(
sa.Get,
O.Chain(ab.Get),
),
func(b O.Option[B]) func(S) S {
func(b O.Option[B]) EM.Endomorphism[S] {
return func(s S) S {
return O.MonadFold(b, func() func(S) S {
return O.MonadFold(b, func() EM.Endomorphism[S] {
return F.Pipe2(
s,
sa.Get,
O.Fold(noops, F.Flow2(unsetb, seta)),
)
}, func(b B) func(S) S {
}, func(b B) EM.Endomorphism[S] {
// sets a B onto an A
setb := F.Flow2(
ab.Set(O.Some(b)),
@@ -218,20 +218,20 @@ func ComposeOptions[S, B, A any](defaultA A) func(ab Lens[A, O.Option[B]]) func(
// Compose combines two lenses and allows to narrow down the focus to a sub-lens
func ComposeRef[S, A, B any](ab Lens[A, B]) func(Lens[*S, A]) Lens[*S, B] {
return compose(MakeLensRef[S, B], ab)
return compose(MakeLensRef[func(*S) B, func(*S, B) *S], ab)
}
func modify[S, A any](f func(A) A, sa Lens[S, A], s S) S {
func modify[FCT ~func(A) A, S, A any](f FCT, sa Lens[S, A], s S) S {
return sa.Set(f(sa.Get(s)))(s)
}
// Modify changes a property of a lens by invoking a transformation function
// Modify changes a property of a [Lens] by invoking a transformation function
// if the transformed property has not changes, the method returns the original state
func Modify[S, A any](f func(A) A) func(Lens[S, A]) func(S) S {
return F.Curry3(modify[S, A])(f)
func Modify[S any, FCT ~func(A) A, A any](f FCT) func(Lens[S, A]) EM.Endomorphism[S] {
return EM.Curry3(modify[FCT, S, A])(f)
}
func IMap[E, A, B any](ab func(A) B, ba func(B) A) func(Lens[E, A]) Lens[E, B] {
func IMap[E any, AB ~func(A) B, BA ~func(B) A, A, B any](ab AB, ba BA) func(Lens[E, A]) Lens[E, B] {
return func(ea Lens[E, A]) Lens[E, B] {
return Lens[E, B]{Get: F.Flow2(ea.Get, ab), Set: F.Flow2(ba, ea.Set)}
}
@@ -239,7 +239,7 @@ func IMap[E, A, B any](ab func(A) B, ba func(B) A) func(Lens[E, A]) Lens[E, B] {
// fromPredicate returns a `Lens` for a property accessibly as a getter and setter that can be optional
// if the optional value is set then the nil value will be set instead
func fromPredicate[S, A any](creator func(get func(S) O.Option[A], set func(S, O.Option[A]) S) Lens[S, O.Option[A]], pred func(A) bool, nilValue A) func(sa Lens[S, A]) Lens[S, O.Option[A]] {
func fromPredicate[GET ~func(S) O.Option[A], SET ~func(S, O.Option[A]) S, S, A any](creator func(get GET, set SET) Lens[S, O.Option[A]], pred func(A) bool, nilValue A) func(sa Lens[S, A]) Lens[S, O.Option[A]] {
fromPred := O.FromPredicate(pred)
return func(sa Lens[S, A]) Lens[S, O.Option[A]] {
fold := O.Fold(F.Bind1of1(sa.Set)(nilValue), sa.Set)
@@ -247,7 +247,7 @@ func fromPredicate[S, A any](creator func(get func(S) O.Option[A], set func(S, O
return F.Pipe2(
a,
fold,
I.Ap[S, S](s),
EM.Ap(s),
)
})
}
@@ -256,13 +256,13 @@ func fromPredicate[S, A any](creator func(get func(S) O.Option[A], set func(S, O
// FromPredicate returns a `Lens` for a property accessibly as a getter and setter that can be optional
// if the optional value is set then the nil value will be set instead
func FromPredicate[S, A any](pred func(A) bool, nilValue A) func(sa Lens[S, A]) Lens[S, O.Option[A]] {
return fromPredicate(MakeLens[S, O.Option[A]], pred, nilValue)
return fromPredicate(MakeLens[func(S) O.Option[A], func(S, O.Option[A]) S], pred, nilValue)
}
// FromPredicateRef returns a `Lens` for a property accessibly as a getter and setter that can be optional
// if the optional value is set then the nil value will be set instead
func FromPredicateRef[S, A any](pred func(A) bool, nilValue A) func(sa Lens[*S, A]) Lens[*S, O.Option[A]] {
return fromPredicate(MakeLensRef[S, O.Option[A]], pred, nilValue)
return fromPredicate(MakeLensRef[func(*S) O.Option[A], func(*S, O.Option[A]) *S], pred, nilValue)
}
// FromPredicate returns a `Lens` for a property accessibly as a getter and setter that can be optional
@@ -278,7 +278,7 @@ func FromNillableRef[S, A any](sa Lens[*S, *A]) Lens[*S, O.Option[*A]] {
}
// fromNullableProp returns a `Lens` from a property that may be optional. The getter returns a default value for these items
func fromNullableProp[S, A any](creator func(get func(S) A, set func(S, A) S) Lens[S, A], isNullable func(A) O.Option[A], defaultValue A) func(sa Lens[S, A]) Lens[S, A] {
func fromNullableProp[GET ~func(S) A, SET ~func(S, A) S, S, A any](creator func(get GET, set SET) Lens[S, A], isNullable func(A) O.Option[A], defaultValue A) func(sa Lens[S, A]) Lens[S, A] {
return func(sa Lens[S, A]) Lens[S, A] {
return creator(F.Flow3(
sa.Get,
@@ -293,16 +293,16 @@ func fromNullableProp[S, A any](creator func(get func(S) A, set func(S, A) S) Le
// FromNullableProp returns a `Lens` from a property that may be optional. The getter returns a default value for these items
func FromNullableProp[S, A any](isNullable func(A) O.Option[A], defaultValue A) func(sa Lens[S, A]) Lens[S, A] {
return fromNullableProp(MakeLens[S, A], isNullable, defaultValue)
return fromNullableProp(MakeLens[func(S) A, func(S, A) S], isNullable, defaultValue)
}
// FromNullablePropRef returns a `Lens` from a property that may be optional. The getter returns a default value for these items
func FromNullablePropRef[S, A any](isNullable func(A) O.Option[A], defaultValue A) func(sa Lens[*S, A]) Lens[*S, A] {
return fromNullableProp(MakeLensRef[S, A], isNullable, defaultValue)
return fromNullableProp(MakeLensRef[func(*S) A, func(*S, A) *S], isNullable, defaultValue)
}
// fromFromOption returns a `Lens` from an option property. The getter returns a default value the setter will always set the some option
func fromOption[S, A any](creator func(get func(S) A, set func(S, A) S) Lens[S, A], defaultValue A) func(sa Lens[S, O.Option[A]]) Lens[S, A] {
func fromOption[GET ~func(S) A, SET ~func(S, A) S, S, A any](creator func(get GET, set SET) Lens[S, A], defaultValue A) func(sa Lens[S, O.Option[A]]) Lens[S, A] {
return func(sa Lens[S, O.Option[A]]) Lens[S, A] {
return creator(F.Flow2(
sa.Get,
@@ -316,10 +316,10 @@ func fromOption[S, A any](creator func(get func(S) A, set func(S, A) S) Lens[S,
// FromFromOption returns a `Lens` from an option property. The getter returns a default value the setter will always set the some option
func FromOption[S, A any](defaultValue A) func(sa Lens[S, O.Option[A]]) Lens[S, A] {
return fromOption(MakeLens[S, A], defaultValue)
return fromOption(MakeLens[func(S) A, func(S, A) S], defaultValue)
}
// FromFromOptionRef returns a `Lens` from an option property. The getter returns a default value the setter will always set the some option
func FromOptionRef[S, A any](defaultValue A) func(sa Lens[*S, O.Option[A]]) Lens[*S, A] {
return fromOption(MakeLensRef[S, A], defaultValue)
return fromOption(MakeLensRef[func(*S) A, func(*S, A) *S], defaultValue)
}

Some files were not shown because too many files have changed in this diff Show More