diff --git a/context/readerioeither/file/write.go b/context/readerioeither/file/write.go index 7675d54..66c87ef 100644 --- a/context/readerioeither/file/write.go +++ b/context/readerioeither/file/write.go @@ -26,7 +26,7 @@ import ( func onWriteAll[W io.Writer](data []byte) func(w W) RIOE.ReaderIOEither[[]byte] { return func(w W) RIOE.ReaderIOEither[[]byte] { return F.Pipe1( - RIOE.TryCatch(func(ctx context.Context) func() ([]byte, error) { + RIOE.TryCatch(func(_ context.Context) func() ([]byte, error) { return func() ([]byte, error) { _, err := w.Write(data) return data, err diff --git a/context/readerioeither/http/request.go b/context/readerioeither/http/request.go index 9e461d9..5699ba5 100644 --- a/context/readerioeither/http/request.go +++ b/context/readerioeither/http/request.go @@ -109,17 +109,21 @@ func ReadJson[A any](client Client) func(Requester) RIOE.ReaderIOEither[A] { return ReadJSON[A](client) } -// ReadJSON sends a request, reads the response and parses the response as JSON -func ReadJSON[A any](client Client) func(Requester) RIOE.ReaderIOEither[A] { +func readJSON(client Client) func(Requester) RIOE.ReaderIOEither[[]byte] { return F.Flow3( ReadFullResponse(client), RIOE.ChainFirstEitherK(F.Flow2( H.Response, H.ValidateJSONResponse, )), - RIOE.ChainEitherK(F.Flow2( - H.Body, - J.Unmarshal[A], - )), + RIOE.Map(H.Body), + ) +} + +// ReadJSON sends a request, reads the response and parses the response as JSON +func ReadJSON[A any](client Client) func(Requester) RIOE.ReaderIOEither[A] { + return F.Flow2( + readJSON(client), + RIOE.ChainEitherK(J.Unmarshal[A]), ) } diff --git a/di/provider.go b/di/provider.go index e547f1f..3b04fe2 100644 --- a/di/provider.go +++ b/di/provider.go @@ -41,7 +41,7 @@ func eraseTuple[A, R any](f func(A) IOE.IOEither[error, R]) func(E.Either[error, } func eraseProviderFactory0[R any](f IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { - return func(params ...any) IOE.IOEither[error, any] { + return func(_ ...any) IOE.IOEither[error, any] { return F.Pipe1( f, IOE.Map[error](F.ToAny[R]), diff --git a/identity/identity.go b/identity/identity.go index 33979c5..e6d64aa 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -36,7 +36,7 @@ func Map[A, B any](f func(A) B) func(A) B { return G.Map(f) } -func MonadMapTo[A, B any](fa A, b B) B { +func MonadMapTo[A, B any](_ A, b B) B { return b } diff --git a/internal/applicative/testing/law.go b/internal/applicative/testing/law.go index 73416a4..b47764c 100644 --- a/internal/applicative/testing/law.go +++ b/internal/applicative/testing/law.go @@ -20,13 +20,19 @@ import ( E "github.com/IBM/fp-go/eq" F "github.com/IBM/fp-go/function" + "github.com/IBM/fp-go/internal/applicative" + "github.com/IBM/fp-go/internal/apply" L "github.com/IBM/fp-go/internal/apply/testing" + "github.com/IBM/fp-go/internal/functor" + "github.com/IBM/fp-go/internal/pointed" "github.com/stretchr/testify/assert" ) // Applicative identity law // // A.ap(A.of(a => a), fa) <-> fa +// +// Deprecated: use [ApplicativeAssertIdentity] func AssertIdentity[HKTA, HKTAA, A any](t *testing.T, eq E.Eq[HKTA], @@ -46,9 +52,33 @@ func AssertIdentity[HKTA, HKTAA, A any](t *testing.T, } } +// Applicative identity law +// +// A.ap(A.of(a => a), fa) <-> fa +func ApplicativeAssertIdentity[HKTA, HKTFAA, A any](t *testing.T, + eq E.Eq[HKTA], + + ap applicative.Applicative[A, A, HKTA, HKTA, HKTFAA], + paa pointed.Pointed[func(A) A, HKTFAA], + +) func(fa HKTA) bool { + // mark as test helper + t.Helper() + + return func(fa HKTA) bool { + + left := ap.Ap(fa)(paa.Of(F.Identity[A])) + right := fa + + return assert.True(t, eq.Equals(left, right), "Applicative identity") + } +} + // Applicative homomorphism law // // A.ap(A.of(ab), A.of(a)) <-> A.of(ab(a)) +// +// Deprecated: use [ApplicativeAssertHomomorphism] func AssertHomomorphism[HKTA, HKTB, HKTAB, A, B any](t *testing.T, eq E.Eq[HKTB], @@ -72,9 +102,35 @@ func AssertHomomorphism[HKTA, HKTB, HKTAB, A, B any](t *testing.T, } } +// Applicative homomorphism law +// +// A.ap(A.of(ab), A.of(a)) <-> A.of(ab(a)) +func ApplicativeAssertHomomorphism[HKTA, HKTB, HKTFAB, A, B any](t *testing.T, + eq E.Eq[HKTB], + + apab applicative.Applicative[A, B, HKTA, HKTB, HKTFAB], + pb pointed.Pointed[B, HKTB], + pfab pointed.Pointed[func(A) B, HKTFAB], + + ab func(A) B, +) func(a A) bool { + // mark as test helper + t.Helper() + + return func(a A) bool { + + left := apab.Ap(apab.Of(a))(pfab.Of(ab)) + right := pb.Of(ab(a)) + + return assert.True(t, eq.Equals(left, right), "Applicative homomorphism") + } +} + // Applicative interchange law // // A.ap(fab, A.of(a)) <-> A.ap(A.of(ab => ab(a)), fab) +// +// Deprecated: use [ApplicativeAssertInterchange] func AssertInterchange[HKTA, HKTB, HKTAB, HKTABB, A, B any](t *testing.T, eq E.Eq[HKTB], @@ -103,7 +159,38 @@ func AssertInterchange[HKTA, HKTB, HKTAB, HKTABB, A, B any](t *testing.T, } } +// Applicative interchange law +// +// A.ap(fab, A.of(a)) <-> A.ap(A.of(ab => ab(a)), fab) +func ApplicativeAssertInterchange[HKTA, HKTB, HKTFAB, HKTABB, A, B any](t *testing.T, + eq E.Eq[HKTB], + + apab applicative.Applicative[A, B, HKTA, HKTB, HKTFAB], + apabb applicative.Applicative[func(A) B, B, HKTFAB, HKTB, HKTABB], + pabb pointed.Pointed[func(func(A) B) B, HKTABB], + + ab func(A) B, +) func(a A) bool { + // mark as test helper + t.Helper() + + return func(a A) bool { + + fab := apabb.Of(ab) + + left := apab.Ap(apab.Of(a))(fab) + + right := apabb.Ap(fab)(pabb.Of(func(ab func(A) B) B { + return ab(a) + })) + + return assert.True(t, eq.Equals(left, right), "Applicative homomorphism") + } +} + // AssertLaws asserts the apply laws `identity`, `composition`, `associative composition`, 'applicative identity', 'homomorphism', 'interchange' +// +// Deprecated: use [ApplicativeAssertLaws] instead func AssertLaws[HKTA, HKTB, HKTC, HKTAA, HKTAB, HKTBC, HKTAC, HKTABB, HKTABAC, A, B, C any](t *testing.T, eqa E.Eq[HKTA], eqb E.Eq[HKTB], @@ -150,3 +237,47 @@ func AssertLaws[HKTA, HKTB, HKTC, HKTAA, HKTAB, HKTBC, HKTAC, HKTABB, HKTABAC, A return apply(fa) && identity(fa) && homomorphism(a) && interchange(a) } } + +// ApplicativeAssertLaws asserts the apply laws `identity`, `composition`, `associative composition`, 'applicative identity', 'homomorphism', 'interchange' +func ApplicativeAssertLaws[HKTA, HKTB, HKTC, HKTAA, HKTAB, HKTBC, HKTAC, HKTABB, HKTABAC, A, B, C any](t *testing.T, + eqa E.Eq[HKTA], + eqb E.Eq[HKTB], + eqc E.Eq[HKTC], + + fofb pointed.Pointed[B, HKTB], + + fofaa pointed.Pointed[func(A) A, HKTAA], + fofbc pointed.Pointed[func(B) C, HKTBC], + + fofabb pointed.Pointed[func(func(A) B) B, HKTABB], + + faa functor.Functor[A, A, HKTA, HKTA], + + fmap functor.Functor[func(B) C, func(func(A) B) func(A) C, HKTBC, HKTABAC], + + fapaa applicative.Applicative[A, A, HKTA, HKTA, HKTAA], + fapab applicative.Applicative[A, B, HKTA, HKTB, HKTAB], + fapbc apply.Apply[B, C, HKTB, HKTC, HKTBC], + fapac apply.Apply[A, C, HKTA, HKTC, HKTAC], + + fapabb applicative.Applicative[func(A) B, B, HKTAB, HKTB, HKTABB], + fapabac applicative.Applicative[func(A) B, func(A) C, HKTAB, HKTAC, HKTABAC], + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + // mark as test helper + t.Helper() + + // apply laws + apply := L.ApplyAssertLaws(t, eqa, eqc, applicative.ToPointed(fapabac), fofbc, faa, fmap, applicative.ToApply(fapab), fapbc, fapac, applicative.ToApply(fapabac), ab, bc) + // applicative laws + identity := ApplicativeAssertIdentity(t, eqa, fapaa, fofaa) + homomorphism := ApplicativeAssertHomomorphism(t, eqb, fapab, fofb, applicative.ToPointed(fapabb), ab) + interchange := ApplicativeAssertInterchange(t, eqb, fapab, fapabb, fofabb, ab) + + return func(a A) bool { + fa := fapaa.Of(a) + return apply(fa) && identity(fa) && homomorphism(a) && interchange(a) + } +} diff --git a/internal/applicative/types.go b/internal/applicative/types.go index 589fc6e..4dbc991 100644 --- a/internal/applicative/types.go +++ b/internal/applicative/types.go @@ -17,6 +17,7 @@ package applicative import ( "github.com/IBM/fp-go/internal/apply" + "github.com/IBM/fp-go/internal/functor" "github.com/IBM/fp-go/internal/pointed" ) @@ -24,3 +25,18 @@ type Applicative[A, B, HKTA, HKTB, HKTFAB any] interface { apply.Apply[A, B, HKTA, HKTB, HKTFAB] pointed.Pointed[A, HKTA] } + +// ToFunctor converts from [Applicative] to [functor.Functor] +func ToFunctor[A, B, HKTA, HKTB, HKTFAB any](ap Applicative[A, B, HKTA, HKTB, HKTFAB]) functor.Functor[A, B, HKTA, HKTB] { + return ap +} + +// ToApply converts from [Applicative] to [apply.Apply] +func ToApply[A, B, HKTA, HKTB, HKTFAB any](ap Applicative[A, B, HKTA, HKTB, HKTFAB]) apply.Apply[A, B, HKTA, HKTB, HKTFAB] { + return ap +} + +// ToPointed converts from [Applicative] to [pointed.Pointed] +func ToPointed[A, B, HKTA, HKTB, HKTFAB any](ap Applicative[A, B, HKTA, HKTB, HKTFAB]) pointed.Pointed[A, HKTA] { + return ap +} diff --git a/internal/apply/testing/laws.go b/internal/apply/testing/laws.go index 4b405c9..90f455a 100644 --- a/internal/apply/testing/laws.go +++ b/internal/apply/testing/laws.go @@ -19,13 +19,18 @@ import ( "testing" E "github.com/IBM/fp-go/eq" + "github.com/IBM/fp-go/internal/apply" + "github.com/IBM/fp-go/internal/functor" FCT "github.com/IBM/fp-go/internal/functor/testing" + "github.com/IBM/fp-go/internal/pointed" "github.com/stretchr/testify/assert" ) // Apply associative composition law // // F.ap(F.ap(F.map(fbc, bc => ab => a => bc(ab(a))), fab), fa) <-> F.ap(fbc, F.ap(fab, fa)) +// +// Deprecated: use [ApplyAssertAssociativeComposition] instead func AssertAssociativeComposition[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *testing.T, eq E.Eq[HKTC], @@ -63,7 +68,49 @@ func AssertAssociativeComposition[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC } } +// Apply associative composition law +// +// F.ap(F.ap(F.map(fbc, bc => ab => a => bc(ab(a))), fab), fa) <-> F.ap(fbc, F.ap(fab, fa)) +func ApplyAssertAssociativeComposition[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *testing.T, + eq E.Eq[HKTC], + + fofab pointed.Pointed[func(A) B, HKTAB], + fofbc pointed.Pointed[func(B) C, HKTBC], + + fmap functor.Functor[func(B) C, func(func(A) B) func(A) C, HKTBC, HKTABAC], + + fapab apply.Apply[A, B, HKTA, HKTB, HKTAB], + fapbc apply.Apply[B, C, HKTB, HKTC, HKTBC], + fapac apply.Apply[A, C, HKTA, HKTC, HKTAC], + + fapabac apply.Apply[func(A) B, func(A) C, HKTAB, HKTAC, HKTABAC], + + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + t.Helper() + return func(fa HKTA) bool { + + fab := fofab.Of(ab) + fbc := fofbc.Of(bc) + + left := fapac.Ap(fa)(fapabac.Ap(fab)(fmap.Map(func(bc func(B) C) func(func(A) B) func(A) C { + return func(ab func(A) B) func(A) C { + return func(a A) C { + return bc(ab(a)) + } + } + })(fbc))) + + right := fapbc.Ap(fapab.Ap(fa)(fab))(fbc) + + return assert.True(t, eq.Equals(left, right), "Apply associative composition") + } +} + // AssertLaws asserts the apply laws `identity`, `composition` and `associative composition` +// +// Deprecated: use [ApplyAssertLaws] instead func AssertLaws[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *testing.T, eqa E.Eq[HKTA], eqc E.Eq[HKTC], @@ -98,3 +145,36 @@ func AssertLaws[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t * return functor(fa) && composition(fa) } } + +// ApplyAssertLaws asserts the apply laws `identity`, `composition` and `associative composition` +func ApplyAssertLaws[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *testing.T, + eqa E.Eq[HKTA], + eqc E.Eq[HKTC], + + fofab pointed.Pointed[func(A) B, HKTAB], + fofbc pointed.Pointed[func(B) C, HKTBC], + + faa functor.Functor[A, A, HKTA, HKTA], + + fmap functor.Functor[func(B) C, func(func(A) B) func(A) C, HKTBC, HKTABAC], + + fapab apply.Apply[A, B, HKTA, HKTB, HKTAB], + fapbc apply.Apply[B, C, HKTB, HKTC, HKTBC], + fapac apply.Apply[A, C, HKTA, HKTC, HKTAC], + + fapabac apply.Apply[func(A) B, func(A) C, HKTAB, HKTAC, HKTABAC], + + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + // mark as test helper + t.Helper() + // functor laws + functor := FCT.FunctorAssertLaws(t, eqa, eqc, faa, apply.ToFunctor(fapab), apply.ToFunctor(fapac), apply.ToFunctor(fapbc), ab, bc) + // associative composition laws + composition := ApplyAssertAssociativeComposition(t, eqc, fofab, fofbc, fmap, fapab, fapbc, fapac, fapabac, ab, bc) + + return func(fa HKTA) bool { + return functor(fa) && composition(fa) + } +} diff --git a/internal/apply/types.go b/internal/apply/types.go index d42417e..24434b5 100644 --- a/internal/apply/types.go +++ b/internal/apply/types.go @@ -23,3 +23,8 @@ type Apply[A, B, HKTA, HKTB, HKTFAB any] interface { functor.Functor[A, B, HKTA, HKTB] Ap(HKTA) func(HKTFAB) HKTB } + +// ToFunctor converts from [Apply] to [functor.Functor] +func ToFunctor[A, B, HKTA, HKTB, HKTFAB any](ap Apply[A, B, HKTA, HKTB, HKTFAB]) functor.Functor[A, B, HKTA, HKTB] { + return ap +} diff --git a/internal/chain/testing/laws.go b/internal/chain/testing/laws.go index a5c0ea0..0290b70 100644 --- a/internal/chain/testing/laws.go +++ b/internal/chain/testing/laws.go @@ -20,13 +20,19 @@ import ( E "github.com/IBM/fp-go/eq" F "github.com/IBM/fp-go/function" + "github.com/IBM/fp-go/internal/apply" L "github.com/IBM/fp-go/internal/apply/testing" + "github.com/IBM/fp-go/internal/chain" + "github.com/IBM/fp-go/internal/functor" + "github.com/IBM/fp-go/internal/pointed" "github.com/stretchr/testify/assert" ) // Chain associativity law // // F.chain(F.chain(fa, afb), bfc) <-> F.chain(fa, a => F.chain(afb(a), bfc)) +// +// Deprecated: use [ChainAssertAssociativity] instead func AssertAssociativity[HKTA, HKTB, HKTC, A, B, C any](t *testing.T, eq E.Eq[HKTC], @@ -55,7 +61,40 @@ func AssertAssociativity[HKTA, HKTB, HKTC, A, B, C any](t *testing.T, } } +// Chain associativity law +// +// F.chain(F.chain(fa, afb), bfc) <-> F.chain(fa, a => F.chain(afb(a), bfc)) +func ChainAssertAssociativity[HKTA, HKTB, HKTC, HKTAB, HKTAC, HKTBC, A, B, C any](t *testing.T, + eq E.Eq[HKTC], + + fofb pointed.Pointed[B, HKTB], + fofc pointed.Pointed[C, HKTC], + + chainab chain.Chainable[A, B, HKTA, HKTB, HKTAB], + chainac chain.Chainable[A, C, HKTA, HKTC, HKTAC], + chainbc chain.Chainable[B, C, HKTB, HKTC, HKTBC], + + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + return func(fa HKTA) bool { + + afb := F.Flow2(ab, fofb.Of) + bfc := F.Flow2(bc, fofc.Of) + + left := chainbc.Chain(bfc)(chainab.Chain(afb)(fa)) + + right := chainac.Chain(func(a A) HKTC { + return chainbc.Chain(bfc)(afb(a)) + })(fa) + + return assert.True(t, eq.Equals(left, right), "Chain associativity") + } +} + // AssertLaws asserts the apply laws `identity`, `composition`, `associative composition` and `associativity` +// +// Deprecated: use [ChainAssertLaws] instead func AssertLaws[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *testing.T, eqa E.Eq[HKTA], eqc E.Eq[HKTC], @@ -95,3 +134,37 @@ func AssertLaws[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t * return apply(fa) && associativity(fa) } } + +// ChainAssertLaws asserts the apply laws `identity`, `composition`, `associative composition` and `associativity` +func ChainAssertLaws[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *testing.T, + eqa E.Eq[HKTA], + eqc E.Eq[HKTC], + + fofb pointed.Pointed[B, HKTB], + fofc pointed.Pointed[C, HKTC], + + fofab pointed.Pointed[func(A) B, HKTAB], + fofbc pointed.Pointed[func(B) C, HKTBC], + + faa functor.Functor[A, A, HKTA, HKTA], + + fmap functor.Functor[func(B) C, func(func(A) B) func(A) C, HKTBC, HKTABAC], + + chainab chain.Chainable[A, B, HKTA, HKTB, HKTAB], + chainac chain.Chainable[A, C, HKTA, HKTC, HKTAC], + chainbc chain.Chainable[B, C, HKTB, HKTC, HKTBC], + + fapabac apply.Apply[func(A) B, func(A) C, HKTAB, HKTAC, HKTABAC], + + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + // apply laws + apply := L.ApplyAssertLaws(t, eqa, eqc, fofab, fofbc, faa, fmap, chain.ToApply(chainab), chain.ToApply(chainbc), chain.ToApply(chainac), fapabac, ab, bc) + // chain laws + associativity := ChainAssertAssociativity(t, eqc, fofb, fofc, chainab, chainac, chainbc, ab, bc) + + return func(fa HKTA) bool { + return apply(fa) && associativity(fa) + } +} diff --git a/internal/chain/types.go b/internal/chain/types.go index 0d643f2..380ccc3 100644 --- a/internal/chain/types.go +++ b/internal/chain/types.go @@ -17,9 +17,20 @@ package chain import ( "github.com/IBM/fp-go/internal/apply" + "github.com/IBM/fp-go/internal/functor" ) type Chainable[A, B, HKTA, HKTB, HKTFAB any] interface { apply.Apply[A, B, HKTA, HKTB, HKTFAB] Chain(func(A) HKTB) func(HKTA) HKTB } + +// ToFunctor converts from [Chainable] to [functor.Functor] +func ToFunctor[A, B, HKTA, HKTB, HKTFAB any](ap Chainable[A, B, HKTA, HKTB, HKTFAB]) functor.Functor[A, B, HKTA, HKTB] { + return ap +} + +// ToApply converts from [Chainable] to [functor.Functor] +func ToApply[A, B, HKTA, HKTB, HKTFAB any](ap Chainable[A, B, HKTA, HKTB, HKTFAB]) apply.Apply[A, B, HKTA, HKTB, HKTFAB] { + return ap +} diff --git a/internal/functor/testing/laws.go b/internal/functor/testing/laws.go index b0bb597..2521848 100644 --- a/internal/functor/testing/laws.go +++ b/internal/functor/testing/laws.go @@ -20,12 +20,15 @@ import ( E "github.com/IBM/fp-go/eq" F "github.com/IBM/fp-go/function" + "github.com/IBM/fp-go/internal/functor" "github.com/stretchr/testify/assert" ) // Functor identity law // // F.map(fa, a => a) <-> fa +// +// Deprecated: use [FunctorAssertIdentity] func AssertIdentity[HKTA, A any](t *testing.T, eq E.Eq[HKTA], fmap func(HKTA, func(A) A) HKTA) func(fa HKTA) bool { t.Helper() return func(fa HKTA) bool { @@ -33,9 +36,28 @@ func AssertIdentity[HKTA, A any](t *testing.T, eq E.Eq[HKTA], fmap func(HKTA, fu } } +// Functor identity law +// +// F.map(fa, a => a) <-> fa +func FunctorAssertIdentity[HKTA, A any]( + t *testing.T, + eq E.Eq[HKTA], + + fca functor.Functor[A, A, HKTA, HKTA], +) func(fa HKTA) bool { + + t.Helper() + return func(fa HKTA) bool { + + return assert.True(t, eq.Equals(fa, fca.Map(F.Identity[A])(fa)), "Functor identity law") + } +} + // Functor composition law // // F.map(fa, a => bc(ab(a))) <-> F.map(F.map(fa, ab), bc) +// +// Deprecated: use [FunctorAssertComposition] instead func AssertComposition[HKTA, HKTB, HKTC, A, B, C any]( t *testing.T, @@ -53,7 +75,30 @@ func AssertComposition[HKTA, HKTB, HKTC, A, B, C any]( } } +// Functor composition law +// +// F.map(fa, a => bc(ab(a))) <-> F.map(F.map(fa, ab), bc) +func FunctorAssertComposition[HKTA, HKTB, HKTC, A, B, C any]( + t *testing.T, + + eq E.Eq[HKTC], + + fab functor.Functor[A, B, HKTA, HKTB], + fac functor.Functor[A, C, HKTA, HKTC], + fbc functor.Functor[B, C, HKTB, HKTC], + + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + t.Helper() + return func(fa HKTA) bool { + return assert.True(t, eq.Equals(fac.Map(F.Flow2(ab, bc))(fa), fbc.Map(bc)(fab.Map(ab)(fa))), "Functor composition law") + } +} + // AssertLaws asserts the functor laws `identity` and `composition` +// +// Deprecated: use [FunctorAssertLaws] instead func AssertLaws[HKTA, HKTB, HKTC, A, B, C any](t *testing.T, eqa E.Eq[HKTA], eqc E.Eq[HKTC], @@ -62,6 +107,7 @@ func AssertLaws[HKTA, HKTB, HKTC, A, B, C any](t *testing.T, fab func(HKTA, func(A) B) HKTB, fac func(HKTA, func(A) C) HKTC, fbc func(HKTB, func(B) C) HKTC, + ab func(A) B, bc func(B) C, ) func(fa HKTA) bool { @@ -73,3 +119,25 @@ func AssertLaws[HKTA, HKTB, HKTC, A, B, C any](t *testing.T, return identity(fa) && composition(fa) } } + +// FunctorAssertLaws asserts the functor laws `identity` and `composition` +func FunctorAssertLaws[HKTA, HKTB, HKTC, A, B, C any](t *testing.T, + eqa E.Eq[HKTA], + eqc E.Eq[HKTC], + + faa functor.Functor[A, A, HKTA, HKTA], + fab functor.Functor[A, B, HKTA, HKTB], + fac functor.Functor[A, C, HKTA, HKTC], + fbc functor.Functor[B, C, HKTB, HKTC], + + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + t.Helper() + identity := FunctorAssertIdentity(t, eqa, faa) + composition := FunctorAssertComposition(t, eqc, fab, fac, fbc, ab, bc) + + return func(fa HKTA) bool { + return identity(fa) && composition(fa) + } +} diff --git a/internal/monad/monad.go b/internal/monad/monad.go index cbb5938..b7a805d 100644 --- a/internal/monad/monad.go +++ b/internal/monad/monad.go @@ -17,10 +17,38 @@ package monad import ( "github.com/IBM/fp-go/internal/applicative" + "github.com/IBM/fp-go/internal/apply" "github.com/IBM/fp-go/internal/chain" + "github.com/IBM/fp-go/internal/functor" + "github.com/IBM/fp-go/internal/pointed" ) type Monad[A, B, HKTA, HKTB, HKTFAB any] interface { applicative.Applicative[A, B, HKTA, HKTB, HKTFAB] chain.Chainable[A, B, HKTA, HKTB, HKTFAB] } + +// ToFunctor converts from [Monad] to [functor.Functor] +func ToFunctor[A, B, HKTA, HKTB, HKTFAB any](ap Monad[A, B, HKTA, HKTB, HKTFAB]) functor.Functor[A, B, HKTA, HKTB] { + return ap +} + +// ToApply converts from [Monad] to [apply.Apply] +func ToApply[A, B, HKTA, HKTB, HKTFAB any](ap Monad[A, B, HKTA, HKTB, HKTFAB]) apply.Apply[A, B, HKTA, HKTB, HKTFAB] { + return ap +} + +// ToPointed converts from [Monad] to [pointed.Pointed] +func ToPointed[A, B, HKTA, HKTB, HKTFAB any](ap Monad[A, B, HKTA, HKTB, HKTFAB]) pointed.Pointed[A, HKTA] { + return ap +} + +// ToApplicative converts from [Monad] to [applicative.Applicative] +func ToApplicative[A, B, HKTA, HKTB, HKTFAB any](ap Monad[A, B, HKTA, HKTB, HKTFAB]) applicative.Applicative[A, B, HKTA, HKTB, HKTFAB] { + return ap +} + +// ToChainable converts from [Monad] to [chain.Chainable] +func ToChainable[A, B, HKTA, HKTB, HKTFAB any](ap Monad[A, B, HKTA, HKTB, HKTFAB]) chain.Chainable[A, B, HKTA, HKTB, HKTFAB] { + return ap +} diff --git a/internal/monad/testing/laws.go b/internal/monad/testing/laws.go index be549eb..58b4c6c 100644 --- a/internal/monad/testing/laws.go +++ b/internal/monad/testing/laws.go @@ -19,14 +19,21 @@ import ( "testing" E "github.com/IBM/fp-go/eq" + "github.com/IBM/fp-go/internal/applicative" LA "github.com/IBM/fp-go/internal/applicative/testing" + "github.com/IBM/fp-go/internal/chain" LC "github.com/IBM/fp-go/internal/chain/testing" + "github.com/IBM/fp-go/internal/functor" + "github.com/IBM/fp-go/internal/monad" + "github.com/IBM/fp-go/internal/pointed" "github.com/stretchr/testify/assert" ) // Apply monad left identity law // // M.chain(M.of(a), f) <-> f(a) +// +// Deprecated: use [MonadAssertLeftIdentity] instead func AssertLeftIdentity[HKTA, HKTB, A, B any](t *testing.T, eq E.Eq[HKTB], @@ -50,9 +57,36 @@ func AssertLeftIdentity[HKTA, HKTB, A, B any](t *testing.T, } } +// Apply monad left identity law +// +// M.chain(M.of(a), f) <-> f(a) +func MonadAssertLeftIdentity[HKTA, HKTB, HKTFAB, A, B any](t *testing.T, + eq E.Eq[HKTB], + + fofb pointed.Pointed[B, HKTB], + + ma monad.Monad[A, B, HKTA, HKTB, HKTFAB], + + ab func(A) B, +) func(a A) bool { + return func(a A) bool { + + f := func(a A) HKTB { + return fofb.Of(ab(a)) + } + + left := ma.Chain(f)(ma.Of(a)) + right := f(a) + + return assert.True(t, eq.Equals(left, right), "Monad left identity") + } +} + // Apply monad right identity law // // M.chain(fa, M.of) <-> fa +// +// Deprecated: use [MonadAssertRightIdentity] instead func AssertRightIdentity[HKTA, A any](t *testing.T, eq E.Eq[HKTA], @@ -69,7 +103,27 @@ func AssertRightIdentity[HKTA, A any](t *testing.T, } } +// Apply monad right identity law +// +// M.chain(fa, M.of) <-> fa +func MonadAssertRightIdentity[HKTA, HKTAA, A any](t *testing.T, + eq E.Eq[HKTA], + + ma monad.Monad[A, A, HKTA, HKTA, HKTAA], + +) func(fa HKTA) bool { + return func(fa HKTA) bool { + + left := ma.Chain(ma.Of)(fa) + right := fa + + return assert.True(t, eq.Equals(left, right), "Monad right identity") + } +} + // AssertLaws asserts the apply laws `identity`, `composition`, `associative composition`, 'applicative identity', 'homomorphism', 'interchange', `associativity`, `left identity`, `right identity` +// +// Deprecated: use [MonadAssertLaws] instead func AssertLaws[HKTA, HKTB, HKTC, HKTAA, HKTAB, HKTBC, HKTAC, HKTABB, HKTABAC, A, B, C any](t *testing.T, eqa E.Eq[HKTA], eqb E.Eq[HKTB], @@ -120,3 +174,55 @@ func AssertLaws[HKTA, HKTB, HKTC, HKTAA, HKTAB, HKTBC, HKTAC, HKTABB, HKTABAC, A return applicative(a) && chain(fa) && leftIdentity(a) && rightIdentity(fa) } } + +// MonadAssertLaws asserts the apply laws `identity`, `composition`, `associative composition`, 'applicative identity', 'homomorphism', 'interchange', `associativity`, `left identity`, `right identity` +func MonadAssertLaws[HKTA, HKTB, HKTC, HKTAA, HKTAB, HKTBC, HKTAC, HKTABB, HKTABAC, A, B, C any](t *testing.T, + eqa E.Eq[HKTA], + eqb E.Eq[HKTB], + eqc E.Eq[HKTC], + + fofc pointed.Pointed[C, HKTC], + fofaa pointed.Pointed[func(A) A, HKTAA], + fofbc pointed.Pointed[func(B) C, HKTBC], + fofabb pointed.Pointed[func(func(A) B) B, HKTABB], + + fmap functor.Functor[func(B) C, func(func(A) B) func(A) C, HKTBC, HKTABAC], + + fapabb applicative.Applicative[func(A) B, B, HKTAB, HKTB, HKTABB], + fapabac applicative.Applicative[func(A) B, func(A) C, HKTAB, HKTAC, HKTABAC], + + maa monad.Monad[A, A, HKTA, HKTA, HKTAA], + mab monad.Monad[A, B, HKTA, HKTB, HKTAB], + mac monad.Monad[A, C, HKTA, HKTC, HKTAC], + mbc monad.Monad[B, C, HKTB, HKTC, HKTBC], + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + // derivations + fofa := monad.ToPointed(maa) + fofb := monad.ToPointed(mbc) + fofab := applicative.ToPointed(fapabb) + fapaa := monad.ToApplicative(maa) + fapab := monad.ToApplicative(mab) + chainab := monad.ToChainable(mab) + chainac := monad.ToChainable(mac) + chainbc := monad.ToChainable(mbc) + fapbc := chain.ToApply(chainbc) + fapac := chain.ToApply(chainac) + + faa := monad.ToFunctor(maa) + + // applicative laws + apLaw := LA.ApplicativeAssertLaws(t, eqa, eqb, eqc, fofb, fofaa, fofbc, fofabb, faa, fmap, fapaa, fapab, fapbc, fapac, fapabb, fapabac, ab, bc) + // chain laws + chainLaw := LC.ChainAssertLaws(t, eqa, eqc, fofb, fofc, fofab, fofbc, faa, fmap, chainab, chainac, chainbc, applicative.ToApply(fapabac), ab, bc) + // monad laws + leftIdentity := MonadAssertLeftIdentity(t, eqb, fofb, mab, ab) + rightIdentity := MonadAssertRightIdentity(t, eqa, maa) + + return func(a A) bool { + fa := fofa.Of(a) + return apLaw(a) && chainLaw(fa) && leftIdentity(a) && rightIdentity(fa) + } +} diff --git a/ioeither/http/request.go b/ioeither/http/request.go index 42e4514..07d079e 100644 --- a/ioeither/http/request.go +++ b/ioeither/http/request.go @@ -124,17 +124,22 @@ func ReadJson[A any](client Client) func(Requester) IOE.IOEither[error, A] { return ReadJSON[A](client) } -// 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] { +// readJSON sends a request, reads the response and parses the response as a []byte +func readJSON(client Client) func(Requester) IOE.IOEither[error, []byte] { return F.Flow3( ReadFullResponse(client), IOE.ChainFirstEitherK(F.Flow2( H.Response, H.ValidateJSONResponse, )), - IOE.ChainEitherK(F.Flow2( - H.Body, - J.Unmarshal[A], - )), + IOE.Map[error](H.Body), + ) +} + +// 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.Flow2( + readJSON(client), + IOE.ChainEitherK[error](J.Unmarshal[A]), ) } diff --git a/optics/optional/optional.go b/optics/optional/optional.go index a14e010..6784815 100644 --- a/optics/optional/optional.go +++ b/optics/optional/optional.go @@ -113,7 +113,7 @@ func fromPredicate[S, A any](creator func(get func(S) O.Option[A], set func(S, A return func(get func(S) A, set func(S, A) S) Optional[S, A] { return creator( F.Flow2(get, fromPred), - func(s S, a A) S { + func(s S, _ A) S { return F.Pipe3( s, get, diff --git a/pair/eq.go b/pair/eq.go new file mode 100644 index 0000000..9243672 --- /dev/null +++ b/pair/eq.go @@ -0,0 +1,33 @@ +// Copyright (c) 2024 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 pair + +import ( + EQ "github.com/IBM/fp-go/eq" +) + +// Constructs an equal predicate for an `Either` +func Eq[A, B any](a EQ.Eq[A], b EQ.Eq[B]) EQ.Eq[Pair[A, B]] { + return EQ.FromEquals(func(l, r Pair[A, B]) bool { + return a.Equals(Head(l), Head(r)) && b.Equals(Tail(l), Tail(r)) + }) + +} + +// FromStrictEquals constructs an [EQ.Eq] from the canonical comparison function +func FromStrictEquals[A, B comparable]() EQ.Eq[Pair[A, B]] { + return Eq(EQ.FromStrictEquals[A](), EQ.FromStrictEquals[B]()) +} diff --git a/pair/monad.go b/pair/monad.go new file mode 100644 index 0000000..2db71b3 --- /dev/null +++ b/pair/monad.go @@ -0,0 +1,193 @@ +// Copyright (c) 2024 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 pair + +import ( + "github.com/IBM/fp-go/internal/applicative" + "github.com/IBM/fp-go/internal/functor" + "github.com/IBM/fp-go/internal/monad" + "github.com/IBM/fp-go/internal/pointed" + M "github.com/IBM/fp-go/monoid" + Sg "github.com/IBM/fp-go/semigroup" +) + +type ( + pairPointedHead[A, B any] struct { + m M.Monoid[B] + } + + pairFunctorHead[A, B, A1 any] struct { + } + + pairApplicativeHead[A, B, A1 any] struct { + s Sg.Semigroup[B] + m M.Monoid[B] + } + + pairMonadHead[A, B, A1 any] struct { + s Sg.Semigroup[B] + m M.Monoid[B] + } + + pairPointedTail[A, B any] struct { + m M.Monoid[A] + } + + pairFunctorTail[A, B, B1 any] struct { + } + + pairApplicativeTail[A, B, B1 any] struct { + s Sg.Semigroup[A] + m M.Monoid[A] + } + + pairMonadTail[A, B, B1 any] struct { + s Sg.Semigroup[A] + m M.Monoid[A] + } +) + +func (o *pairMonadHead[A, B, A1]) Of(a A) Pair[A, B] { + return MakePair(a, o.m.Empty()) +} + +func (o *pairMonadHead[A, B, A1]) Map(f func(A) A1) func(Pair[A, B]) Pair[A1, B] { + return Map[B](f) +} + +func (o *pairMonadHead[A, B, A1]) Chain(f func(A) Pair[A1, B]) func(Pair[A, B]) Pair[A1, B] { + return Chain[B, A, A1](o.s, f) +} + +func (o *pairMonadHead[A, B, A1]) Ap(fa Pair[A, B]) func(Pair[func(A) A1, B]) Pair[A1, B] { + return Ap[B, A, A1](o.s, fa) +} + +func (o *pairPointedHead[A, B]) Of(a A) Pair[A, B] { + return MakePair(a, o.m.Empty()) +} + +func (o *pairFunctorHead[A, B, A1]) Map(f func(A) A1) func(Pair[A, B]) Pair[A1, B] { + return Map[B, A, A1](f) +} + +func (o *pairApplicativeHead[A, B, A1]) Map(f func(A) A1) func(Pair[A, B]) Pair[A1, B] { + return Map[B, A, A1](f) +} + +func (o *pairApplicativeHead[A, B, A1]) Ap(fa Pair[A, B]) func(Pair[func(A) A1, B]) Pair[A1, B] { + return Ap[B, A, A1](o.s, fa) +} + +func (o *pairApplicativeHead[A, B, A1]) Of(a A) Pair[A, B] { + return MakePair(a, o.m.Empty()) +} + +// Monad implements the monadic operations for [Pair] +func Monad[A, B, A1 any](m M.Monoid[B]) monad.Monad[A, A1, Pair[A, B], Pair[A1, B], Pair[func(A) A1, B]] { + return &pairMonadHead[A, B, A1]{s: M.ToSemigroup(m), m: m} +} + +// Pointed implements the pointed operations for [Pair] +func Pointed[A, B any](m M.Monoid[B]) pointed.Pointed[A, Pair[A, B]] { + return &pairPointedHead[A, B]{m: m} +} + +// Functor implements the functor operations for [Pair] +func Functor[A, B, A1 any]() functor.Functor[A, A1, Pair[A, B], Pair[A1, B]] { + return &pairFunctorHead[A, B, A1]{} +} + +// Applicative implements the applicative operations for [Pair] +func Applicative[A, B, A1 any](m M.Monoid[B]) applicative.Applicative[A, A1, Pair[A, B], Pair[A1, B], Pair[func(A) A1, B]] { + return &pairApplicativeHead[A, B, A1]{s: M.ToSemigroup(m), m: m} +} + +// MonadHead implements the monadic operations for [Pair] +func MonadHead[A, B, A1 any](m M.Monoid[B]) monad.Monad[A, A1, Pair[A, B], Pair[A1, B], Pair[func(A) A1, B]] { + return Monad[A, B, A1](m) +} + +// PointedHead implements the pointed operations for [Pair] +func PointedHead[A, B any](m M.Monoid[B]) pointed.Pointed[A, Pair[A, B]] { + return PointedHead[A, B](m) +} + +// FunctorHead implements the functor operations for [Pair] +func FunctorHead[A, B, A1 any]() functor.Functor[A, A1, Pair[A, B], Pair[A1, B]] { + return Functor[A, B, A1]() +} + +// ApplicativeHead implements the applicative operations for [Pair] +func ApplicativeHead[A, B, A1 any](m M.Monoid[B]) applicative.Applicative[A, A1, Pair[A, B], Pair[A1, B], Pair[func(A) A1, B]] { + return Applicative[A, B, A1](m) +} + +func (o *pairMonadTail[A, B, B1]) Of(b B) Pair[A, B] { + return MakePair(o.m.Empty(), b) +} + +func (o *pairMonadTail[A, B, B1]) Map(f func(B) B1) func(Pair[A, B]) Pair[A, B1] { + return MapTail[A, B, B1](f) +} + +func (o *pairMonadTail[A, B, B1]) Chain(f func(B) Pair[A, B1]) func(Pair[A, B]) Pair[A, B1] { + return ChainTail[A, B, B1](o.s, f) +} + +func (o *pairMonadTail[A, B, B1]) Ap(fa Pair[A, B]) func(Pair[A, func(B) B1]) Pair[A, B1] { + return ApTail[A, B, B1](o.s, fa) +} + +func (o *pairPointedTail[A, B]) Of(b B) Pair[A, B] { + return MakePair(o.m.Empty(), b) +} + +func (o *pairFunctorTail[A, B, B1]) Map(f func(B) B1) func(Pair[A, B]) Pair[A, B1] { + return MapTail[A, B, B1](f) +} + +func (o *pairApplicativeTail[A, B, B1]) Map(f func(B) B1) func(Pair[A, B]) Pair[A, B1] { + return MapTail[A, B, B1](f) +} + +func (o *pairApplicativeTail[A, B, B1]) Ap(fa Pair[A, B]) func(Pair[A, func(B) B1]) Pair[A, B1] { + return ApTail[A, B, B1](o.s, fa) +} + +func (o *pairApplicativeTail[A, B, B1]) Of(b B) Pair[A, B] { + return MakePair(o.m.Empty(), b) +} + +// MonadTail implements the monadic operations for [Pair] +func MonadTail[B, A, B1 any](m M.Monoid[A]) monad.Monad[B, B1, Pair[A, B], Pair[A, B1], Pair[A, func(B) B1]] { + return &pairMonadTail[A, B, B1]{s: M.ToSemigroup(m), m: m} +} + +// PointedTail implements the pointed operations for [Pair] +func PointedTail[B, A any](m M.Monoid[A]) pointed.Pointed[B, Pair[A, B]] { + return &pairPointedTail[A, B]{m: m} +} + +// FunctorTail implements the functor operations for [Pair] +func FunctorTail[B, A, B1 any]() functor.Functor[B, B1, Pair[A, B], Pair[A, B1]] { + return &pairFunctorTail[A, B, B1]{} +} + +// ApplicativeTail implements the applicative operations for [Pair] +func ApplicativeTail[B, A, B1 any](m M.Monoid[A]) applicative.Applicative[B, B1, Pair[A, B], Pair[A, B1], Pair[A, func(B) B1]] { + return &pairApplicativeTail[A, B, B1]{s: M.ToSemigroup(m), m: m} +} diff --git a/pair/pair.go b/pair/pair.go new file mode 100644 index 0000000..7f891d8 --- /dev/null +++ b/pair/pair.go @@ -0,0 +1,204 @@ +// Copyright (c) 2024 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 pair + +import ( + "fmt" + + F "github.com/IBM/fp-go/function" + Sg "github.com/IBM/fp-go/semigroup" + T "github.com/IBM/fp-go/tuple" +) + +type ( + pair struct { + head, Tail any + } + + // Pair defines a data structure that holds two strongly typed values + Pair[A, B any] pair +) + +// String prints some debug info for the object +// +// go:noinline +func pairString(s *pair) string { + return fmt.Sprintf("Pair[%T, %t](%v, %v)", s.head, s.Tail, s.head, s.Tail) +} + +// Format prints some debug info for the object +// +// go:noinline +func pairFormat(e *pair, f fmt.State, c rune) { + switch c { + case 's': + fmt.Fprint(f, pairString(e)) + default: + fmt.Fprint(f, pairString(e)) + } +} + +// String prints some debug info for the object +func (s Pair[A, B]) String() string { + return pairString((*pair)(&s)) +} + +// Format prints some debug info for the object +func (s Pair[A, B]) Format(f fmt.State, c rune) { + pairFormat((*pair)(&s), f, c) +} + +// Of creates a [Pair] with the same value to to both fields +func Of[A any](value A) Pair[A, A] { + return Pair[A, A]{head: value, Tail: value} +} + +// FromTuple creates a [Pair] from a [T.Tuple2] +func FromTuple[A, B any](t T.Tuple2[A, B]) Pair[A, B] { + return Pair[A, B]{head: t.F1, Tail: t.F2} +} + +// ToTuple creates a [T.Tuple2] from a [Pair] +func ToTuple[A, B any](t Pair[A, B]) T.Tuple2[A, B] { + return T.MakeTuple2(Head(t), Tail(t)) +} + +// MakePair creates a [Pair] from two values +func MakePair[A, B any](a A, b B) Pair[A, B] { + return Pair[A, B]{head: a, Tail: b} +} + +// Head returns the head value of the pair +func Head[A, B any](fa Pair[A, B]) A { + return fa.head.(A) +} + +// Tail returns the head value of the pair +func Tail[A, B any](fa Pair[A, B]) B { + return fa.Tail.(B) +} + +// MonadMapHead maps the head value +func MonadMapHead[B, A, A1 any](fa Pair[A, B], f func(A) A1) Pair[A1, B] { + return Pair[A1, B]{f(Head(fa)), fa.Tail} +} + +// MonadMap maps the head value +func MonadMap[B, A, A1 any](fa Pair[A, B], f func(A) A1) Pair[A1, B] { + return MonadMapHead(fa, f) +} + +// MonadMapTail maps the Tail value +func MonadMapTail[A, B, B1 any](fa Pair[A, B], f func(B) B1) Pair[A, B1] { + return Pair[A, B1]{fa.head, f(Tail(fa))} +} + +// MonadBiMap maps both values +func MonadBiMap[A, B, A1, B1 any](fa Pair[A, B], f func(A) A1, g func(B) B1) Pair[A1, B1] { + return Pair[A1, B1]{f(Head(fa)), g(Tail(fa))} +} + +// Map maps the head value +func Map[B, A, A1 any](f func(A) A1) func(Pair[A, B]) Pair[A1, B] { + return MapHead[B, A, A1](f) +} + +// MapHead maps the head value +func MapHead[B, A, A1 any](f func(A) A1) func(Pair[A, B]) Pair[A1, B] { + return F.Bind2nd(MonadMapHead[B, A, A1], f) +} + +// MapTail maps the Tail value +func MapTail[A, B, B1 any](f func(B) B1) func(Pair[A, B]) Pair[A, B1] { + return F.Bind2nd(MonadMapTail[A, B, B1], f) +} + +// BiMap maps both values +func BiMap[A, B, A1, B1 any](f func(A) A1, g func(B) B1) func(Pair[A, B]) Pair[A1, B1] { + return func(fa Pair[A, B]) Pair[A1, B1] { + return MonadBiMap(fa, f, g) + } +} + +// MonadChainHead chains on the head value +func MonadChainHead[B, A, A1 any](sg Sg.Semigroup[B], fa Pair[A, B], f func(A) Pair[A1, B]) Pair[A1, B] { + fb := f(Head(fa)) + return Pair[A1, B]{fb.head, sg.Concat(Tail(fa), Tail(fb))} +} + +// MonadChainTail chains on the Tail value +func MonadChainTail[A, B, B1 any](sg Sg.Semigroup[A], fb Pair[A, B], f func(B) Pair[A, B1]) Pair[A, B1] { + fa := f(Tail(fb)) + return Pair[A, B1]{sg.Concat(Head(fb), Head(fa)), fa.Tail} +} + +// MonadChain chains on the head value +func MonadChain[B, A, A1 any](sg Sg.Semigroup[B], fa Pair[A, B], f func(A) Pair[A1, B]) Pair[A1, B] { + return MonadChainHead(sg, fa, f) +} + +// ChainHead chains on the head value +func ChainHead[B, A, A1 any](sg Sg.Semigroup[B], f func(A) Pair[A1, B]) func(Pair[A, B]) Pair[A1, B] { + return func(fa Pair[A, B]) Pair[A1, B] { + return MonadChainHead(sg, fa, f) + } +} + +// ChainTail chains on the Tail value +func ChainTail[A, B, B1 any](sg Sg.Semigroup[A], f func(B) Pair[A, B1]) func(Pair[A, B]) Pair[A, B1] { + return func(fa Pair[A, B]) Pair[A, B1] { + return MonadChainTail(sg, fa, f) + } +} + +// Chain chains on the head value +func Chain[B, A, A1 any](sg Sg.Semigroup[B], f func(A) Pair[A1, B]) func(Pair[A, B]) Pair[A1, B] { + return ChainHead[B, A, A1](sg, f) +} + +// MonadApHead applies on the head value +func MonadApHead[B, A, A1 any](sg Sg.Semigroup[B], faa Pair[func(A) A1, B], fa Pair[A, B]) Pair[A1, B] { + return Pair[A1, B]{Head(faa)(Head(fa)), sg.Concat(Tail(fa), Tail(faa))} +} + +// MonadApTail applies on the Tail value +func MonadApTail[A, B, B1 any](sg Sg.Semigroup[A], fbb Pair[A, func(B) B1], fb Pair[A, B]) Pair[A, B1] { + return Pair[A, B1]{sg.Concat(Head(fb), Head(fbb)), Tail(fbb)(Tail(fb))} +} + +// MonadAp applies on the head value +func MonadAp[B, A, A1 any](sg Sg.Semigroup[B], faa Pair[func(A) A1, B], fa Pair[A, B]) Pair[A1, B] { + return MonadApHead(sg, faa, fa) +} + +// ApHead applies on the head value +func ApHead[B, A, A1 any](sg Sg.Semigroup[B], fa Pair[A, B]) func(Pair[func(A) A1, B]) Pair[A1, B] { + return func(faa Pair[func(A) A1, B]) Pair[A1, B] { + return MonadApHead(sg, faa, fa) + } +} + +// ApTail applies on the Tail value +func ApTail[A, B, B1 any](sg Sg.Semigroup[A], fb Pair[A, B]) func(Pair[A, func(B) B1]) Pair[A, B1] { + return func(fbb Pair[A, func(B) B1]) Pair[A, B1] { + return MonadApTail(sg, fbb, fb) + } +} + +// Ap applies on the head value +func Ap[B, A, A1 any](sg Sg.Semigroup[B], fa Pair[A, B]) func(Pair[func(A) A1, B]) Pair[A1, B] { + return ApHead[B, A, A1](sg, fa) +} diff --git a/pair/testing/laws.go b/pair/testing/laws.go new file mode 100644 index 0000000..079d307 --- /dev/null +++ b/pair/testing/laws.go @@ -0,0 +1,155 @@ +// Copyright (c) 2024 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 testing + +import ( + "testing" + + EQ "github.com/IBM/fp-go/eq" + L "github.com/IBM/fp-go/internal/monad/testing" + P "github.com/IBM/fp-go/pair" + + M "github.com/IBM/fp-go/monoid" +) + +// AssertLaws asserts the apply monad laws for the [P.Pair] monad +func assertLawsHead[E, A, B, C any](t *testing.T, + m M.Monoid[E], + + eqe EQ.Eq[E], + eqa EQ.Eq[A], + eqb EQ.Eq[B], + eqc EQ.Eq[C], + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + + fofc := P.Pointed[C](m) + fofaa := P.Pointed[func(A) A](m) + fofbc := P.Pointed[func(B) C](m) + fofabb := P.Pointed[func(func(A) B) B](m) + + fmap := P.Functor[func(B) C, E, func(func(A) B) func(A) C]() + + fapabb := P.Applicative[func(A) B, E, B](m) + fapabac := P.Applicative[func(A) B, E, func(A) C](m) + + maa := P.Monad[A, E, A](m) + mab := P.Monad[A, E, B](m) + mac := P.Monad[A, E, C](m) + mbc := P.Monad[B, E, C](m) + + return L.MonadAssertLaws(t, + P.Eq(eqa, eqe), + P.Eq(eqb, eqe), + P.Eq(eqc, eqe), + + fofc, + fofaa, + fofbc, + fofabb, + + fmap, + + fapabb, + fapabac, + + maa, + mab, + mac, + mbc, + + ab, + bc, + ) + +} + +// AssertLaws asserts the apply monad laws for the [P.Pair] monad +func assertLawsTail[E, A, B, C any](t *testing.T, + m M.Monoid[E], + + eqe EQ.Eq[E], + eqa EQ.Eq[A], + eqb EQ.Eq[B], + eqc EQ.Eq[C], + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + + fofc := P.PointedTail[C](m) + fofaa := P.PointedTail[func(A) A](m) + fofbc := P.PointedTail[func(B) C](m) + fofabb := P.PointedTail[func(func(A) B) B](m) + + fmap := P.FunctorTail[func(B) C, E, func(func(A) B) func(A) C]() + + fapabb := P.ApplicativeTail[func(A) B, E, B](m) + fapabac := P.ApplicativeTail[func(A) B, E, func(A) C](m) + + maa := P.MonadTail[A, E, A](m) + mab := P.MonadTail[A, E, B](m) + mac := P.MonadTail[A, E, C](m) + mbc := P.MonadTail[B, E, C](m) + + return L.MonadAssertLaws(t, + P.Eq(eqe, eqa), + P.Eq(eqe, eqb), + P.Eq(eqe, eqc), + + fofc, + fofaa, + fofbc, + fofabb, + + fmap, + + fapabb, + fapabac, + + maa, + mab, + mac, + mbc, + + ab, + bc, + ) + +} + +// AssertLaws asserts the apply monad laws for the [P.Pair] monad +func AssertLaws[E, A, B, C any](t *testing.T, + m M.Monoid[E], + + eqe EQ.Eq[E], + eqa EQ.Eq[A], + eqb EQ.Eq[B], + eqc EQ.Eq[C], + + ab func(A) B, + bc func(B) C, +) func(A) bool { + + head := assertLawsHead(t, m, eqe, eqa, eqb, eqc, ab, bc) + tail := assertLawsHead(t, m, eqe, eqa, eqb, eqc, ab, bc) + + return func(a A) bool { + return head(a) && tail(a) + } +} diff --git a/pair/testing/laws_test.go b/pair/testing/laws_test.go new file mode 100644 index 0000000..8240ddf --- /dev/null +++ b/pair/testing/laws_test.go @@ -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 testing + +import ( + "fmt" + "testing" + + EQ "github.com/IBM/fp-go/eq" + S "github.com/IBM/fp-go/string" + "github.com/stretchr/testify/assert" +) + +func TestMonadLaws(t *testing.T) { + // some comparison + eqe := EQ.FromStrictEquals[string]() + eqa := EQ.FromStrictEquals[bool]() + eqb := EQ.FromStrictEquals[int]() + eqc := EQ.FromStrictEquals[string]() + + m := S.Monoid + + ab := func(a bool) int { + if a { + return 1 + } + return 0 + } + + bc := func(b int) string { + return fmt.Sprintf("value %d", b) + } + + laws := AssertLaws(t, m, eqe, eqa, eqb, eqc, ab, bc) + + assert.True(t, laws(true)) + assert.True(t, laws(false)) +}