1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-08-10 22:31:32 +02:00

fix: add support for context sensitive readers

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
Dr. Carsten Leue
2023-07-14 23:52:14 +02:00
parent 5020437b6a
commit 84c3e3ff88
101 changed files with 4440 additions and 13 deletions

15
reader/array.go Normal file
View File

@@ -0,0 +1,15 @@
package reader
import (
G "github.com/ibm/fp-go/reader/generic"
)
// TraverseArray transforms an array
func TraverseArray[R, A, B any](f func(A) Reader[R, B]) func([]A) Reader[R, []B] {
return G.TraverseArray[Reader[R, B], Reader[R, []B], []A](f)
}
// SequenceArray converts a homogeneous sequence of either into an either of sequence
func SequenceArray[R, A any](ma []Reader[R, A]) Reader[R, []A] {
return G.SequenceArray[Reader[R, A], Reader[R, []A]](ma)
}

25
reader/array_test.go Normal file
View File

@@ -0,0 +1,25 @@
package reader
import (
"context"
"testing"
A "github.com/ibm/fp-go/array"
F "github.com/ibm/fp-go/function"
"github.com/stretchr/testify/assert"
)
func TestSequenceArray(t *testing.T) {
n := 10
readers := A.MakeBy(n, Of[context.Context, int])
exp := A.MakeBy(n, F.Identity[int])
g := F.Pipe1(
readers,
SequenceArray[context.Context, int],
)
assert.Equal(t, exp, g(context.Background()))
}

49
reader/curry.go Normal file
View File

@@ -0,0 +1,49 @@
package reader
import (
G "github.com/ibm/fp-go/reader/generic"
)
// these functions curry a golang function with the context as the firsr parameter into a reader with the context as the last parameter, which
// is a equivalent to a function returning a reader of that context
// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention
func Curry0[R, A any](f func(R) A) Reader[R, A] {
return G.Curry0[Reader[R, A]](f)
}
func Curry1[R, T1, A any](f func(R, T1) A) func(T1) Reader[R, A] {
return G.Curry1[Reader[R, A]](f)
}
func Curry2[R, T1, T2, A any](f func(R, T1, T2) A) func(T1) func(T2) Reader[R, A] {
return G.Curry2[Reader[R, A]](f)
}
func Curry3[R, T1, T2, T3, A any](f func(R, T1, T2, T3) A) func(T1) func(T2) func(T3) Reader[R, A] {
return G.Curry3[Reader[R, A]](f)
}
func Curry4[R, T1, T2, T3, T4, A any](f func(R, T1, T2, T3, T4) A) func(T1) func(T2) func(T3) func(T4) Reader[R, A] {
return G.Curry4[Reader[R, A]](f)
}
func Uncurry0[R, A any](f Reader[R, A]) func(R) A {
return G.Uncurry0(f)
}
func Uncurry1[R, T1, A any](f func(T1) Reader[R, A]) func(R, T1) A {
return G.Uncurry1(f)
}
func Uncurry2[R, T1, T2, A any](f func(T1) func(T2) Reader[R, A]) func(R, T1, T2) A {
return G.Uncurry2(f)
}
func Uncurry3[R, T1, T2, T3, A any](f func(T1) func(T2) func(T3) Reader[R, A]) func(R, T1, T2, T3) A {
return G.Uncurry3(f)
}
func Uncurry4[R, T1, T2, T3, T4, A any](f func(T1) func(T2) func(T3) func(T4) Reader[R, A]) func(R, T1, T2, T3, T4) A {
return G.Uncurry4(f)
}

25
reader/from.go Normal file
View File

@@ -0,0 +1,25 @@
package reader
import (
G "github.com/ibm/fp-go/reader/generic"
)
func From0[R, A any](f func(R) A) Reader[R, A] {
return G.From0[Reader[R, A]](f)
}
func From1[R, T1, A any](f func(R, T1) A) func(T1) Reader[R, A] {
return G.From1[Reader[R, A]](f)
}
func From2[R, T1, T2, A any](f func(R, T1, T2) A) func(T1, T2) Reader[R, A] {
return G.From2[Reader[R, A]](f)
}
func From3[R, T1, T2, T3, A any](f func(R, T1, T2, T3) A) func(T1, T2, T3) Reader[R, A] {
return G.From3[Reader[R, A]](f)
}
func From4[R, T1, T2, T3, T4, A any](f func(R, T1, T2, T3, T4) A) func(T1, T2, T3, T4) Reader[R, A] {
return G.From4[Reader[R, A]](f)
}

31
reader/generic/array.go Normal file
View File

@@ -0,0 +1,31 @@
package generic
import (
F "github.com/ibm/fp-go/function"
RA "github.com/ibm/fp-go/internal/array"
)
// TraverseArray transforms an array
func MonadTraverseArray[GB ~func(R) B, GBS ~func(R) BBS, AAS ~[]A, BBS ~[]B, R, A, B any](tas AAS, f func(A) GB) GBS {
return RA.MonadTraverse[AAS](
Of[GBS, R, BBS],
Map[GBS, func(R) func(B) BBS, R, BBS, func(B) BBS],
Ap[GB, GBS, func(R) func(B) BBS, R, B, BBS],
tas, f,
)
}
// TraverseArray transforms an array
func TraverseArray[GB ~func(R) B, GBS ~func(R) BBS, AAS ~[]A, BBS ~[]B, R, A, B any](f func(A) GB) func(AAS) GBS {
return RA.Traverse[AAS](
Of[GBS, R, BBS],
Map[GBS, func(R) func(B) BBS, R, BBS, func(B) BBS],
Ap[GB, GBS, func(R) func(B) BBS, R, B, BBS],
f,
)
}
// SequenceArray converts a homogeneous sequence of either into an either of sequence
func SequenceArray[GA ~func(R) A, GAS ~func(R) AAS, AAS ~[]A, GAAS ~[]GA, R, A any](ma GAAS) GAS {
return MonadTraverseArray[GA, GAS](ma, F.Identity[GA])
}

61
reader/generic/curry.go Normal file
View File

@@ -0,0 +1,61 @@
package generic
import (
F "github.com/ibm/fp-go/function"
)
// these functions curry a golang function with the context as the firsr parameter into a reader with the context as the last parameter, which
// is a equivalent to a function returning a reader of that context
// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention
func Curry0[GA ~func(R) A, R, A any](f func(R) A) GA {
return MakeReader[GA](f)
}
func Curry1[GA ~func(R) A, R, T1, A any](f func(R, T1) A) func(T1) GA {
return F.Curry1(From1[GA](f))
}
func Curry2[GA ~func(R) A, R, T1, T2, A any](f func(R, T1, T2) A) func(T1) func(T2) GA {
return F.Curry2(From2[GA](f))
}
func Curry3[GA ~func(R) A, R, T1, T2, T3, A any](f func(R, T1, T2, T3) A) func(T1) func(T2) func(T3) GA {
return F.Curry3(From3[GA](f))
}
func Curry4[GA ~func(R) A, R, T1, T2, T3, T4, A any](f func(R, T1, T2, T3, T4) A) func(T1) func(T2) func(T3) func(T4) GA {
return F.Curry4(From4[GA](f))
}
func Uncurry0[GA ~func(R) A, R, A any](f GA) func(R) A {
return f
}
func Uncurry1[GA ~func(R) A, R, T1, A any](f func(T1) GA) func(R, T1) A {
uc := F.Uncurry1(f)
return func(r R, t1 T1) A {
return uc(t1)(r)
}
}
func Uncurry2[GA ~func(R) A, R, T1, T2, A any](f func(T1) func(T2) GA) func(R, T1, T2) A {
uc := F.Uncurry2(f)
return func(r R, t1 T1, t2 T2) A {
return uc(t1, t2)(r)
}
}
func Uncurry3[GA ~func(R) A, R, T1, T2, T3, A any](f func(T1) func(T2) func(T3) GA) func(R, T1, T2, T3) A {
uc := F.Uncurry3(f)
return func(r R, t1 T1, t2 T2, t3 T3) A {
return uc(t1, t2, t3)(r)
}
}
func Uncurry4[GA ~func(R) A, R, T1, T2, T3, T4, A any](f func(T1) func(T2) func(T3) func(T4) GA) func(R, T1, T2, T3, T4) A {
uc := F.Uncurry4(f)
return func(r R, t1 T1, t2 T2, t3 T3, t4 T4) A {
return uc(t1, t2, t3, t4)(r)
}
}

41
reader/generic/from.go Normal file
View File

@@ -0,0 +1,41 @@
package generic
// these functions convert a golang function with the context as the first parameter into a reader with the context as the last parameter, which
// is a equivalent to a function returning a reader of that context
// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention
func From0[GA ~func(R) A, R, A any](f func(R) A) GA {
return MakeReader[GA](f)
}
func From1[GA ~func(R) A, R, T1, A any](f func(R, T1) A) func(T1) GA {
return func(t1 T1) GA {
return MakeReader[GA](func(r R) A {
return f(r, t1)
})
}
}
func From2[GA ~func(R) A, R, T1, T2, A any](f func(R, T1, T2) A) func(T1, T2) GA {
return func(t1 T1, t2 T2) GA {
return MakeReader[GA](func(r R) A {
return f(r, t1, t2)
})
}
}
func From3[GA ~func(R) A, R, T1, T2, T3, A any](f func(R, T1, T2, T3) A) func(T1, T2, T3) GA {
return func(t1 T1, t2 T2, t3 T3) GA {
return MakeReader[GA](func(r R) A {
return f(r, t1, t2, t3)
})
}
}
func From4[GA ~func(R) A, R, T1, T2, T3, T4, A any](f func(R, T1, T2, T3, T4) A) func(T1, T2, T3, T4) GA {
return func(t1 T1, t2 T2, t3 T3, t4 T4) GA {
return MakeReader[GA](func(r R) A {
return f(r, t1, t2, t3, t4)
})
}
}

107
reader/generic/reader.go Normal file
View File

@@ -0,0 +1,107 @@
package generic
import (
F "github.com/ibm/fp-go/function"
I "github.com/ibm/fp-go/identity/generic"
T "github.com/ibm/fp-go/tuple"
)
// Reader[R, A] = func(R) A
// MakeReader creates a reader, i.e. a method that accepts a context and that returns a value
func MakeReader[GA ~func(R) A, R, A any](r GA) GA {
return r
}
// Ask reads the current context
func Ask[GR ~func(R) R, R any]() GR {
return MakeReader(F.Identity[R])
}
// Asks projects a value from the global context in a Reader
func Asks[GA ~func(R) A, R, A any](f GA) GA {
return MakeReader(f)
}
func AsksReader[GA ~func(R) A, R, A any](f func(R) GA) GA {
return MakeReader(func(r R) A {
return f(r)(r)
})
}
func MonadMap[GA ~func(E) A, GB ~func(E) B, E, A, B any](fa GA, f func(A) B) GB {
return MakeReader(F.Flow2(fa, f))
}
// Map can be used to turn functions `func(A)B` into functions `(fa F[A])F[B]` whose argument and return types
// use the type constructor `F` to represent some computational context.
func Map[GA ~func(E) A, GB ~func(E) B, E, A, B any](f func(A) B) func(GA) GB {
return F.Bind2nd(MonadMap[GA, GB, E, A, B], f)
}
func MonadAp[GA ~func(R) A, GB ~func(R) B, GAB ~func(R) func(A) B, R, A, B any](fab GAB, fa GA) GB {
return MakeReader(func(r R) B {
return fab(r)(fa(r))
})
}
// Ap applies a function to an argument under a type constructor.
func Ap[GA ~func(R) A, GB ~func(R) B, GAB ~func(R) func(A) B, R, A, B any](fa GA) func(GAB) GB {
return F.Bind2nd(MonadAp[GA, GB, GAB, R, A, B], fa)
}
func Of[GA ~func(R) A, R, A any](a A) GA {
return F.Constant1[R](a)
}
func MonadChain[GA ~func(R) A, GB ~func(R) B, R, A, B any](ma GA, f func(A) GB) GB {
return MakeReader(func(r R) B {
return f(ma(r))(r)
})
}
// Chain composes computations in sequence, using the return value of one computation to determine the next computation.
func Chain[GA ~func(R) A, GB ~func(R) B, R, A, B any](f func(A) GB) func(GA) GB {
return F.Bind2nd(MonadChain[GA, GB, R, A, B], f)
}
func Flatten[GA ~func(R) A, GGA ~func(R) GA, R, A any](mma GGA) GA {
return MonadChain(mma, F.Identity[GA])
}
func Compose[AB ~func(A) B, BC ~func(B) C, AC ~func(A) C, A, B, C any](ab AB) func(BC) AC {
return func(bc BC) AC {
return F.Flow2(ab, bc)
}
}
func First[GAB ~func(A) B, GABC ~func(T.Tuple2[A, C]) T.Tuple2[B, C], A, B, C any](pab GAB) GABC {
return MakeReader(func(tac T.Tuple2[A, C]) T.Tuple2[B, C] {
return T.MakeTuple2(pab(tac.F1), tac.F2)
})
}
func Second[GBC ~func(B) C, GABC ~func(T.Tuple2[A, B]) T.Tuple2[A, C], A, B, C any](pbc GBC) GABC {
return MakeReader(func(tab T.Tuple2[A, B]) T.Tuple2[A, C] {
return T.MakeTuple2(tab.F1, pbc(tab.F2))
})
}
func Promap[GA ~func(E) A, GB ~func(D) B, E, A, D, B any](f func(D) E, g func(A) B) func(GA) GB {
return func(fea GA) GB {
return MakeReader(F.Flow3(f, fea, g))
}
}
// Local changes the value of the local context during the execution of the action `ma` (similar to `Contravariant`'s
// `contramap`).
func Local[GA1 ~func(R1) A, GA2 ~func(R2) A, R2, R1, A any](f func(R2) R1) func(GA1) GA2 {
return func(r1 GA1) GA2 {
return F.Flow2(f, r1)
}
}
// Read applies a context to a reader to obtain its value
func Read[GA ~func(E) A, E, A any](e E) func(GA) A {
return I.Ap[GA](e)
}

View File

@@ -0,0 +1,46 @@
package generic
import (
"github.com/ibm/fp-go/internal/apply"
T "github.com/ibm/fp-go/tuple"
)
// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple
func SequenceT1[GA ~func(R) A, GTA ~func(R) T.Tuple1[A], R, A any](a GA) GTA {
return apply.SequenceT1(
Map[GA, GTA, R, A, T.Tuple1[A]],
a,
)
}
func SequenceT2[GA ~func(R) A, GB ~func(R) B, GTAB ~func(R) T.Tuple2[A, B], R, A, B any](a GA, b GB) GTAB {
return apply.SequenceT2(
Map[GA, func(R) func(B) T.Tuple2[A, B], R, A, func(B) T.Tuple2[A, B]],
Ap[GB, GTAB, func(R) func(B) T.Tuple2[A, B], R, B, T.Tuple2[A, B]],
a, b,
)
}
func SequenceT3[GA ~func(R) A, GB ~func(R) B, GC ~func(R) C, GTABC ~func(R) T.Tuple3[A, B, C], R, A, B, C any](a GA, b GB, c GC) GTABC {
return apply.SequenceT3(
Map[GA, func(R) func(B) func(C) T.Tuple3[A, B, C], R, A, func(B) func(C) T.Tuple3[A, B, C]],
Ap[GB, func(R) func(C) T.Tuple3[A, B, C], func(R) func(B) func(C) T.Tuple3[A, B, C], R, B, func(C) T.Tuple3[A, B, C]],
Ap[GC, GTABC, func(R) func(C) T.Tuple3[A, B, C], R, C, T.Tuple3[A, B, C]],
a, b, c,
)
}
func SequenceT4[GA ~func(R) A, GB ~func(R) B, GC ~func(R) C, GD ~func(R) D, GTABCD ~func(R) T.Tuple4[A, B, C, D], R, A, B, C, D any](a GA, b GB, c GC, d GD) GTABCD {
return apply.SequenceT4(
Map[GA, func(R) func(B) func(C) func(D) T.Tuple4[A, B, C, D], R, A, func(B) func(C) func(D) T.Tuple4[A, B, C, D]],
Ap[GB, func(R) func(C) func(D) T.Tuple4[A, B, C, D], func(R) func(B) func(C) func(D) T.Tuple4[A, B, C, D], R, B, func(C) func(D) T.Tuple4[A, B, C, D]],
Ap[GC, func(R) func(D) T.Tuple4[A, B, C, D], func(R) func(C) func(D) T.Tuple4[A, B, C, D], R, C, func(D) T.Tuple4[A, B, C, D]],
Ap[GD, GTABCD, func(R) func(D) T.Tuple4[A, B, C, D], R, D, T.Tuple4[A, B, C, D]],
a, b, c, d,
)
}

92
reader/reader.go Normal file
View File

@@ -0,0 +1,92 @@
package reader
import (
G "github.com/ibm/fp-go/reader/generic"
T "github.com/ibm/fp-go/tuple"
)
// The purpose of the `Reader` monad is to avoid threading arguments through multiple functions in order to only get them where they are needed.
// The first template argument `R` is the the context to read from, the second argument `A` is the return value of the monad
type Reader[R, A any] func(R) A
// MakeReader creates a reader, i.e. a method that accepts a context and that returns a value
func MakeReader[R, A any](r Reader[R, A]) Reader[R, A] {
return G.MakeReader(r)
}
// Ask reads the current context
func Ask[R any]() Reader[R, R] {
return G.Ask[Reader[R, R]]()
}
// Asks projects a value from the global context in a Reader
func Asks[R, A any](f Reader[R, A]) Reader[R, A] {
return G.Asks(f)
}
func AsksReader[R, A any](f func(R) Reader[R, A]) Reader[R, A] {
return G.AsksReader(f)
}
func MonadMap[E, A, B any](fa Reader[E, A], f func(A) B) Reader[E, B] {
return G.MonadMap[Reader[E, A], Reader[E, B]](fa, f)
}
// Map can be used to turn functions `func(A)B` into functions `(fa F[A])F[B]` whose argument and return types
// use the type constructor `F` to represent some computational context.
func Map[E, A, B any](f func(A) B) func(Reader[E, A]) Reader[E, B] {
return G.Map[Reader[E, A], Reader[E, B]](f)
}
func MonadAp[B, R, A any](fab Reader[R, func(A) B], fa Reader[R, A]) Reader[R, B] {
return G.MonadAp[Reader[R, A], Reader[R, B]](fab, fa)
}
// Ap applies a function to an argument under a type constructor.
func Ap[B, R, A any](fa Reader[R, A]) func(Reader[R, func(A) B]) Reader[R, B] {
return G.Ap[Reader[R, A], Reader[R, B], Reader[R, func(A) B]](fa)
}
func Of[R, A any](a A) Reader[R, A] {
return G.Of[Reader[R, A]](a)
}
func MonadChain[R, A, B any](ma Reader[R, A], f func(A) Reader[R, B]) Reader[R, B] {
return G.MonadChain(ma, f)
}
// Chain composes computations in sequence, using the return value of one computation to determine the next computation.
func Chain[R, A, B any](f func(A) Reader[R, B]) func(Reader[R, A]) Reader[R, B] {
return G.Chain[Reader[R, A]](f)
}
func Flatten[R, A any](mma func(R) Reader[R, A]) Reader[R, A] {
return G.Flatten(mma)
}
func Compose[R, B, C any](ab Reader[R, B]) func(Reader[B, C]) Reader[R, C] {
return G.Compose[Reader[R, B], Reader[B, C], Reader[R, C]](ab)
}
func First[A, B, C any](pab Reader[A, B]) Reader[T.Tuple2[A, C], T.Tuple2[B, C]] {
return G.First[Reader[A, B], Reader[T.Tuple2[A, C], T.Tuple2[B, C]]](pab)
}
func Second[A, B, C any](pbc Reader[B, C]) Reader[T.Tuple2[A, B], T.Tuple2[A, C]] {
return G.Second[Reader[B, C], Reader[T.Tuple2[A, B], T.Tuple2[A, C]]](pbc)
}
func Promap[E, A, D, B any](f func(D) E, g func(A) B) func(Reader[E, A]) Reader[D, B] {
return G.Promap[Reader[E, A], Reader[D, B]](f, g)
}
// Local changes the value of the local context during the execution of the action `ma` (similar to `Contravariant`'s
// `contramap`).
func Local[R2, R1, A any](f func(R2) R1) func(Reader[R1, A]) Reader[R2, A] {
return G.Local[Reader[R1, A], Reader[R2, A]](f)
}
// Read applies a context to a reader to obtain its value
func Read[E, A any](e E) func(Reader[E, A]) A {
return G.Read[Reader[E, A]](e)
}

19
reader/reader_test.go Normal file
View File

@@ -0,0 +1,19 @@
package reader
import (
"testing"
F "github.com/ibm/fp-go/function"
"github.com/stretchr/testify/assert"
"github.com/ibm/fp-go/internal/utils"
)
func TestMap(t *testing.T) {
assert.Equal(t, 2, F.Pipe1(Of[string](1), Map[string](utils.Double))(""))
}
func TestAp(t *testing.T) {
assert.Equal(t, 2, F.Pipe1(Of[int](utils.Double), Ap[int, int, int](Of[int](1)))(0))
}

25
reader/semigroup.go Normal file
View File

@@ -0,0 +1,25 @@
package reader
import (
M "github.com/ibm/fp-go/monoid"
S "github.com/ibm/fp-go/semigroup"
)
func ApplySemigroup[R, A any](
_map func(func(R) A, func(A) func(A) A) func(R, func(A) A),
_ap func(func(R, func(A) A), func(R) A) func(R) A,
s S.Semigroup[A],
) S.Semigroup[func(R) A] {
return S.ApplySemigroup(_map, _ap, s)
}
func ApplicativeMonoid[R, A any](
_of func(A) func(R) A,
_map func(func(R) A, func(A) func(A) A) func(R, func(A) A),
_ap func(func(R, func(A) A), func(R) A) func(R) A,
m M.Monoid[A],
) M.Monoid[func(R) A] {
return M.ApplicativeMonoid(_of, _map, _ap, m)
}

24
reader/sequence.go Normal file
View File

@@ -0,0 +1,24 @@
package reader
import (
G "github.com/ibm/fp-go/reader/generic"
T "github.com/ibm/fp-go/tuple"
)
// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple
func SequenceT1[R, A any](a Reader[R, A]) Reader[R, T.Tuple1[A]] {
return G.SequenceT1[Reader[R, A], Reader[R, T.Tuple1[A]]](a)
}
func SequenceT2[R, A, B any](a Reader[R, A], b Reader[R, B]) Reader[R, T.Tuple2[A, B]] {
return G.SequenceT2[Reader[R, A], Reader[R, B], Reader[R, T.Tuple2[A, B]]](a, b)
}
func SequenceT3[R, A, B, C any](a Reader[R, A], b Reader[R, B], c Reader[R, C]) Reader[R, T.Tuple3[A, B, C]] {
return G.SequenceT3[Reader[R, A], Reader[R, B], Reader[R, C], Reader[R, T.Tuple3[A, B, C]]](a, b, c)
}
func SequenceT4[R, A, B, C, D any](a Reader[R, A], b Reader[R, B], c Reader[R, C], d Reader[R, D]) Reader[R, T.Tuple4[A, B, C, D]] {
return G.SequenceT4[Reader[R, A], Reader[R, B], Reader[R, C], Reader[R, D], Reader[R, T.Tuple4[A, B, C, D]]](a, b, c, d)
}