From 01786a054b28faf99c3555982ebb2de586346cfa Mon Sep 17 00:00:00 2001 From: "Dr. Carsten Leue" Date: Tue, 13 Feb 2024 10:44:57 +0100 Subject: [PATCH] fix: refactor Writer monad Signed-off-by: Dr. Carsten Leue --- writer/bind.go | 11 ++-- writer/bind_test.go | 20 ++++---- writer/generic/bind.go | 25 +++++---- writer/generic/eq.go | 13 ++--- writer/generic/monad.go | 106 +++++++++++++++++++++++++++++++++++++++ writer/generic/writer.go | 104 +++++++++++++++++++------------------- writer/monad.go | 45 +++++++++++++++++ writer/testing/laws.go | 53 ++++++++++---------- writer/writer.go | 58 ++++++++++----------- writer/writer_test.go | 16 +++--- 10 files changed, 302 insertions(+), 149 deletions(-) create mode 100644 writer/generic/monad.go create mode 100644 writer/monad.go diff --git a/writer/bind.go b/writer/bind.go index 981576b..3263b96 100644 --- a/writer/bind.go +++ b/writer/bind.go @@ -17,20 +17,22 @@ package writer import ( M "github.com/IBM/fp-go/monoid" + SG "github.com/IBM/fp-go/semigroup" G "github.com/IBM/fp-go/writer/generic" ) // Bind creates an empty context of type [S] to be used with the [Bind] operation -func Do[S, W any](m M.Monoid[W]) func(S) Writer[W, S] { - return G.Do[Writer[W, S], W, S](m) +func Do[S, W any](m M.Monoid[W], s S) Writer[W, S] { + return G.Do[Writer[W, S], W, S](m, s) } // Bind attaches the result of a computation to a context [S1] to produce a context [S2] func Bind[S1, S2, T, W any]( + s SG.Semigroup[W], setter func(T) func(S1) S2, f func(S1) Writer[W, T], ) func(Writer[W, S1]) Writer[W, S2] { - return G.Bind[Writer[W, S1], Writer[W, S2], Writer[W, T], W, S1, S2, T](setter, f) + return G.Bind[Writer[W, S1], Writer[W, S2], Writer[W, T], W, S1, S2, T](s, setter, f) } // Let attaches the result of a computation to a context [S1] to produce a context [S2] @@ -58,8 +60,9 @@ func BindTo[W, S1, T any]( // ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently func ApS[S1, S2, T, W any]( + s SG.Semigroup[W], setter func(T) func(S1) S2, fa Writer[W, T], ) func(Writer[W, S1]) Writer[W, S2] { - return G.ApS[Writer[W, S1], Writer[W, S2], Writer[W, T], W, S1, S2, T](setter, fa) + return G.ApS[Writer[W, S1], Writer[W, S2], Writer[W, T], W, S1, S2, T](s, setter, fa) } diff --git a/writer/bind_test.go b/writer/bind_test.go index 425d488..fd6deca 100644 --- a/writer/bind_test.go +++ b/writer/bind_test.go @@ -34,33 +34,33 @@ var ( ) func getLastName(s utils.Initial) Writer[[]string, string] { - return Of[string](monoid)("Doe") + return Of[string](monoid, "Doe") } func getGivenName(s utils.WithLastName) Writer[[]string, string] { - return Of[string](monoid)("John") + return Of[string](monoid, "John") } func TestBind(t *testing.T) { res := F.Pipe3( - Do[utils.Initial](monoid)(utils.Empty), - Bind(utils.SetLastName, getLastName), - Bind(utils.SetGivenName, getGivenName), + Do[utils.Initial](monoid, utils.Empty), + Bind(sg, utils.SetLastName, getLastName), + Bind(sg, utils.SetGivenName, getGivenName), Map[[]string](utils.GetFullName), ) - assert.True(t, eq.Equals(res, Of[string](monoid)("John Doe"))) + assert.True(t, eq.Equals(res, Of[string](monoid, "John Doe"))) } func TestApS(t *testing.T) { res := F.Pipe3( - Do[utils.Initial](monoid)(utils.Empty), - ApS(utils.SetLastName, Of[string](monoid)("Doe")), - ApS(utils.SetGivenName, Of[string](monoid)("John")), + Do[utils.Initial](monoid, utils.Empty), + ApS(sg, utils.SetLastName, Of[string](monoid, "Doe")), + ApS(sg, utils.SetGivenName, Of[string](monoid, "John")), Map[[]string](utils.GetFullName), ) - assert.True(t, eq.Equals(res, Of[string](monoid)("John Doe"))) + assert.True(t, eq.Equals(res, Of[string](monoid, "John Doe"))) } diff --git a/writer/generic/bind.go b/writer/generic/bind.go index 5b90101..449e019 100644 --- a/writer/generic/bind.go +++ b/writer/generic/bind.go @@ -16,26 +16,28 @@ package generic import ( + FCT "github.com/IBM/fp-go/function" "github.com/IBM/fp-go/internal/apply" C "github.com/IBM/fp-go/internal/chain" F "github.com/IBM/fp-go/internal/functor" M "github.com/IBM/fp-go/monoid" + P "github.com/IBM/fp-go/pair" SG "github.com/IBM/fp-go/semigroup" - T "github.com/IBM/fp-go/tuple" ) // Bind creates an empty context of type [S] to be used with the [Bind] operation -func Do[GS ~func() T.Tuple3[S, W, SG.Semigroup[W]], W, S any](m M.Monoid[W]) func(S) GS { - return Of[GS, W, S](m) +func Do[GS ~func() P.Pair[S, W], W, S any](m M.Monoid[W], s S) GS { + return Of[GS, W, S](m, s) } // Bind attaches the result of a computation to a context [S1] to produce a context [S2] -func Bind[GS1 ~func() T.Tuple3[S1, W, SG.Semigroup[W]], GS2 ~func() T.Tuple3[S2, W, SG.Semigroup[W]], GT ~func() T.Tuple3[A, W, SG.Semigroup[W]], W, S1, S2, A any]( +func Bind[GS1 ~func() P.Pair[S1, W], GS2 ~func() P.Pair[S2, W], GT ~func() P.Pair[A, W], W, S1, S2, A any]( + s SG.Semigroup[W], setter func(A) func(S1) S2, f func(S1) GT, ) func(GS1) GS2 { return C.Bind( - Chain[GS2, GS1, func(S1) GS2, W, S1, S2], + FCT.Bind1st(Chain[GS2, GS1, func(S1) GS2, W, S1, S2], s), Map[GS2, GT, func(A) S2, W, A, S2], setter, f, @@ -43,7 +45,7 @@ func Bind[GS1 ~func() T.Tuple3[S1, W, SG.Semigroup[W]], GS2 ~func() T.Tuple3[S2, } // Let attaches the result of a computation to a context [S1] to produce a context [S2] -func Let[GS1 ~func() T.Tuple3[S1, W, SG.Semigroup[W]], GS2 ~func() T.Tuple3[S2, W, SG.Semigroup[W]], W, S1, S2, A any]( +func Let[GS1 ~func() P.Pair[S1, W], GS2 ~func() P.Pair[S2, W], W, S1, S2, A any]( key func(A) func(S1) S2, f func(S1) A, ) func(GS1) GS2 { @@ -55,7 +57,7 @@ func Let[GS1 ~func() T.Tuple3[S1, W, SG.Semigroup[W]], GS2 ~func() T.Tuple3[S2, } // LetTo attaches the a value to a context [S1] to produce a context [S2] -func LetTo[GS1 ~func() T.Tuple3[S1, W, SG.Semigroup[W]], GS2 ~func() T.Tuple3[S2, W, SG.Semigroup[W]], W, S1, S2, B any]( +func LetTo[GS1 ~func() P.Pair[S1, W], GS2 ~func() P.Pair[S2, W], W, S1, S2, B any]( key func(B) func(S1) S2, b B, ) func(GS1) GS2 { @@ -67,7 +69,7 @@ func LetTo[GS1 ~func() T.Tuple3[S1, W, SG.Semigroup[W]], GS2 ~func() T.Tuple3[S2 } // BindTo initializes a new state [S1] from a value [T] -func BindTo[GS1 ~func() T.Tuple3[S1, W, SG.Semigroup[W]], GT ~func() T.Tuple3[A, W, SG.Semigroup[W]], W, S1, A any]( +func BindTo[GS1 ~func() P.Pair[S1, W], GT ~func() P.Pair[A, W], W, S1, A any]( setter func(A) S1, ) func(GT) GS1 { return C.BindTo( @@ -77,13 +79,14 @@ func BindTo[GS1 ~func() T.Tuple3[S1, W, SG.Semigroup[W]], GT ~func() T.Tuple3[A, } // ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently -func ApS[GS1 ~func() T.Tuple3[S1, W, SG.Semigroup[W]], GS2 ~func() T.Tuple3[S2, W, SG.Semigroup[W]], GT ~func() T.Tuple3[A, W, SG.Semigroup[W]], W, S1, S2, A any]( +func ApS[GS1 ~func() P.Pair[S1, W], GS2 ~func() P.Pair[S2, W], GT ~func() P.Pair[A, W], W, S1, S2, A any]( + s SG.Semigroup[W], setter func(A) func(S1) S2, fa GT, ) func(GS1) GS2 { return apply.ApS( - Ap[GS2, func() T.Tuple3[func(A) S2, W, SG.Semigroup[W]], GT, W, A, S2], - Map[func() T.Tuple3[func(A) S2, W, SG.Semigroup[W]], GS1, func(S1) func(A) S2], + FCT.Bind1st(Ap[GS2, func() P.Pair[func(A) S2, W], GT, W, A, S2], s), + Map[func() P.Pair[func(A) S2, W], GS1, func(S1) func(A) S2], setter, fa, ) diff --git a/writer/generic/eq.go b/writer/generic/eq.go index 9bd9092..e34ec2d 100644 --- a/writer/generic/eq.go +++ b/writer/generic/eq.go @@ -17,21 +17,18 @@ package generic import ( EQ "github.com/IBM/fp-go/eq" - SG "github.com/IBM/fp-go/semigroup" - T "github.com/IBM/fp-go/tuple" + P "github.com/IBM/fp-go/pair" ) // Constructs an equal predicate for a [Writer] -func Eq[GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], W, A any](w EQ.Eq[W], a EQ.Eq[A]) EQ.Eq[GA] { +func Eq[GA ~func() P.Pair[A, W], W, A any](w EQ.Eq[W], a EQ.Eq[A]) EQ.Eq[GA] { + eqp := P.Eq(a, w) return EQ.FromEquals(func(l, r GA) bool { - ll := l() - rr := r() - - return a.Equals(ll.F1, rr.F1) && w.Equals(ll.F2, rr.F2) + return eqp.Equals(l(), r()) }) } // FromStrictEquals constructs an [EQ.Eq] from the canonical comparison function -func FromStrictEquals[GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], W, A comparable]() EQ.Eq[GA] { +func FromStrictEquals[GA ~func() P.Pair[A, W], W, A comparable]() EQ.Eq[GA] { return Eq[GA](EQ.FromStrictEquals[W](), EQ.FromStrictEquals[A]()) } diff --git a/writer/generic/monad.go b/writer/generic/monad.go new file mode 100644 index 0000000..fd97a1d --- /dev/null +++ b/writer/generic/monad.go @@ -0,0 +1,106 @@ +// 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 generic + +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" + P "github.com/IBM/fp-go/pair" + SG "github.com/IBM/fp-go/semigroup" +) + +type writerPointed[GA ~func() P.Pair[A, W], W, A any] struct { + m M.Monoid[W] +} + +type writerFunctor[GB ~func() P.Pair[B, W], GA ~func() P.Pair[A, W], W, A, B any] struct{} + +type writerApplicative[GB ~func() P.Pair[B, W], GAB ~func() P.Pair[func(A) B, W], GA ~func() P.Pair[A, W], W, A, B any] struct { + s SG.Semigroup[W] + m M.Monoid[W] +} + +type writerMonad[GB ~func() P.Pair[B, W], GAB ~func() P.Pair[func(A) B, W], GA ~func() P.Pair[A, W], W, A, B any] struct { + s SG.Semigroup[W] + m M.Monoid[W] +} + +func (o *writerPointed[GA, W, A]) Of(a A) GA { + return Of[GA](o.m, a) +} + +func (o *writerApplicative[GB, GAB, GA, W, A, B]) Of(a A) GA { + return Of[GA](o.m, a) +} + +func (o *writerMonad[GB, GAB, GA, W, A, B]) Of(a A) GA { + return Of[GA](o.m, a) +} + +func (o *writerFunctor[GB, GA, W, A, B]) Map(f func(A) B) func(GA) GB { + return Map[GB, GA](f) +} + +func (o *writerApplicative[GB, GAB, GA, W, A, B]) Map(f func(A) B) func(GA) GB { + return Map[GB, GA](f) +} + +func (o *writerMonad[GB, GAB, GA, W, A, B]) Map(f func(A) B) func(GA) GB { + return Map[GB, GA](f) +} + +func (o *writerMonad[GB, GAB, GA, W, A, B]) Chain(f func(A) GB) func(GA) GB { + return Chain[GB, GA](o.s, f) +} + +func (o *writerApplicative[GB, GAB, GA, W, A, B]) Ap(fa GA) func(GAB) GB { + return Ap[GB, GAB, GA](o.s, fa) +} + +func (o *writerMonad[GB, GAB, GA, W, A, B]) Ap(fa GA) func(GAB) GB { + return Ap[GB, GAB, GA](o.s, fa) +} + +// Pointed implements the pointed operations for [Writer] +func Pointed[GA ~func() P.Pair[A, W], W, A any](m M.Monoid[W]) pointed.Pointed[A, GA] { + return &writerPointed[GA, W, A]{ + m: m, + } +} + +// Functor implements the functor operations for [Writer] +func Functor[GB ~func() P.Pair[B, W], GA ~func() P.Pair[A, W], W, A, B any]() functor.Functor[A, B, GA, GB] { + return &writerFunctor[GB, GA, W, A, B]{} +} + +// Applicative implements the applicative operations for [Writer] +func Applicative[GB ~func() P.Pair[B, W], GAB ~func() P.Pair[func(A) B, W], GA ~func() P.Pair[A, W], W, A, B any](m M.Monoid[W]) applicative.Applicative[A, B, GA, GB, GAB] { + return &writerApplicative[GB, GAB, GA, W, A, B]{ + s: M.ToSemigroup(m), + m: m, + } +} + +// Monad implements the monadic operations for [Writer] +func Monad[GB ~func() P.Pair[B, W], GAB ~func() P.Pair[func(A) B, W], GA ~func() P.Pair[A, W], W, A, B any](m M.Monoid[W]) monad.Monad[A, B, GA, GB, GAB] { + return &writerMonad[GB, GAB, GA, W, A, B]{ + s: M.ToSemigroup(m), + m: m, + } +} diff --git a/writer/generic/writer.go b/writer/generic/writer.go index df38d14..90434f0 100644 --- a/writer/generic/writer.go +++ b/writer/generic/writer.go @@ -21,133 +21,133 @@ import ( FC "github.com/IBM/fp-go/internal/functor" IO "github.com/IBM/fp-go/io/generic" M "github.com/IBM/fp-go/monoid" + P "github.com/IBM/fp-go/pair" SG "github.com/IBM/fp-go/semigroup" - T "github.com/IBM/fp-go/tuple" ) -func Tell[GA ~func() T.Tuple3[any, W, SG.Semigroup[W]], W any](s SG.Semigroup[W]) func(W) GA { - return F.Flow2( - F.Bind13of3(T.MakeTuple3[any, W, SG.Semigroup[W]])(nil, s), - IO.Of[GA], - ) +func Tell[GA ~func() P.Pair[any, W], W any](w W) GA { + return IO.Of[GA](P.MakePair[any](w, w)) } -func Of[GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], W, A any](m M.Monoid[W]) func(A) GA { - return F.Flow2( - F.Bind23of3(T.MakeTuple3[A, W, SG.Semigroup[W]])(m.Empty(), M.ToSemigroup(m)), - IO.Of[GA], - ) +func Of[GA ~func() P.Pair[A, W], W, A any](m M.Monoid[W], a A) GA { + return IO.Of[GA](P.MakePair(a, m.Empty())) } // Listen modifies the result to include the changes to the accumulator -func Listen[GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], GTA ~func() T.Tuple3[T.Tuple2[A, W], W, SG.Semigroup[W]], W, A any](fa GA) GTA { - return func() T.Tuple3[T.Tuple2[A, W], W, SG.Semigroup[W]] { +func Listen[GA ~func() P.Pair[A, W], GTA ~func() P.Pair[P.Pair[A, W], W], W, A any](fa GA) GTA { + return func() P.Pair[P.Pair[A, W], W] { t := fa() - return T.MakeTuple3(T.MakeTuple2(t.F1, t.F2), t.F2, t.F3) + return P.MakePair(t, P.Tail(t)) } } // Pass applies the returned function to the accumulator -func Pass[GFA ~func() T.Tuple3[T.Tuple2[A, FCT], W, SG.Semigroup[W]], GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], FCT ~func(W) W, W, A any](fa GFA) GA { - return func() T.Tuple3[A, W, SG.Semigroup[W]] { +func Pass[GFA ~func() P.Pair[P.Pair[A, FCT], W], GA ~func() P.Pair[A, W], FCT ~func(W) W, W, A any](fa GFA) GA { + return func() P.Pair[A, W] { t := fa() - return T.MakeTuple3(t.F1.F1, t.F1.F2(t.F2), t.F3) + a := P.Head(t) + return P.MakePair(P.Head(a), P.Tail(a)(P.Tail(t))) } } -func MonadMap[GB ~func() T.Tuple3[B, W, SG.Semigroup[W]], GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], FCT ~func(A) B, W, A, B any](fa GA, f FCT) GB { - return IO.MonadMap[GA, GB](fa, T.Map3(f, F.Identity[W], F.Identity[SG.Semigroup[W]])) +func MonadMap[GB ~func() P.Pair[B, W], GA ~func() P.Pair[A, W], FCT ~func(A) B, W, A, B any](fa GA, f FCT) GB { + return IO.MonadMap[GA, GB](fa, P.Map[W](f)) } -func Map[GB ~func() T.Tuple3[B, W, SG.Semigroup[W]], GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], FCT ~func(A) B, W, A, B any](f FCT) func(GA) GB { - return IO.Map[GA, GB](T.Map3(f, F.Identity[W], F.Identity[SG.Semigroup[W]])) +func Map[GB ~func() P.Pair[B, W], GA ~func() P.Pair[A, W], FCT ~func(A) B, W, A, B any](f FCT) func(GA) GB { + return IO.Map[GA, GB](P.Map[W](f)) } -func MonadChain[GB ~func() T.Tuple3[B, W, SG.Semigroup[W]], GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], FCT ~func(A) GB, W, A, B any](fa GA, f FCT) GB { - return func() T.Tuple3[B, W, SG.Semigroup[W]] { +func MonadChain[GB ~func() P.Pair[B, W], GA ~func() P.Pair[A, W], FCT ~func(A) GB, W, A, B any](s SG.Semigroup[W], fa GA, f FCT) GB { + return func() P.Pair[B, W] { a := fa() - b := f(a.F1)() + b := f(P.Head(a))() - return T.MakeTuple3(b.F1, b.F3.Concat(a.F2, b.F2), b.F3) + return P.MakePair(P.Head(b), s.Concat(P.Tail(a), P.Tail(b))) } } -func Chain[GB ~func() T.Tuple3[B, W, SG.Semigroup[W]], GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], FCT ~func(A) GB, W, A, B any](f FCT) func(GA) GB { - return F.Bind2nd(MonadChain[GB, GA, FCT, W, A, B], f) +func Chain[GB ~func() P.Pair[B, W], GA ~func() P.Pair[A, W], FCT ~func(A) GB, W, A, B any](s SG.Semigroup[W], f FCT) func(GA) GB { + return func(fa GA) GB { + return MonadChain(s, fa, f) + } } -func MonadAp[GB ~func() T.Tuple3[B, W, SG.Semigroup[W]], GAB ~func() T.Tuple3[func(A) B, W, SG.Semigroup[W]], GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], W, A, B any](fab GAB, fa GA) GB { - return func() T.Tuple3[B, W, SG.Semigroup[W]] { +func MonadAp[GB ~func() P.Pair[B, W], GAB ~func() P.Pair[func(A) B, W], GA ~func() P.Pair[A, W], W, A, B any](s SG.Semigroup[W], fab GAB, fa GA) GB { + return func() P.Pair[B, W] { f := fab() a := fa() - return T.MakeTuple3(f.F1(a.F1), f.F3.Concat(f.F2, a.F2), f.F3) + return P.MakePair(P.Head(f)(P.Head(a)), s.Concat(P.Tail(f), P.Tail(a))) } } -func Ap[GB ~func() T.Tuple3[B, W, SG.Semigroup[W]], GAB ~func() T.Tuple3[func(A) B, W, SG.Semigroup[W]], GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], W, A, B any](ga GA) func(GAB) GB { - return F.Bind2nd(MonadAp[GB, GAB, GA], ga) +func Ap[GB ~func() P.Pair[B, W], GAB ~func() P.Pair[func(A) B, W], GA ~func() P.Pair[A, W], W, A, B any](s SG.Semigroup[W], ga GA) func(GAB) GB { + return func(fab GAB) GB { + return MonadAp[GB](s, fab, ga) + } } -func MonadChainFirst[GB ~func() T.Tuple3[B, W, SG.Semigroup[W]], GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], FCT ~func(A) GB, W, A, B any](ma GA, f FCT) GA { +func MonadChainFirst[GB ~func() P.Pair[B, W], GA ~func() P.Pair[A, W], FCT ~func(A) GB, W, A, B any](s SG.Semigroup[W], ma GA, f FCT) GA { return C.MonadChainFirst( - MonadChain[GA, GA, func(A) GA], + F.Bind1of3(MonadChain[GA, GA, func(A) GA])(s), MonadMap[GA, GB, func(B) A], ma, f, ) } -func ChainFirst[GB ~func() T.Tuple3[B, W, SG.Semigroup[W]], GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], FCT ~func(A) GB, W, A, B any](f FCT) func(GA) GA { +func ChainFirst[GB ~func() P.Pair[B, W], GA ~func() P.Pair[A, W], FCT ~func(A) GB, W, A, B any](s SG.Semigroup[W], f FCT) func(GA) GA { return C.ChainFirst( - Chain[GA, GA, func(A) GA], + F.Bind1st(Chain[GA, GA, func(A) GA], s), Map[GA, GB, func(B) A], f, ) } -func Flatten[GAA ~func() T.Tuple3[GA, W, SG.Semigroup[W]], GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], W, A any](mma GAA) GA { - return MonadChain[GA, GAA, func(GA) GA](mma, F.Identity[GA]) +func Flatten[GAA ~func() P.Pair[GA, W], GA ~func() P.Pair[A, W], W, A any](s SG.Semigroup[W], mma GAA) GA { + return MonadChain[GA, GAA, func(GA) GA](s, mma, F.Identity[GA]) } -func Execute[GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], W, A any](fa GA) W { - return fa().F2 +func Execute[GA ~func() P.Pair[A, W], W, A any](fa GA) W { + return P.Tail(fa()) } -func Evaluate[GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], W, A any](fa GA) A { - return fa().F1 +func Evaluate[GA ~func() P.Pair[A, W], W, A any](fa GA) A { + return P.Head(fa()) } // MonadCensor modifies the final accumulator value by applying a function -func MonadCensor[GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], FCT ~func(W) W, W, A any](fa GA, f FCT) GA { - return IO.MonadMap[GA, GA](fa, T.Map3(F.Identity[A], f, F.Identity[SG.Semigroup[W]])) +func MonadCensor[GA ~func() P.Pair[A, W], FCT ~func(W) W, W, A any](fa GA, f FCT) GA { + return IO.MonadMap[GA, GA](fa, P.MapTail[A](f)) } // Censor modifies the final accumulator value by applying a function -func Censor[GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], FCT ~func(W) W, W, A any](f FCT) func(GA) GA { - return IO.Map[GA, GA](T.Map3(F.Identity[A], f, F.Identity[SG.Semigroup[W]])) +func Censor[GA ~func() P.Pair[A, W], FCT ~func(W) W, W, A any](f FCT) func(GA) GA { + return IO.Map[GA, GA](P.MapTail[A](f)) } // MonadListens projects a value from modifications made to the accumulator during an action -func MonadListens[GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], GAB ~func() T.Tuple3[T.Tuple2[A, B], W, SG.Semigroup[W]], FCT ~func(W) B, W, A, B any](fa GA, f FCT) GAB { - return func() T.Tuple3[T.Tuple2[A, B], W, SG.Semigroup[W]] { +func MonadListens[GA ~func() P.Pair[A, W], GAB ~func() P.Pair[P.Pair[A, B], W], FCT ~func(W) B, W, A, B any](fa GA, f FCT) GAB { + return func() P.Pair[P.Pair[A, B], W] { a := fa() - return T.MakeTuple3(T.MakeTuple2(a.F1, f(a.F2)), a.F2, a.F3) + t := P.Tail(a) + return P.MakePair(P.MakePair(P.Head(a), f(t)), t) } } // Listens projects a value from modifications made to the accumulator during an action -func Listens[GA ~func() T.Tuple3[A, W, SG.Semigroup[W]], GAB ~func() T.Tuple3[T.Tuple2[A, B], W, SG.Semigroup[W]], FCT ~func(W) B, W, A, B any](f FCT) func(GA) GAB { +func Listens[GA ~func() P.Pair[A, W], GAB ~func() P.Pair[P.Pair[A, B], W], FCT ~func(W) B, W, A, B any](f FCT) func(GA) GAB { return F.Bind2nd(MonadListens[GA, GAB, FCT], f) } -func MonadFlap[FAB ~func(A) B, GFAB ~func() T.Tuple3[FAB, W, SG.Semigroup[W]], GB ~func() T.Tuple3[B, W, SG.Semigroup[W]], W, A, B any](fab GFAB, a A) GB { +func MonadFlap[FAB ~func(A) B, GFAB ~func() P.Pair[FAB, W], GB ~func() P.Pair[B, W], W, A, B any](fab GFAB, a A) GB { return FC.MonadFlap( MonadMap[GB, GFAB, func(FAB) B], fab, a) } -func Flap[FAB ~func(A) B, GFAB ~func() T.Tuple3[FAB, W, SG.Semigroup[W]], GB ~func() T.Tuple3[B, W, SG.Semigroup[W]], W, A, B any](a A) func(GFAB) GB { +func Flap[FAB ~func(A) B, GFAB ~func() P.Pair[FAB, W], GB ~func() P.Pair[B, W], W, A, B any](a A) func(GFAB) GB { return FC.Flap(Map[GB, GFAB, func(FAB) B], a) } diff --git a/writer/monad.go b/writer/monad.go new file mode 100644 index 0000000..93493c6 --- /dev/null +++ b/writer/monad.go @@ -0,0 +1,45 @@ +// 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 writer + +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" + G "github.com/IBM/fp-go/writer/generic" +) + +// Pointed implements the pointed operations for [Writer] +func Pointed[W, A any](m M.Monoid[W]) pointed.Pointed[A, Writer[W, A]] { + return G.Pointed[Writer[W, A], W, A](m) +} + +// Functor implements the pointed operations for [Writer] +func Functor[W, A, B any]() functor.Functor[A, B, Writer[W, A], Writer[W, B]] { + return G.Functor[Writer[W, B], Writer[W, A], W, A, B]() +} + +// Applicative implements the applicative operations for [Writer] +func Applicative[W, A, B any](m M.Monoid[W]) applicative.Applicative[A, B, Writer[W, A], Writer[W, B], Writer[W, func(A) B]] { + return G.Applicative[Writer[W, B], Writer[W, func(A) B], Writer[W, A]](m) +} + +// Monad implements the monadic operations for [Writer] +func Monad[W, A, B any](m M.Monoid[W]) monad.Monad[A, B, Writer[W, A], Writer[W, B], Writer[W, func(A) B]] { + return G.Monad[Writer[W, B], Writer[W, func(A) B], Writer[W, A]](m) +} diff --git a/writer/testing/laws.go b/writer/testing/laws.go index ed24c06..084430e 100644 --- a/writer/testing/laws.go +++ b/writer/testing/laws.go @@ -37,39 +37,40 @@ func AssertLaws[W, A, B, C any](t *testing.T, bc func(B) C, ) func(a A) bool { - return L.AssertLaws(t, + fofc := WRT.Pointed[W, C](m) + fofaa := WRT.Pointed[W, func(A) A](m) + fofbc := WRT.Pointed[W, func(B) C](m) + fofabb := WRT.Pointed[W, func(func(A) B) B](m) + + fmap := WRT.Functor[W, func(B) C, func(func(A) B) func(A) C]() + + fapabb := WRT.Applicative[W, func(A) B, B](m) + fapabac := WRT.Applicative[W, func(A) B, func(A) C](m) + + maa := WRT.Monad[W, A, A](m) + mab := WRT.Monad[W, A, B](m) + mac := WRT.Monad[W, A, C](m) + mbc := WRT.Monad[W, B, C](m) + + return L.MonadAssertLaws(t, WRT.Eq(eqw, eqa), WRT.Eq(eqw, eqb), WRT.Eq(eqw, eqc), - WRT.Of[A](m), - WRT.Of[B](m), - WRT.Of[C](m), + fofc, + fofaa, + fofbc, + fofabb, - WRT.Of[func(A) A](m), - WRT.Of[func(A) B](m), - WRT.Of[func(B) C](m), - WRT.Of[func(func(A) B) B](m), + fmap, - WRT.MonadMap[func(A) A, W, A, A], - WRT.MonadMap[func(A) B, W, A, B], - WRT.MonadMap[func(A) C, W, A, C], - WRT.MonadMap[func(B) C, W, B, C], + fapabb, + fapabac, - WRT.MonadMap[func(func(B) C) func(func(A) B) func(A) C, W, func(B) C, func(func(A) B) func(A) C], - - WRT.MonadChain[func(A) WRT.Writer[W, A], W, A, A], - WRT.MonadChain[func(A) WRT.Writer[W, B], W, A, B], - WRT.MonadChain[func(A) WRT.Writer[W, C], W, A, C], - WRT.MonadChain[func(B) WRT.Writer[W, C], W, B, C], - - WRT.MonadAp[A, A, W], - WRT.MonadAp[B, A, W], - WRT.MonadAp[C, B, W], - WRT.MonadAp[C, A, W], - - WRT.MonadAp[B, func(A) B, W], - WRT.MonadAp[func(A) C, func(A) B, W], + maa, + mab, + mac, + mbc, ab, bc, diff --git a/writer/writer.go b/writer/writer.go index db64042..de2fad8 100644 --- a/writer/writer.go +++ b/writer/writer.go @@ -19,30 +19,30 @@ import ( EM "github.com/IBM/fp-go/endomorphism" IO "github.com/IBM/fp-go/io" M "github.com/IBM/fp-go/monoid" - S "github.com/IBM/fp-go/semigroup" - T "github.com/IBM/fp-go/tuple" + P "github.com/IBM/fp-go/pair" + SG "github.com/IBM/fp-go/semigroup" G "github.com/IBM/fp-go/writer/generic" ) -type Writer[W, A any] IO.IO[T.Tuple3[A, W, S.Semigroup[W]]] +type Writer[W, A any] IO.IO[P.Pair[A, W]] // Tell appends a value to the accumulator -func Tell[W any](s S.Semigroup[W]) func(W) Writer[W, any] { - return G.Tell[Writer[W, any]](s) +func Tell[W any](w W) Writer[W, any] { + return G.Tell[Writer[W, any]](w) } -func Of[A, W any](m M.Monoid[W]) func(A) Writer[W, A] { - return G.Of[Writer[W, A]](m) +func Of[A, W any](m M.Monoid[W], a A) Writer[W, A] { + return G.Of[Writer[W, A]](m, a) } // Listen modifies the result to include the changes to the accumulator -func Listen[W, A any](fa Writer[W, A]) Writer[W, T.Tuple2[A, W]] { - return G.Listen[Writer[W, A], Writer[W, T.Tuple2[A, W]], W, A](fa) +func Listen[W, A any](fa Writer[W, A]) Writer[W, P.Pair[A, W]] { + return G.Listen[Writer[W, A], Writer[W, P.Pair[A, W]], W, A](fa) } // Pass applies the returned function to the accumulator -func Pass[W, A any](fa Writer[W, T.Tuple2[A, EM.Endomorphism[W]]]) Writer[W, A] { - return G.Pass[Writer[W, T.Tuple2[A, EM.Endomorphism[W]]], Writer[W, A]](fa) +func Pass[W, A any](fa Writer[W, P.Pair[A, EM.Endomorphism[W]]]) Writer[W, A] { + return G.Pass[Writer[W, P.Pair[A, EM.Endomorphism[W]]], Writer[W, A]](fa) } func MonadMap[FCT ~func(A) B, W, A, B any](fa Writer[W, A], f FCT) Writer[W, B] { @@ -53,32 +53,32 @@ func Map[W any, FCT ~func(A) B, A, B any](f FCT) func(Writer[W, A]) Writer[W, B] return G.Map[Writer[W, B], Writer[W, A]](f) } -func MonadChain[FCT ~func(A) Writer[W, B], W, A, B any](fa Writer[W, A], fct FCT) Writer[W, B] { - return G.MonadChain[Writer[W, B], Writer[W, A], FCT](fa, fct) +func MonadChain[FCT ~func(A) Writer[W, B], W, A, B any](s SG.Semigroup[W], fa Writer[W, A], fct FCT) Writer[W, B] { + return G.MonadChain[Writer[W, B], Writer[W, A], FCT](s, fa, fct) } -func Chain[A, B, W any](fa func(A) Writer[W, B]) func(Writer[W, A]) Writer[W, B] { - return G.Chain[Writer[W, B], Writer[W, A], func(A) Writer[W, B]](fa) +func Chain[A, B, W any](s SG.Semigroup[W], fa func(A) Writer[W, B]) func(Writer[W, A]) Writer[W, B] { + return G.Chain[Writer[W, B], Writer[W, A], func(A) Writer[W, B]](s, fa) } -func MonadAp[B, A, W any](fab Writer[W, func(A) B], fa Writer[W, A]) Writer[W, B] { - return G.MonadAp[Writer[W, B], Writer[W, func(A) B], Writer[W, A]](fab, fa) +func MonadAp[B, A, W any](s SG.Semigroup[W], fab Writer[W, func(A) B], fa Writer[W, A]) Writer[W, B] { + return G.MonadAp[Writer[W, B], Writer[W, func(A) B], Writer[W, A]](s, fab, fa) } -func Ap[B, A, W any](fa Writer[W, A]) func(Writer[W, func(A) B]) Writer[W, B] { - return G.Ap[Writer[W, B], Writer[W, func(A) B], Writer[W, A]](fa) +func Ap[B, A, W any](s SG.Semigroup[W], fa Writer[W, A]) func(Writer[W, func(A) B]) Writer[W, B] { + return G.Ap[Writer[W, B], Writer[W, func(A) B], Writer[W, A]](s, fa) } -func MonadChainFirst[FCT ~func(A) Writer[W, B], W, A, B any](fa Writer[W, A], fct FCT) Writer[W, A] { - return G.MonadChainFirst[Writer[W, B], Writer[W, A], FCT](fa, fct) +func MonadChainFirst[FCT ~func(A) Writer[W, B], W, A, B any](s SG.Semigroup[W], fa Writer[W, A], fct FCT) Writer[W, A] { + return G.MonadChainFirst[Writer[W, B], Writer[W, A], FCT](s, fa, fct) } -func ChainFirst[FCT ~func(A) Writer[W, B], W, A, B any](fct FCT) func(Writer[W, A]) Writer[W, A] { - return G.ChainFirst[Writer[W, B], Writer[W, A], FCT](fct) +func ChainFirst[FCT ~func(A) Writer[W, B], W, A, B any](s SG.Semigroup[W], fct FCT) func(Writer[W, A]) Writer[W, A] { + return G.ChainFirst[Writer[W, B], Writer[W, A], FCT](s, fct) } -func Flatten[W, A any](mma Writer[W, Writer[W, A]]) Writer[W, A] { - return G.Flatten[Writer[W, Writer[W, A]], Writer[W, A]](mma) +func Flatten[W, A any](s SG.Semigroup[W], mma Writer[W, Writer[W, A]]) Writer[W, A] { + return G.Flatten[Writer[W, Writer[W, A]], Writer[W, A]](s, mma) } // Execute extracts the accumulator @@ -102,13 +102,13 @@ func Censor[A any, FCT ~func(W) W, W any](f FCT) func(Writer[W, A]) Writer[W, A] } // MonadListens projects a value from modifications made to the accumulator during an action -func MonadListens[A any, FCT ~func(W) B, W, B any](fa Writer[W, A], f FCT) Writer[W, T.Tuple2[A, B]] { - return G.MonadListens[Writer[W, A], Writer[W, T.Tuple2[A, B]]](fa, f) +func MonadListens[A any, FCT ~func(W) B, W, B any](fa Writer[W, A], f FCT) Writer[W, P.Pair[A, B]] { + return G.MonadListens[Writer[W, A], Writer[W, P.Pair[A, B]]](fa, f) } // Listens projects a value from modifications made to the accumulator during an action -func Listens[A any, FCT ~func(W) B, W, B any](f FCT) func(Writer[W, A]) Writer[W, T.Tuple2[A, B]] { - return G.Listens[Writer[W, A], Writer[W, T.Tuple2[A, B]]](f) +func Listens[A any, FCT ~func(W) B, W, B any](f FCT) func(Writer[W, A]) Writer[W, P.Pair[A, B]] { + return G.Listens[Writer[W, A], Writer[W, P.Pair[A, B]]](f) } func MonadFlap[W, B, A any](fab Writer[W, func(A) B], a A) Writer[W, B] { diff --git a/writer/writer_test.go b/writer/writer_test.go index c584ddc..78fb5f5 100644 --- a/writer/writer_test.go +++ b/writer/writer_test.go @@ -20,24 +20,22 @@ import ( A "github.com/IBM/fp-go/array" F "github.com/IBM/fp-go/function" - S "github.com/IBM/fp-go/semigroup" - T "github.com/IBM/fp-go/tuple" + P "github.com/IBM/fp-go/pair" ) func doubleAndLog(data int) Writer[[]string, int] { - return func() T.Tuple3[int, []string, S.Semigroup[[]string]] { + return func() P.Pair[int, []string] { result := data * 2 - return T.MakeTuple3(result, A.Of(fmt.Sprintf("Doubled %d -> %d", data, result)), sg) + return P.MakePair(result, A.Of(fmt.Sprintf("Doubled %d -> %d", data, result))) } } func ExampleWriter_logging() { - res := F.Pipe4( - 10, - Of[int](monoid), - Chain(doubleAndLog), - Chain(doubleAndLog), + res := F.Pipe3( + Of[int](monoid, 10), + Chain(sg, doubleAndLog), + Chain(sg, doubleAndLog), Execute[[]string, int], )