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

Compare commits

...

21 Commits

Author SHA1 Message Date
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
68 changed files with 3420 additions and 78 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 }}
-
@@ -65,7 +65,7 @@ jobs:
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}}

View File

@@ -52,6 +52,10 @@ func MonadMapRef[A, B any](as []A, f func(a *A) B) []B {
return bs
}
func MapWithIndex[A, B any](f func(int, A) B) func([]A) []B {
return G.MapWithIndex[[]A, []B](f)
}
func Map[A, B any](f func(a A) B) func([]A) []B {
return F.Bind2nd(MonadMap[A, B], f)
}
@@ -304,15 +308,26 @@ func SliceRight[A any](start int) func([]A) []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)

View File

@@ -150,6 +150,14 @@ 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)
}
@@ -296,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 {
@@ -306,6 +319,16 @@ func FoldMap[AS ~[]A, A, B any](m M.Monoid[B]) func(func(A) B) func(AS) B {
}
}
func FoldMapWithIndex[AS ~[]A, A, B any](m M.Monoid[B]) func(func(int, A) B) func(AS) B {
return func(f func(int, A) B) func(AS) B {
return func(as AS) B {
return array.ReduceWithIndex(as, func(idx int, cur B, a A) B {
return m.Concat(cur, f(idx, a))
}, m.Empty())
}
}
}
func Fold[AS ~[]A, A any](m M.Monoid[A]) func(AS) A {
return func(as AS) A {
return array.Reduce(as, m.Concat, m.Empty())

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

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

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

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

@@ -0,0 +1,168 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package erasure
import (
A "github.com/IBM/fp-go/array"
"github.com/IBM/fp-go/errors"
F "github.com/IBM/fp-go/function"
I "github.com/IBM/fp-go/identity"
IG "github.com/IBM/fp-go/identity/generic"
IOE "github.com/IBM/fp-go/ioeither"
L "github.com/IBM/fp-go/lazy"
O "github.com/IBM/fp-go/option"
R "github.com/IBM/fp-go/record"
T "github.com/IBM/fp-go/tuple"
"sync"
)
func providerToEntry(p Provider) T.Tuple2[string, ProviderFactory] {
return T.MakeTuple2(p.Provides().Id(), p.Factory())
}
func itemProviderToMap(p Provider) map[string][]ProviderFactory {
return R.Singleton(p.Provides().Id(), A.Of(p.Factory()))
}
var (
// missingProviderError returns a [ProviderFactory] that fails due to a missing dependency
missingProviderError = F.Flow4(
Dependency.String,
errors.OnSome[string]("no provider for dependency [%s]"),
IOE.Left[any, error],
F.Constant1[InjectableFactory, IOE.IOEither[error, any]],
)
// missingProviderErrorOrDefault returns the default [ProviderFactory] or an error
missingProviderErrorOrDefault = F.Flow3(
T.Replicate2[Dependency],
T.Map2(Dependency.ProviderFactory, F.Flow2(missingProviderError, F.Constant[ProviderFactory])),
T.Tupled2(O.MonadGetOrElse[ProviderFactory]),
)
emptyMulti any = A.Empty[any]()
// emptyMultiDependency returns a [ProviderFactory] for an empty, multi dependency
emptyMultiDependency = F.Constant1[Dependency](F.Constant1[InjectableFactory](IOE.Of[error](emptyMulti)))
// handleMissingProvider covers the case of a missing provider. It either
// returns an error or an empty multi value provider
handleMissingProvider = F.Flow2(
F.Ternary(isMultiDependency, emptyMultiDependency, missingProviderErrorOrDefault),
F.Constant[ProviderFactory],
)
// mergeItemProviders is a monoid for item provider factories
mergeItemProviders = R.UnionMonoid[string](A.Semigroup[ProviderFactory]())
// mergeProviders is a monoid for provider factories
mergeProviders = R.UnionLastMonoid[string, ProviderFactory]()
// collectItemProviders create a provider map for item providers
collectItemProviders = F.Flow2(
A.FoldMap[Provider](mergeItemProviders)(itemProviderToMap),
R.Map[string](itemProviderFactory),
)
// collectProviders collects non-item providers
collectProviders = F.Flow2(
A.Map(providerToEntry),
R.FromEntries[string, ProviderFactory],
)
// assembleProviders constructs the provider map for item and non-item providers
assembleProviders = F.Flow3(
A.Partition(isItemProvider),
T.Map2(collectProviders, collectItemProviders),
T.Tupled2(mergeProviders.Concat),
)
)
// isMultiDependency tests if a dependency is a container dependency
func isMultiDependency(dep Dependency) bool {
return dep.Flag()&Multi == Multi
}
// isItemProvider tests if a provivder provides a single item
func isItemProvider(provider Provider) bool {
return provider.Provides().Flag()&Item == Item
}
// itemProviderFactory combines multiple factories into one, returning an array
func itemProviderFactory(fcts []ProviderFactory) ProviderFactory {
return func(inj InjectableFactory) IOE.IOEither[error, any] {
return F.Pipe2(
fcts,
IOE.TraverseArray(I.Flap[IOE.IOEither[error, any]](inj)),
IOE.Map[error](F.ToAny[[]any]),
)
}
}
// MakeInjector creates an [InjectableFactory] based on a set of [Provider]s
func MakeInjector(providers []Provider) InjectableFactory {
type Result = IOE.IOEither[error, any]
type LazyResult = L.Lazy[Result]
// resolved stores the values resolved so far, key is the string ID
// of the token, value is a lazy result
var resolved sync.Map
// provide a mapping for all providers
factoryById := assembleProviders(providers)
// the actual factory, we need lazy initialization
var injFct InjectableFactory
// lazy initialization, so we can cross reference it
injFct = func(token Dependency) Result {
key := token.Id()
// according to https://github.com/golang/go/issues/44159 this
// is the best way to use the sync map
actual, loaded := resolved.Load(key)
if !loaded {
computeResult := L.MakeLazy(func() Result {
return F.Pipe5(
token,
T.Replicate2[Dependency],
T.Map2(F.Flow3(
Dependency.Id,
R.Lookup[ProviderFactory, string],
I.Ap[O.Option[ProviderFactory]](factoryById),
), handleMissingProvider),
T.Tupled2(O.MonadGetOrElse[ProviderFactory]),
IG.Ap[ProviderFactory](injFct),
IOE.Memoize[error, any],
)
})
actual, _ = resolved.LoadOrStore(key, F.Pipe1(
computeResult,
L.Memoize[Result],
))
}
return actual.(LazyResult)()
}
return injFct
}

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

@@ -0,0 +1,174 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package erasure
import (
"fmt"
A "github.com/IBM/fp-go/array"
E "github.com/IBM/fp-go/either"
F "github.com/IBM/fp-go/function"
I "github.com/IBM/fp-go/identity"
IO "github.com/IBM/fp-go/io"
IOG "github.com/IBM/fp-go/io/generic"
IOE "github.com/IBM/fp-go/ioeither"
IOO "github.com/IBM/fp-go/iooption"
Int "github.com/IBM/fp-go/number/integer"
O "github.com/IBM/fp-go/option"
R "github.com/IBM/fp-go/record"
)
type InjectableFactory = func(Dependency) IOE.IOEither[error, any]
type ProviderFactory = func(InjectableFactory) IOE.IOEither[error, any]
type paramIndex = map[int]int
type paramValue = map[int]any
type handler = func(paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue]
type Provider interface {
fmt.Stringer
// Provides returns the [Dependency] implemented by this provider
Provides() Dependency
// Factory returns s function that can create an instance of the dependency based on an [InjectableFactory]
Factory() ProviderFactory
}
type provider struct {
provides Dependency
factory ProviderFactory
}
func (p *provider) Provides() Dependency {
return p.provides
}
func (p *provider) Factory() ProviderFactory {
return p.factory
}
func (p *provider) String() string {
return fmt.Sprintf("Provider for [%s]", p.provides)
}
func MakeProvider(token Dependency, fct ProviderFactory) Provider {
return &provider{token, fct}
}
func mapFromToken(idx int, token Dependency) map[int]paramIndex {
return R.Singleton(token.Flag()&BehaviourMask, R.Singleton(idx, idx))
}
var (
mergeTokenMaps = R.UnionMonoid[int](R.UnionLastSemigroup[int, int]())
foldDeps = A.FoldMapWithIndex[Dependency](mergeTokenMaps)(mapFromToken)
mergeMaps = R.UnionLastMonoid[int, any]()
collectParams = R.CollectOrd[any, any](Int.Ord)(F.SK[int, any])
handlers = map[int]handler{
Identity: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return F.Pipe1(
mp,
IOE.TraverseRecord[int](getAt(res)),
)
}
},
Option: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return F.Pipe3(
mp,
IOG.TraverseRecord[IO.IO[map[int]E.Either[error, any]], paramIndex](getAt(res)),
IO.Map(R.Map[int](F.Flow2(
E.ToOption[error, any],
F.ToAny[O.Option[any]],
))),
IOE.FromIO[error, paramValue],
)
}
},
IOEither: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return F.Pipe2(
mp,
R.Map[int](F.Flow2(
getAt(res),
F.ToAny[IOE.IOEither[error, any]],
)),
IOE.Of[error, paramValue],
)
}
},
IOOption: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return F.Pipe2(
mp,
R.Map[int](F.Flow3(
getAt(res),
IOE.ToIOOption[error, any],
F.ToAny[IOO.IOOption[any]],
)),
IOE.Of[error, paramValue],
)
}
},
}
)
type Mapping = map[int]paramIndex
func getAt[T any](ar []T) func(idx int) T {
return func(idx int) T {
return ar[idx]
}
}
func handleMapping(mp Mapping) func(res []IOE.IOEither[error, any]) IOE.IOEither[error, []any] {
preFct := F.Pipe1(
mp,
R.Collect(func(idx int, p paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
return handlers[idx](p)
}),
)
doFct := F.Flow2(
I.Flap[IOE.IOEither[error, paramValue], []IOE.IOEither[error, any]],
IOE.TraverseArray[error, func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue], paramValue],
)
postFct := IOE.Map[error](F.Flow2(
A.Fold(mergeMaps),
collectParams,
))
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, []any] {
return F.Pipe2(
preFct,
doFct(res),
postFct,
)
}
}
// MakeProviderFactory constructs a [ProviderFactory] based on a set of [Dependency]s and
// a function that accepts the resolved dependencies to return a result
func MakeProviderFactory(
deps []Dependency,
fct func(param ...any) IOE.IOEither[error, any]) ProviderFactory {
return F.Flow3(
F.Curry2(A.MonadMap[Dependency, IOE.IOEither[error, any]])(deps),
handleMapping(foldDeps(deps)),
IOE.Chain(F.Unvariadic0(fct)),
)
}

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

@@ -0,0 +1,45 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package erasure
import (
"fmt"
O "github.com/IBM/fp-go/option"
)
const (
BehaviourMask = 0x0f
Identity = 0 // required dependency
Option = 1 // optional dependency
IOEither = 2 // lazy and required
IOOption = 3 // lazy and optional
TypeMask = 0xf0
Multi = 1 << 4 // array of implementations
Item = 2 << 4 // item of a multi token
)
// Dependency describes the relationship to a service
type Dependency interface {
fmt.Stringer
// Id returns a unique identifier for a token that can be used as a cache key
Id() string
// Flag returns a tag that identifies the behaviour of the dependency
Flag() int
// ProviderFactory optionally returns an attached [ProviderFactory] that represents the default for this dependency
ProviderFactory() O.Option[ProviderFactory]
}

33
di/injector.go Normal file
View File

@@ -0,0 +1,33 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package di
import (
DIE "github.com/IBM/fp-go/di/erasure"
F "github.com/IBM/fp-go/function"
IG "github.com/IBM/fp-go/identity/generic"
IOE "github.com/IBM/fp-go/ioeither"
RIOE "github.com/IBM/fp-go/readerioeither"
)
// Resolve performs a type safe resolution of a dependency
func Resolve[T any](token InjectionToken[T]) RIOE.ReaderIOEither[DIE.InjectableFactory, error, T] {
return F.Flow2(
IG.Ap[DIE.InjectableFactory](asDependency(token)),
IOE.ChainEitherK(token.Unerase),
)
}

281
di/provider.go Normal file
View File

@@ -0,0 +1,281 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package di
import (
A "github.com/IBM/fp-go/array"
DIE "github.com/IBM/fp-go/di/erasure"
E "github.com/IBM/fp-go/either"
"github.com/IBM/fp-go/errors"
F "github.com/IBM/fp-go/function"
IOE "github.com/IBM/fp-go/ioeither"
T "github.com/IBM/fp-go/tuple"
)
func lookupAt[T any](idx int, token Dependency[T]) func(params []any) E.Either[error, T] {
return F.Flow3(
A.Lookup[any](idx),
E.FromOption[any](errors.OnNone("No parameter at position %d", idx)),
E.Chain(token.Unerase),
)
}
func eraseProviderFactory0[R any](f func() IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] {
return func(params ...any) IOE.IOEither[error, any] {
return F.Pipe1(
f(),
IOE.Map[error](F.ToAny[R]),
)
}
}
func eraseProviderFactory1[T1 any, R any](
d1 Dependency[T1],
f func(T1) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] {
ft := T.Tupled1(f)
t1 := lookupAt[T1](0, d1)
return func(params ...any) IOE.IOEither[error, any] {
return F.Pipe3(
E.SequenceT1(t1(params)),
IOE.FromEither[error, T.Tuple1[T1]],
IOE.Chain(ft),
IOE.Map[error](F.ToAny[R]),
)
}
}
func eraseProviderFactory2[T1, T2 any, R any](
d1 Dependency[T1],
d2 Dependency[T2],
f func(T1, T2) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] {
ft := T.Tupled2(f)
t1 := lookupAt[T1](0, d1)
t2 := lookupAt[T2](1, d2)
return func(params ...any) IOE.IOEither[error, any] {
return F.Pipe3(
E.SequenceT2(t1(params), t2(params)),
IOE.FromEither[error, T.Tuple2[T1, T2]],
IOE.Chain(ft),
IOE.Map[error](F.ToAny[R]),
)
}
}
func eraseProviderFactory3[T1, T2, T3 any, R any](
d1 Dependency[T1],
d2 Dependency[T2],
d3 Dependency[T3],
f func(T1, T2, T3) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] {
ft := T.Tupled3(f)
t1 := lookupAt[T1](0, d1)
t2 := lookupAt[T2](1, d2)
t3 := lookupAt[T3](2, d3)
return func(params ...any) IOE.IOEither[error, any] {
return F.Pipe3(
E.SequenceT3(t1(params), t2(params), t3(params)),
IOE.FromEither[error, T.Tuple3[T1, T2, T3]],
IOE.Chain(ft),
IOE.Map[error](F.ToAny[R]),
)
}
}
func eraseProviderFactory4[T1, T2, T3, T4 any, R any](
d1 Dependency[T1],
d2 Dependency[T2],
d3 Dependency[T3],
d4 Dependency[T4],
f func(T1, T2, T3, T4) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] {
ft := T.Tupled4(f)
t1 := lookupAt[T1](0, d1)
t2 := lookupAt[T2](1, d2)
t3 := lookupAt[T3](2, d3)
t4 := lookupAt[T4](3, d4)
return func(params ...any) IOE.IOEither[error, any] {
return F.Pipe3(
E.SequenceT4(t1(params), t2(params), t3(params), t4(params)),
IOE.FromEither[error, T.Tuple4[T1, T2, T3, T4]],
IOE.Chain(ft),
IOE.Map[error](F.ToAny[R]),
)
}
}
func MakeProviderFactory0[R any](
fct func() IOE.IOEither[error, R],
) DIE.ProviderFactory {
return DIE.MakeProviderFactory(
A.Empty[DIE.Dependency](),
eraseProviderFactory0(fct),
)
}
// MakeTokenWithDefault0 create a unique `InjectionToken` for a specific type with an attached default provider
func MakeTokenWithDefault0[R any](name string, fct func() IOE.IOEither[error, R]) InjectionToken[R] {
return MakeTokenWithDefault[R](name, MakeProviderFactory0(fct))
}
func MakeProvider0[R any](
token InjectionToken[R],
fct func() IOE.IOEither[error, R],
) DIE.Provider {
return DIE.MakeProvider(
token,
MakeProviderFactory0(fct),
)
}
func MakeProviderFactory1[T1, R any](
d1 Dependency[T1],
fct func(T1) IOE.IOEither[error, R],
) DIE.ProviderFactory {
return DIE.MakeProviderFactory(
A.From[DIE.Dependency](d1),
eraseProviderFactory1(d1, fct),
)
}
// MakeTokenWithDefault1 create a unique `InjectionToken` for a specific type with an attached default provider
func MakeTokenWithDefault1[T1, R any](name string,
d1 Dependency[T1],
fct func(T1) IOE.IOEither[error, R]) InjectionToken[R] {
return MakeTokenWithDefault[R](name, MakeProviderFactory1(d1, fct))
}
func MakeProvider1[T1, R any](
token InjectionToken[R],
d1 Dependency[T1],
fct func(T1) IOE.IOEither[error, R],
) DIE.Provider {
return DIE.MakeProvider(
token,
MakeProviderFactory1(d1, fct),
)
}
func MakeProviderFactory2[T1, T2, R any](
d1 Dependency[T1],
d2 Dependency[T2],
fct func(T1, T2) IOE.IOEither[error, R],
) DIE.ProviderFactory {
return DIE.MakeProviderFactory(
A.From[DIE.Dependency](d1, d2),
eraseProviderFactory2(d1, d2, fct),
)
}
// MakeTokenWithDefault2 create a unique `InjectionToken` for a specific type with an attached default provider
func MakeTokenWithDefault2[T1, T2, R any](name string,
d1 Dependency[T1],
d2 Dependency[T2],
fct func(T1, T2) IOE.IOEither[error, R]) InjectionToken[R] {
return MakeTokenWithDefault[R](name, MakeProviderFactory2(d1, d2, fct))
}
func MakeProvider2[T1, T2, R any](
token InjectionToken[R],
d1 Dependency[T1],
d2 Dependency[T2],
fct func(T1, T2) IOE.IOEither[error, R],
) DIE.Provider {
return DIE.MakeProvider(
token,
MakeProviderFactory2(d1, d2, fct),
)
}
func MakeProviderFactory3[T1, T2, T3, R any](
d1 Dependency[T1],
d2 Dependency[T2],
d3 Dependency[T3],
fct func(T1, T2, T3) IOE.IOEither[error, R],
) DIE.ProviderFactory {
return DIE.MakeProviderFactory(
A.From[DIE.Dependency](d1, d2, d3),
eraseProviderFactory3(d1, d2, d3, fct),
)
}
// MakeTokenWithDefault3 create a unique `InjectionToken` for a specific type with an attached default provider
func MakeTokenWithDefault3[T1, T2, T3, R any](name string,
d1 Dependency[T1],
d2 Dependency[T2],
d3 Dependency[T3],
fct func(T1, T2, T3) IOE.IOEither[error, R]) InjectionToken[R] {
return MakeTokenWithDefault[R](name, MakeProviderFactory3(d1, d2, d3, fct))
}
func MakeProvider3[T1, T2, T3, R any](
token InjectionToken[R],
d1 Dependency[T1],
d2 Dependency[T2],
d3 Dependency[T3],
fct func(T1, T2, T3) IOE.IOEither[error, R],
) DIE.Provider {
return DIE.MakeProvider(
token,
MakeProviderFactory3(d1, d2, d3, fct),
)
}
func MakeProviderFactory4[T1, T2, T3, T4, R any](
d1 Dependency[T1],
d2 Dependency[T2],
d3 Dependency[T3],
d4 Dependency[T4],
fct func(T1, T2, T3, T4) IOE.IOEither[error, R],
) DIE.ProviderFactory {
return DIE.MakeProviderFactory(
A.From[DIE.Dependency](d1, d2, d3, d4),
eraseProviderFactory4(d1, d2, d3, d4, fct),
)
}
// MakeTokenWithDefault4 create a unique `InjectionToken` for a specific type with an attached default provider
func MakeTokenWithDefault4[T1, T2, T3, T4, R any](name string,
d1 Dependency[T1],
d2 Dependency[T2],
d3 Dependency[T3],
d4 Dependency[T4],
fct func(T1, T2, T3, T4) IOE.IOEither[error, R]) InjectionToken[R] {
return MakeTokenWithDefault[R](name, MakeProviderFactory4(d1, d2, d3, d4, fct))
}
func MakeProvider4[T1, T2, T3, T4, R any](
token InjectionToken[R],
d1 Dependency[T1],
d2 Dependency[T2],
d3 Dependency[T3],
d4 Dependency[T4],
fct func(T1, T2, T3, T4) IOE.IOEither[error, R],
) DIE.Provider {
return DIE.MakeProvider(
token,
MakeProviderFactory4(d1, d2, d3, d4, fct),
)
}
// ConstProvider simple implementation for a provider with a constant value
func ConstProvider[R any](token InjectionToken[R], value R) DIE.Provider {
return MakeProvider0[R](token, F.Constant(IOE.Of[error](value)))
}

351
di/provider_test.go Normal file
View File

@@ -0,0 +1,351 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package di
import (
"fmt"
"testing"
"time"
A "github.com/IBM/fp-go/array"
DIE "github.com/IBM/fp-go/di/erasure"
E "github.com/IBM/fp-go/either"
F "github.com/IBM/fp-go/function"
IOE "github.com/IBM/fp-go/ioeither"
O "github.com/IBM/fp-go/option"
"github.com/stretchr/testify/assert"
)
var (
INJ_KEY2 = MakeToken[string]("INJ_KEY2")
INJ_KEY1 = MakeToken[string]("INJ_KEY1")
INJ_KEY3 = MakeToken[string]("INJ_KEY3")
)
func TestSimpleProvider(t *testing.T) {
var staticCount int
staticValue := func(value string) func() IOE.IOEither[error, string] {
return func() IOE.IOEither[error, string] {
return func() E.Either[error, string] {
staticCount++
return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
}
}
}
var dynamicCount int
dynamicValue := func(value string) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
}
}
p1 := MakeProvider0(INJ_KEY1, staticValue("Carsten"))
p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Identity(), dynamicValue)
inj := DIE.MakeInjector(A.From(p1, p2))
i1 := Resolve(INJ_KEY1)
i2 := Resolve(INJ_KEY2)
res := IOE.SequenceT4(
i2(inj),
i1(inj),
i2(inj),
i1(inj),
)
r := res()
assert.True(t, E.IsRight(r))
assert.Equal(t, 1, staticCount)
assert.Equal(t, 1, dynamicCount)
}
func TestOptionalProvider(t *testing.T) {
var staticCount int
staticValue := func(value string) func() IOE.IOEither[error, string] {
return func() IOE.IOEither[error, string] {
return func() E.Either[error, string] {
staticCount++
return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
}
}
}
var dynamicCount int
dynamicValue := func(value O.Option[string]) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
}
}
p1 := MakeProvider0(INJ_KEY1, staticValue("Carsten"))
p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Option(), dynamicValue)
inj := DIE.MakeInjector(A.From(p1, p2))
i1 := Resolve(INJ_KEY1)
i2 := Resolve(INJ_KEY2)
res := IOE.SequenceT4(
i2(inj),
i1(inj),
i2(inj),
i1(inj),
)
r := res()
assert.True(t, E.IsRight(r))
assert.Equal(t, 1, staticCount)
assert.Equal(t, 1, dynamicCount)
}
func TestOptionalProviderMissingDependency(t *testing.T) {
var dynamicCount int
dynamicValue := func(value O.Option[string]) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
}
}
p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Option(), dynamicValue)
inj := DIE.MakeInjector(A.From(p2))
i2 := Resolve(INJ_KEY2)
res := IOE.SequenceT2(
i2(inj),
i2(inj),
)
r := res()
assert.True(t, E.IsRight(r))
assert.Equal(t, 1, dynamicCount)
}
func TestProviderMissingDependency(t *testing.T) {
var dynamicCount int
dynamicValue := func(value string) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
}
}
p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Identity(), dynamicValue)
inj := DIE.MakeInjector(A.From(p2))
i2 := Resolve(INJ_KEY2)
res := IOE.SequenceT2(
i2(inj),
i2(inj),
)
r := res()
assert.True(t, E.IsLeft(r))
assert.Equal(t, 0, dynamicCount)
}
func TestEagerAndLazyProvider(t *testing.T) {
var staticCount int
staticValue := func(value string) func() IOE.IOEither[error, string] {
return func() IOE.IOEither[error, string] {
return func() E.Either[error, string] {
staticCount++
return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
}
}
}
var dynamicCount int
dynamicValue := func(value string) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
dynamicCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
}
}
var lazyEagerCount int
lazyEager := func(laz IOE.IOEither[error, string], eager string) IOE.IOEither[error, string] {
return F.Pipe1(
laz,
IOE.Chain(func(lazValue string) IOE.IOEither[error, string] {
return func() E.Either[error, string] {
lazyEagerCount++
return E.Of[error](fmt.Sprintf("Dynamic based on [%s], [%s] at [%s]", lazValue, eager, time.Now()))
}
}),
)
}
p1 := MakeProvider0(INJ_KEY1, staticValue("Carsten"))
p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Identity(), dynamicValue)
p3 := MakeProvider2(INJ_KEY3, INJ_KEY2.IOEither(), INJ_KEY1.Identity(), lazyEager)
inj := DIE.MakeInjector(A.From(p1, p2, p3))
i3 := Resolve(INJ_KEY3)
r := i3(inj)()
fmt.Println(r)
assert.True(t, E.IsRight(r))
assert.Equal(t, 1, staticCount)
assert.Equal(t, 1, dynamicCount)
assert.Equal(t, 1, lazyEagerCount)
}
func TestItemProvider(t *testing.T) {
// define a multi token
injMulti := MakeMultiToken[string]("configs")
// provide some values
v1 := ConstProvider(injMulti.Item(), "Value1")
v2 := ConstProvider(injMulti.Item(), "Value2")
// mix in non-multi values
p1 := ConstProvider(INJ_KEY1, "Value3")
p2 := ConstProvider(INJ_KEY2, "Value4")
// populate the injector
inj := DIE.MakeInjector(A.From(p1, v1, p2, v2))
// access the multi value
multi := Resolve(injMulti.Container())
multiInj := multi(inj)
value := multiInj()
assert.Equal(t, E.Of[error](A.From("Value1", "Value2")), value)
}
func TestEmptyItemProvider(t *testing.T) {
// define a multi token
injMulti := MakeMultiToken[string]("configs")
// mix in non-multi values
p1 := ConstProvider(INJ_KEY1, "Value3")
p2 := ConstProvider(INJ_KEY2, "Value4")
// populate the injector
inj := DIE.MakeInjector(A.From(p1, p2))
// access the multi value
multi := Resolve(injMulti.Container())
multiInj := multi(inj)
value := multiInj()
assert.Equal(t, E.Of[error](A.Empty[string]()), value)
}
func TestDependencyOnMultiProvider(t *testing.T) {
// define a multi token
injMulti := MakeMultiToken[string]("configs")
// provide some values
v1 := ConstProvider(injMulti.Item(), "Value1")
v2 := ConstProvider(injMulti.Item(), "Value2")
// mix in non-multi values
p1 := ConstProvider(INJ_KEY1, "Value3")
p2 := ConstProvider(INJ_KEY2, "Value4")
fromMulti := func(val string, multi []string) IOE.IOEither[error, string] {
return IOE.Of[error](fmt.Sprintf("Val: %s, Multi: %s", val, multi))
}
p3 := MakeProvider2(INJ_KEY3, INJ_KEY1.Identity(), injMulti.Container().Identity(), fromMulti)
// populate the injector
inj := DIE.MakeInjector(A.From(p1, p2, v1, v2, p3))
r3 := Resolve(INJ_KEY3)
v := r3(inj)()
assert.Equal(t, E.Of[error]("Val: Value3, Multi: [Value1 Value2]"), v)
}
func TestTokenWithDefaultProvider(t *testing.T) {
// token without a default
injToken1 := MakeToken[string]("Token1")
// token with a default
injToken2 := MakeTokenWithDefault0("Token2", F.Constant(IOE.Of[error]("Carsten")))
// dependency
injToken3 := MakeToken[string]("Token3")
p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOE.IOEither[error, string] {
return IOE.Of[error](fmt.Sprintf("Token: %s", data))
})
// populate the injector
inj := DIE.MakeInjector(A.From(p3))
// resolving injToken3 should work and use the default provider for injToken2
r1 := Resolve(injToken1)
r3 := Resolve(injToken3)
// inj1 should not be available
assert.True(t, E.IsLeft(r1(inj)()))
// r3 should work
assert.Equal(t, E.Of[error]("Token: Carsten"), r3(inj)())
}
func TestTokenWithDefaultProviderAndOverride(t *testing.T) {
// token with a default
injToken2 := MakeTokenWithDefault0("Token2", F.Constant(IOE.Of[error]("Carsten")))
// dependency
injToken3 := MakeToken[string]("Token3")
p2 := ConstProvider(injToken2, "Override")
p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOE.IOEither[error, string] {
return IOE.Of[error](fmt.Sprintf("Token: %s", data))
})
// populate the injector
inj := DIE.MakeInjector(A.From(p2, p3))
// resolving injToken3 should work and use the default provider for injToken2
r3 := Resolve(injToken3)
// r3 should work
assert.Equal(t, E.Of[error]("Token: Override"), r3(inj)())
}

196
di/token.go Normal file
View File

@@ -0,0 +1,196 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package di
import (
"fmt"
"strconv"
"sync/atomic"
DIE "github.com/IBM/fp-go/di/erasure"
E "github.com/IBM/fp-go/either"
IO "github.com/IBM/fp-go/io"
IOE "github.com/IBM/fp-go/ioeither"
IOO "github.com/IBM/fp-go/iooption"
O "github.com/IBM/fp-go/option"
)
// Dependency describes the relationship to a service, that has a type and
// a behaviour such as required, option or lazy
type Dependency[T any] interface {
DIE.Dependency
// Unerase converts a value with erased type signature into a strongly typed value
Unerase(val any) E.Either[error, T]
}
// InjectionToken uniquely identifies a dependency by giving it an Id, Type and name
type InjectionToken[T any] interface {
Dependency[T]
// Identity idenifies this dependency as a mandatory, required dependency, it will be resolved eagerly and injected as `T`.
// If the dependency cannot be resolved, the resolution process fails
Identity() Dependency[T]
// Option identifies this dependency as optional, it will be resolved eagerly and injected as `O.Option[T]`.
// If the dependency cannot be resolved, the resolution process continues and the dependency is represented as `O.None[T]`
Option() Dependency[O.Option[T]]
// IOEither identifies this dependency as mandatory but it will be resolved lazily as a `IOE.IOEither[error, T]`. This
// value is memoized to make sure the dependency is a singleton.
// If the dependency cannot be resolved, the resolution process fails
IOEither() Dependency[IOE.IOEither[error, T]]
// IOOption identifies this dependency as optional but it will be resolved lazily as a `IOO.IOOption[T]`. This
// value is memoized to make sure the dependency is a singleton.
// If the dependency cannot be resolved, the resolution process continues and the dependency is represented as the none value.
IOOption() Dependency[IOO.IOOption[T]]
}
// MultiInjectionToken uniquely identifies a dependency by giving it an Id, Type and name.
type MultiInjectionToken[T any] interface {
// Container returns the injection token used to request an array of all provided items
Container() InjectionToken[[]T]
// Item returns the injection token used to provide an item
Item() InjectionToken[T]
}
// makeID creates a generator of unique string IDs
func makeId() IO.IO[string] {
var count atomic.Int64
return IO.MakeIO(func() string {
return strconv.FormatInt(count.Add(1), 16)
})
}
// genId is the common generator of unique string IDs
var genId = makeId()
type token[T any] struct {
name string
id string
flag int
toType func(val any) E.Either[error, T]
providerFactory O.Option[DIE.ProviderFactory]
}
func (t *token[T]) Id() string {
return t.id
}
func (t *token[T]) Flag() int {
return t.flag
}
func (t *token[T]) String() string {
return t.name
}
func (t *token[T]) Unerase(val any) E.Either[error, T] {
return t.toType(val)
}
func (t *token[T]) ProviderFactory() O.Option[DIE.ProviderFactory] {
return t.providerFactory
}
func makeToken[T any](name string, id string, typ int, unerase func(val any) E.Either[error, T], providerFactory O.Option[DIE.ProviderFactory]) Dependency[T] {
return &token[T]{name, id, typ, unerase, providerFactory}
}
type injectionToken[T any] struct {
token[T]
option Dependency[O.Option[T]]
ioeither Dependency[IOE.IOEither[error, T]]
iooption Dependency[IOO.IOOption[T]]
}
type multiInjectionToken[T any] struct {
container *injectionToken[[]T]
item *injectionToken[T]
}
func (i *injectionToken[T]) Identity() Dependency[T] {
return i
}
func (i *injectionToken[T]) Option() Dependency[O.Option[T]] {
return i.option
}
func (i *injectionToken[T]) IOEither() Dependency[IOE.IOEither[error, T]] {
return i.ioeither
}
func (i *injectionToken[T]) IOOption() Dependency[IOO.IOOption[T]] {
return i.iooption
}
func (i *injectionToken[T]) ProviderFactory() O.Option[DIE.ProviderFactory] {
return i.providerFactory
}
func (m *multiInjectionToken[T]) Container() InjectionToken[[]T] {
return m.container
}
func (m *multiInjectionToken[T]) Item() InjectionToken[T] {
return m.item
}
// makeToken create a unique `InjectionToken` for a specific type
func makeInjectionToken[T any](name string, providerFactory O.Option[DIE.ProviderFactory]) InjectionToken[T] {
id := genId()
toIdentity := toType[T]()
return &injectionToken[T]{
token[T]{name, id, DIE.Identity, toIdentity, providerFactory},
makeToken[O.Option[T]](fmt.Sprintf("Option[%s]", name), id, DIE.Option, toOptionType(toIdentity), providerFactory),
makeToken[IOE.IOEither[error, T]](fmt.Sprintf("IOEither[%s]", name), id, DIE.IOEither, toIOEitherType(toIdentity), providerFactory),
makeToken[IOO.IOOption[T]](fmt.Sprintf("IOOption[%s]", name), id, DIE.IOOption, toIOOptionType(toIdentity), providerFactory),
}
}
// MakeToken create a unique `InjectionToken` for a specific type
func MakeToken[T any](name string) InjectionToken[T] {
return makeInjectionToken[T](name, O.None[DIE.ProviderFactory]())
}
// MakeToken create a unique `InjectionToken` for a specific type
func MakeTokenWithDefault[T any](name string, providerFactory DIE.ProviderFactory) InjectionToken[T] {
return makeInjectionToken[T](name, O.Of(providerFactory))
}
// MakeMultiToken creates a [MultiInjectionToken]
func MakeMultiToken[T any](name string) MultiInjectionToken[T] {
id := genId()
toItem := toType[T]()
toContainer := toArrayType(toItem)
containerName := fmt.Sprintf("Container[%s]", name)
itemName := fmt.Sprintf("Item[%s]", name)
// empty factory
providerFactory := O.None[DIE.ProviderFactory]()
// container
container := &injectionToken[[]T]{
token[[]T]{containerName, id, DIE.Multi | DIE.Identity, toContainer, providerFactory},
makeToken[O.Option[[]T]](fmt.Sprintf("Option[%s]", containerName), id, DIE.Multi|DIE.Option, toOptionType(toContainer), providerFactory),
makeToken[IOE.IOEither[error, []T]](fmt.Sprintf("IOEither[%s]", containerName), id, DIE.Multi|DIE.IOEither, toIOEitherType(toContainer), providerFactory),
makeToken[IOO.IOOption[[]T]](fmt.Sprintf("IOOption[%s]", containerName), id, DIE.Multi|DIE.IOOption, toIOOptionType(toContainer), providerFactory),
}
// item
item := &injectionToken[T]{
token[T]{itemName, id, DIE.Item | DIE.Identity, toItem, providerFactory},
makeToken[O.Option[T]](fmt.Sprintf("Option[%s]", itemName), id, DIE.Item|DIE.Option, toOptionType(toItem), providerFactory),
makeToken[IOE.IOEither[error, T]](fmt.Sprintf("IOEither[%s]", itemName), id, DIE.Item|DIE.IOEither, toIOEitherType(toItem), providerFactory),
makeToken[IOO.IOOption[T]](fmt.Sprintf("IOOption[%s]", itemName), id, DIE.Item|DIE.IOOption, toIOOptionType(toItem), providerFactory),
}
// returns the token
return &multiInjectionToken[T]{container, item}
}

76
di/utils.go Normal file
View File

@@ -0,0 +1,76 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package di
import (
DIE "github.com/IBM/fp-go/di/erasure"
E "github.com/IBM/fp-go/either"
"github.com/IBM/fp-go/errors"
F "github.com/IBM/fp-go/function"
IOE "github.com/IBM/fp-go/ioeither"
IOO "github.com/IBM/fp-go/iooption"
O "github.com/IBM/fp-go/option"
)
// asDependency converts a generic type to a [DIE.Dependency]
func asDependency[T DIE.Dependency](t T) DIE.Dependency {
return t
}
// toType converts an any to a T
func toType[T any]() func(t any) E.Either[error, T] {
return E.ToType[T](errors.OnSome[any]("Value of type [%T] cannot be converted."))
}
// toOptionType converts an any to an Option[any] and then to an Option[T]
func toOptionType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, O.Option[T]] {
return F.Flow2(
toType[O.Option[any]](),
E.Chain(O.Fold(
F.Nullary2(O.None[T], E.Of[error, O.Option[T]]),
F.Flow2(
item,
E.Map[error](O.Of[T]),
),
)),
)
}
// toIOEitherType converts an any to an IOEither[error, any] and then to an IOEither[error, T]
func toIOEitherType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, IOE.IOEither[error, T]] {
return F.Flow2(
toType[IOE.IOEither[error, any]](),
E.Map[error](IOE.ChainEitherK(item)),
)
}
// toIOOptionType converts an any to an IOOption[any] and then to an IOOption[T]
func toIOOptionType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, IOO.IOOption[T]] {
return F.Flow2(
toType[IOO.IOOption[any]](),
E.Map[error](IOO.ChainOptionK(F.Flow2(
item,
E.ToOption[error, T],
))),
)
}
// toArrayType converts an any to a []T
func toArrayType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, []T] {
return F.Flow2(
toType[[]any](),
E.Chain(E.TraverseArray(item)),
)
}

83
di/utils_test.go Normal file
View File

@@ -0,0 +1,83 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package di
import (
"testing"
A "github.com/IBM/fp-go/array"
E "github.com/IBM/fp-go/either"
F "github.com/IBM/fp-go/function"
IOE "github.com/IBM/fp-go/ioeither"
O "github.com/IBM/fp-go/option"
"github.com/stretchr/testify/assert"
)
var (
toInt = toType[int]()
toString = toType[string]()
)
func TestToType(t *testing.T) {
// good cases
assert.Equal(t, E.Of[error](10), toInt(any(10)))
assert.Equal(t, E.Of[error]("Carsten"), toString(any("Carsten")))
assert.Equal(t, E.Of[error](O.Of("Carsten")), toType[O.Option[string]]()(any(O.Of("Carsten"))))
assert.Equal(t, E.Of[error](O.Of(any("Carsten"))), toType[O.Option[any]]()(any(O.Of(any("Carsten")))))
// failure
assert.False(t, E.IsRight(toInt(any("Carsten"))))
assert.False(t, E.IsRight(toType[O.Option[string]]()(O.Of(any("Carsten")))))
}
func TestToOptionType(t *testing.T) {
// shortcuts
toOptInt := toOptionType(toInt)
toOptString := toOptionType(toString)
// good cases
assert.Equal(t, E.Of[error](O.Of(10)), toOptInt(any(O.Of(any(10)))))
assert.Equal(t, E.Of[error](O.Of("Carsten")), toOptString(any(O.Of(any("Carsten")))))
// bad cases
assert.False(t, E.IsRight(toOptInt(any(10))))
assert.False(t, E.IsRight(toOptInt(any(O.Of(10)))))
}
func invokeIOEither[T any](e E.Either[error, IOE.IOEither[error, T]]) E.Either[error, T] {
return F.Pipe1(
e,
E.Chain(func(ioe IOE.IOEither[error, T]) E.Either[error, T] {
return ioe()
}),
)
}
func TestToIOEitherType(t *testing.T) {
// shortcuts
toIOEitherInt := toIOEitherType(toInt)
toIOEitherString := toIOEitherType(toString)
// good cases
assert.Equal(t, E.Of[error](10), invokeIOEither(toIOEitherInt(any(IOE.Of[error](any(10))))))
assert.Equal(t, E.Of[error]("Carsten"), invokeIOEither(toIOEitherString(any(IOE.Of[error](any("Carsten"))))))
// bad cases
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any(IOE.Of[error](any(10)))))))
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any(IOE.Of[error]("Carsten"))))))
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any("Carsten")))))
}
func TestToArrayType(t *testing.T) {
// shortcuts
toArrayString := toArrayType(toString)
// good cases
assert.Equal(t, E.Of[error](A.From("a", "b")), toArrayString(any(A.From(any("a"), any("b")))))
}

View File

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

View File

@@ -0,0 +1,51 @@
// 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 func(a A) A {
return a
}
}
func Compose[ENDO ~func(A) A, A any](f1, f2 ENDO) ENDO {
return func(a A) A {
return F.Pipe2(a, 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
}

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

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

2
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.26.0
)
require (

2
go.sum
View File

@@ -10,6 +10,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
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/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI=
github.com/urfave/cli/v2 v2.26.0/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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

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

@@ -0,0 +1,68 @@
// 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"
)
type (
// FormEndomorphism returns an [ENDO.Endomorphism] that transforms a form
FormEndomorphism = ENDO.Endomorphism[url.Values]
)
var (
// Default is the default form field
Default = make(url.Values)
noField = O.None[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) FormEndomorphism {
return F.Flow3(
O.Of[string],
AtValue(name).Set,
ENDO.Of[func(url.Values) url.Values],
)
}
// WithoutValue creates a [FormBuilder] that removes a field
func WithoutValue(name string) FormEndomorphism {
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))
}

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

@@ -106,6 +106,15 @@ func MonadMap[GA ~[]A, GB ~[]B, A, B any](as GA, f func(a A) B) GB {
return bs
}
func MonadMapWithIndex[GA ~[]A, GB ~[]B, A, B any](as GA, f func(idx int, a A) B) GB {
count := len(as)
bs := make(GB, count)
for i := count - 1; i >= 0; i-- {
bs[i] = f(i, as[i])
}
return bs
}
func ConstNil[GA ~[]A, A any]() GA {
return (GA)(nil)
}

View File

@@ -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,250 @@
// 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"
ENDO "github.com/IBM/fp-go/endomorphism"
F "github.com/IBM/fp-go/function"
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"
)
type (
Builder struct {
method O.Option[string]
url string
headers http.Header
body O.Option[IOE.IOEither[error, []byte]]
}
// BuilderEndomorphism returns an [ENDO.Endomorphism] that transforms a builder
BuilderEndomorphism = 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)
// 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)
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]]()
// WithMethod creates a [BuilderBuilder] for a certain method
WithMethod = Method.Set
// WithUrl creates a [BuilderBuilder] for a certain method
WithUrl = Url.Set
// WithHeaders creates a [BuilderBuilder] for a set of headers
WithHeaders = Headers.Set
// WithBody creates a [BuilderBuilder] for a request body
WithBody = F.Flow2(
O.Of[IOE.IOEither[error, []byte]],
Body.Set,
)
// WithContentType adds the content type header
WithContentType = WithHeader("Content-Type")
// 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)
// Requester creates a requester from a builder
Requester = (*Builder).Requester
// WithoutBody creates a [BuilderBuilder] to remove the body
WithoutBody = Body.Set(noBody)
)
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) 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) AddHeaderHeader(name, value string) *Builder {
builder.headers.Add(name, value)
return builder
}
func (builder *Builder) Requester() IOEH.Requester {
return F.Pipe3(
builder.GetBody(),
O.Map(IOE.Map[error](bytes.NewReader)),
O.GetOrElse(F.Constant(IOE.Of[error, *bytes.Reader](nil))),
IOE.Chain(func(rdr *bytes.Reader) IOE.IOEither[error, *http.Request] {
return IOE.TryCatchError(func() (*http.Request, error) {
req, err := http.NewRequest(builder.GetMethod(), builder.GetUrl(), rdr)
if err == nil {
for name, value := range builder.GetHeaders() {
req.Header[name] = value
}
if rdr != nil {
req.Header.Set("Content-Length", strconv.FormatInt(rdr.Size(), 10))
}
}
return req, err
})
}),
)
}
// 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[*Builder, O.Option[string]](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 [BuilderBuilder] for a certain header
func WithHeader(name string) func(value string) BuilderEndomorphism {
return F.Flow3(
O.Of[string],
Header(name).Set,
ENDO.Of[func(*Builder) *Builder],
)
}
// WithoutHeader creates a [BuilderBuilder] to remove a certain header
func WithoutHeader(name string) BuilderEndomorphism {
return Header(name).Set(noHeader)
}
// WithFormData creates a [BuilderBuilder] to send form data payload
func WithFormData(value url.Values) BuilderEndomorphism {
return F.Flow2(
F.Pipe4(
value,
url.Values.Encode,
S.ToBytes,
IOE.Of[error, []byte],
WithBody,
),
WithContentType("application/x-www-form-urlencoded"),
)
}
// WithJson creates a [BuilderBuilder] to send JSON payload
func WithJson[T any](data T) BuilderEndomorphism {
return F.Flow2(
F.Pipe3(
data,
J.Marshal[T],
IOE.FromEither[error, []byte],
WithBody,
),
WithContentType("application/json"),
)
}

View File

@@ -0,0 +1,51 @@
// 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 (
"testing"
F "github.com/IBM/fp-go/function"
O "github.com/IBM/fp-go/option"
"github.com/stretchr/testify/assert"
)
func TestBuiler(t *testing.T) {
name := "Content-type"
withContentType := WithHeader(name)
withoutContentType := WithoutHeader(name)
b1 := F.Pipe1(
Default,
withContentType("application/json"),
)
b2 := F.Pipe1(
b1,
withContentType("text/plain"),
)
b3 := F.Pipe1(
b2,
withoutContentType,
)
assert.Equal(t, O.None[string](), Default.GetHeader(name))
assert.Equal(t, O.Of("application/json"), b1.GetHeader(name))
assert.Equal(t, O.Of("text/plain"), b2.GetHeader(name))
assert.Equal(t, O.None[string](), b3.GetHeader(name))
}

32
ioeither/http/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 http
import (
"net/http"
DI "github.com/IBM/fp-go/di"
IOE "github.com/IBM/fp-go/ioeither"
L "github.com/IBM/fp-go/lazy"
)
var (
// InjHttpClient is the injection token for the default http client
InjHttpClient = DI.MakeTokenWithDefault0("HTTP_CLIENT", L.Of(IOE.Of[error](http.DefaultClient)))
// InjClient is the injection token for the default [Client]
InjClient = DI.MakeTokenWithDefault1("CLIENT", InjHttpClient.IOEither(), IOE.Map[error](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,10 +52,9 @@ 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),
)
}

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

@@ -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

@@ -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

@@ -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

@@ -65,7 +65,7 @@ func SequenceArray[A any](ma []Option[A]) Option[[]A] {
func CompactArrayG[A1 ~[]Option[A], A2 ~[]A, A any](fa A1) A2 {
return RA.Reduce(fa, func(out A2, value Option[A]) A2 {
return MonadFold(value, F.Constant(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

View File

@@ -34,3 +34,14 @@ func TestSequenceArray(t *testing.T) {
assert.Equal(t, res, Of([]int{1, 2}))
}
func TestCompactArray(t *testing.T) {
ar := []Option[string]{
Of("ok"),
None[string](),
Of("ok"),
}
res := CompactArray(ar)
assert.Equal(t, 2, len(res))
}

View File

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

View File

@@ -99,6 +99,14 @@ func Collect[M ~map[K]V, GR ~[]R, K comparable, V, R any](f func(K, V) R) func(M
return F.Bind2nd(collect[M, GR, K, V, R], f)
}
func CollectOrd[M ~map[K]V, GR ~[]R, K comparable, V, R any](o ord.Ord[K]) func(f func(K, V) R) func(M) GR {
return func(f func(K, V) R) func(M) GR {
return func(r M) GR {
return collectOrd[M, GR](o, r, f)
}
}
}
func Reduce[M ~map[K]V, K comparable, V, R any](f func(R, V) R, initial R) func(M) R {
return func(r M) R {
return G.Reduce(r, f, initial)
@@ -523,3 +531,12 @@ func MonadFlap[GFAB ~map[K]func(A) B, GB ~map[K]B, K comparable, A, B any](fab G
func Flap[GFAB ~map[K]func(A) B, GB ~map[K]B, K comparable, A, B any](a A) func(GFAB) GB {
return FC.Flap(MonadMap[GFAB, GB], a)
}
func Copy[M ~map[K]V, K comparable, V any](m M) M {
return duplicate(m)
}
func Clone[M ~map[K]V, K comparable, V any](f func(V) V) func(m M) M {
// impementation assumes that map does not optimize for the empty map
return Map[M, M](f)
}

View File

@@ -49,6 +49,11 @@ func Collect[K comparable, V, R any](f func(K, V) R) func(map[K]V) []R {
return G.Collect[map[K]V, []R](f)
}
// CollectOrd applies a collector function to the key value pairs in a map and returns the result as an array
func CollectOrd[V, R any, K comparable](o ord.Ord[K]) func(func(K, V) R) func(map[K]V) []R {
return G.CollectOrd[map[K]V, []R](o)
}
func Reduce[K comparable, V, R any](f func(R, V) R, initial R) func(map[K]V) R {
return G.Reduce[map[K]V](f, initial)
}
@@ -279,3 +284,13 @@ func MonadFlap[B any, K comparable, A any](fab map[K]func(A) B, a A) map[K]B {
func Flap[B any, K comparable, A any](a A) func(map[K]func(A) B) map[K]B {
return G.Flap[map[K]func(A) B, map[K]B](a)
}
// Copy creates a shallow copy of the map
func Copy[K comparable, V any](m map[K]V) map[K]V {
return G.Copy[map[K]V](m)
}
// Clone creates a deep copy of the map using the provided endomorphism to clone the values
func Clone[K comparable, V any](f func(V) V) func(m map[K]V) map[K]V {
return G.Clone[map[K]V](f)
}

View File

@@ -21,6 +21,7 @@ import (
"strings"
"testing"
A "github.com/IBM/fp-go/array"
"github.com/IBM/fp-go/internal/utils"
O "github.com/IBM/fp-go/option"
S "github.com/IBM/fp-go/string"
@@ -131,3 +132,20 @@ func ExampleValuesOrd() {
// Output: [c b a]
}
func TestCopyVsClone(t *testing.T) {
slc := []string{"b", "c"}
src := map[string][]string{
"a": slc,
}
// make a shallow copy
cpy := Copy(src)
// make a deep copy
cln := Clone[string](A.Copy[string])(src)
assert.Equal(t, cpy, cln)
// make a modification to the original slice
slc[0] = "d"
assert.NotEqual(t, cpy, cln)
assert.Equal(t, src, cpy)
}

View File

@@ -52,7 +52,7 @@ func TestIOEitherToOption2(t *testing.T) {
content,
IOEF.WriteFile(filepath.Join(tmpDir, "test.txt"), os.ModePerm),
IOE.Swap[error, []byte],
IOO.FromIOEither[[]byte, error],
IOE.ToIOOption[[]byte, error],
)
fmt.Println(resIOO())