From 1713de0c3eb803d624e36d020fe404c67fc4e2c3 Mon Sep 17 00:00:00 2001 From: "Dr. Carsten Leue" Date: Mon, 24 Jul 2023 16:43:07 +0200 Subject: [PATCH] fix: introduce stateless iterator Signed-off-by: Dr. Carsten Leue --- array/array.go | 13 +- array/generic/array.go | 22 +++ bounded/bounded.go | 64 +++++++++ bytes/bytes.go | 4 + context/readerioeither/cancel.go | 2 +- context/readerioeither/data/file.txt | 1 + context/readerioeither/eq.go | 2 +- context/readerioeither/file/file.go | 2 +- context/readerioeither/resource.go | 2 +- context/readerioeither/resource_test.go | 79 +++++++++++ context/readerioeither/type.go | 3 +- erasure/erasure.go | 51 +++++++ erasure/erasure_test.go | 39 +++++ internal/optiont/option.go | 80 +++++++++++ internal/utils/utils.go | 8 ++ io/generic/logging.go | 9 ++ io/logging.go | 6 + ioeither/ap.go | 8 +- ioeither/ioeither.go | 2 +- ioeither/ioeither_test.go | 4 +- iooption/array.go | 30 ++++ iooption/generic/iooption.go | 180 ++++++++++++++++++++++++ iooption/generic/retry.go | 41 ++++++ iooption/generic/sequence.go | 62 ++++++++ iooption/generic/traverse.go | 33 +++++ iooption/iooption.go | 128 +++++++++++++++++ iooption/iooption_test.go | 73 ++++++++++ iooption/retry.go | 30 ++++ iooption/sequence.go | 69 +++++++++ iterator/stateless/doc.go | 18 +++ iterator/stateless/generic/iterator.go | 169 ++++++++++++++++++++++ iterator/stateless/generic/monoid.go | 30 ++++ iterator/stateless/iterator.go | 84 +++++++++++ iterator/stateless/iterator_test.go | 60 ++++++++ iterator/stateless/monoid.go | 26 ++++ lambda/y.go | 36 +++++ lambda/y_test.go | 34 +++++ lazy/apply.go | 30 ++++ lazy/eq.go | 26 ++++ lazy/lazy.go | 139 ++++++++++++++++++ lazy/lazy_test.go | 73 ++++++++++ lazy/retry.go | 34 +++++ lazy/retry_test.go | 47 +++++++ lazy/sequence.go | 39 +++++ lazy/testing/laws.go | 74 ++++++++++ lazy/testing/laws_test.go | 47 +++++++ lazy/traverse.go | 50 +++++++ number/integer/ord.go | 26 ++++ ord/ord.go | 2 +- readerio/ap.go | 8 +- readerioeither/resource.go | 2 +- 51 files changed, 2076 insertions(+), 25 deletions(-) create mode 100644 bounded/bounded.go create mode 100644 context/readerioeither/data/file.txt create mode 100644 context/readerioeither/resource_test.go create mode 100644 erasure/erasure.go create mode 100644 erasure/erasure_test.go create mode 100644 internal/optiont/option.go create mode 100644 iooption/array.go create mode 100644 iooption/generic/iooption.go create mode 100644 iooption/generic/retry.go create mode 100644 iooption/generic/sequence.go create mode 100644 iooption/generic/traverse.go create mode 100644 iooption/iooption.go create mode 100644 iooption/iooption_test.go create mode 100644 iooption/retry.go create mode 100644 iooption/sequence.go create mode 100644 iterator/stateless/doc.go create mode 100644 iterator/stateless/generic/iterator.go create mode 100644 iterator/stateless/generic/monoid.go create mode 100644 iterator/stateless/iterator.go create mode 100644 iterator/stateless/iterator_test.go create mode 100644 iterator/stateless/monoid.go create mode 100644 lambda/y.go create mode 100644 lambda/y_test.go create mode 100644 lazy/apply.go create mode 100644 lazy/eq.go create mode 100644 lazy/lazy.go create mode 100644 lazy/lazy_test.go create mode 100644 lazy/retry.go create mode 100644 lazy/retry_test.go create mode 100644 lazy/sequence.go create mode 100644 lazy/testing/laws.go create mode 100644 lazy/testing/laws_test.go create mode 100644 lazy/traverse.go create mode 100644 number/integer/ord.go diff --git a/array/array.go b/array/array.go index c3eb6e3..b268403 100644 --- a/array/array.go +++ b/array/array.go @@ -144,7 +144,7 @@ func Append[A any](as []A, a A) []A { } func IsEmpty[A any](as []A) bool { - return array.IsEmpty(as) + return G.IsEmpty(as) } func IsNonEmpty[A any](as []A) bool { @@ -181,12 +181,11 @@ func Ap[B, A any](fa []A) func([]func(A) B) []B { } func Match[A, B any](onEmpty func() B, onNonEmpty func([]A) B) func([]A) B { - return func(as []A) B { - if IsEmpty(as) { - return onEmpty() - } - return onNonEmpty(as) - } + return G.Match[[]A](onEmpty, onNonEmpty) +} + +func MatchLeft[A, B any](onEmpty func() B, onNonEmpty func(A, []A) B) func([]A) B { + return G.MatchLeft[[]A](onEmpty, onNonEmpty) } func Tail[A any](as []A) O.Option[[]A] { diff --git a/array/generic/array.go b/array/generic/array.go index da2c168..bd57abe 100644 --- a/array/generic/array.go +++ b/array/generic/array.go @@ -158,3 +158,25 @@ func MonadAp[BS ~[]B, ABS ~[]func(A) B, AS ~[]A, B, A any](fab ABS, fa AS) BS { func Ap[BS ~[]B, ABS ~[]func(A) B, AS ~[]A, B, A any](fa AS) func(ABS) BS { return F.Bind2nd(MonadAp[BS, ABS, AS], fa) } + +func IsEmpty[AS ~[]A, A any](as AS) bool { + return array.IsEmpty(as) +} + +func Match[AS ~[]A, A, B any](onEmpty func() B, onNonEmpty func(AS) B) func(AS) B { + return func(as AS) B { + if IsEmpty(as) { + return onEmpty() + } + return onNonEmpty(as) + } +} + +func MatchLeft[AS ~[]A, A, B any](onEmpty func() B, onNonEmpty func(A, AS) B) func(AS) B { + return func(as AS) B { + if IsEmpty(as) { + return onEmpty() + } + return onNonEmpty(as[0], as[1:]) + } +} diff --git a/bounded/bounded.go b/bounded/bounded.go new file mode 100644 index 0000000..8b24be2 --- /dev/null +++ b/bounded/bounded.go @@ -0,0 +1,64 @@ +// 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 bounded + +import ( + O "github.com/IBM/fp-go/ord" +) + +type Bounded[T any] interface { + O.Ord[T] + Top() T + Bottom() T +} + +type bounded[T any] struct { + c func(x, y T) int + e func(x, y T) bool + t T + b T +} + +func (self bounded[T]) Equals(x, y T) bool { + return self.e(x, y) +} + +func (self bounded[T]) Compare(x, y T) int { + return self.c(x, y) +} + +func (self bounded[T]) Top() T { + return self.t +} + +func (self bounded[T]) Bottom() T { + return self.b +} + +// MakeBounded creates an instance of a bounded type +func MakeBounded[T any](o O.Ord[T], t, b T) Bounded[T] { + return bounded[T]{c: o.Compare, e: o.Equals, t: t, b: b} +} + +// Clamp returns a function that clamps against the bounds defined in the bounded type +func Clamp[T any](b Bounded[T]) func(T) T { + return O.Clamp[T](b)(b.Bottom(), b.Top()) +} + +// Reverse reverses the ordering and swaps the bounds +func Reverse[T any](b Bounded[T]) Bounded[T] { + return MakeBounded(O.Reverse[T](b), b.Bottom(), b.Top()) +} diff --git a/bytes/bytes.go b/bytes/bytes.go index 6b5deb1..a6a6b4a 100644 --- a/bytes/bytes.go +++ b/bytes/bytes.go @@ -18,3 +18,7 @@ package bytes func ToString(a []byte) string { return string(a) } + +func Size(as []byte) int { + return len(as) +} diff --git a/context/readerioeither/cancel.go b/context/readerioeither/cancel.go index 6f5b8b2..10ad588 100644 --- a/context/readerioeither/cancel.go +++ b/context/readerioeither/cancel.go @@ -19,7 +19,7 @@ import ( G "github.com/IBM/fp-go/context/readerioeither/generic" ) -// WithContext wraps an existing ReaderIOEither and performs a context check for cancellation before delegating +// WithContext wraps an existing [ReaderIOEither] and performs a context check for cancellation before delegating func WithContext[A any](ma ReaderIOEither[A]) ReaderIOEither[A] { return G.WithContext(ma) } diff --git a/context/readerioeither/data/file.txt b/context/readerioeither/data/file.txt new file mode 100644 index 0000000..f6a7bf5 --- /dev/null +++ b/context/readerioeither/data/file.txt @@ -0,0 +1 @@ +Carsten \ No newline at end of file diff --git a/context/readerioeither/eq.go b/context/readerioeither/eq.go index 3f99441..bda5a60 100644 --- a/context/readerioeither/eq.go +++ b/context/readerioeither/eq.go @@ -23,7 +23,7 @@ import ( EQ "github.com/IBM/fp-go/eq" ) -// Eq implements the equals predicate for values contained in the IOEither monad +// Eq implements the equals predicate for values contained in the [ReaderIOEither] monad func Eq[A any](eq EQ.Eq[ET.Either[error, A]]) func(context.Context) EQ.Eq[ReaderIOEither[A]] { return G.Eq[ReaderIOEither[A]](eq) } diff --git a/context/readerioeither/file/file.go b/context/readerioeither/file/file.go index e0b6bab..d54507a 100644 --- a/context/readerioeither/file/file.go +++ b/context/readerioeither/file/file.go @@ -48,7 +48,7 @@ func Close[C io.Closer](c C) RIOE.ReaderIOEither[any] { // ReadFile reads a file in the scope of a context func ReadFile(path string) RIOE.ReaderIOEither[[]byte] { - return RIOE.WithResource[*os.File, []byte](Open(path), Close[*os.File])(func(r *os.File) RIOE.ReaderIOEither[[]byte] { + return RIOE.WithResource[[]byte](Open(path), Close[*os.File])(func(r *os.File) RIOE.ReaderIOEither[[]byte] { return func(ctx context.Context) IOE.IOEither[error, []byte] { return IOE.MakeIO(func() ET.Either[error, []byte] { return file.ReadAll(ctx, r) diff --git a/context/readerioeither/resource.go b/context/readerioeither/resource.go index 7bf85fa..7d9c10b 100644 --- a/context/readerioeither/resource.go +++ b/context/readerioeither/resource.go @@ -20,7 +20,7 @@ import ( ) // WithResource constructs a function that creates a resource, then operates on it and then releases the resource -func WithResource[R, A, ANY any](onCreate ReaderIOEither[R], onRelease func(R) ReaderIOEither[ANY]) func(func(R) ReaderIOEither[A]) ReaderIOEither[A] { +func WithResource[A, R, ANY any](onCreate ReaderIOEither[R], onRelease func(R) ReaderIOEither[ANY]) func(func(R) ReaderIOEither[A]) ReaderIOEither[A] { // wraps the callback functions with a context check return G.WithResource[ReaderIOEither[A]](onCreate, onRelease) } diff --git a/context/readerioeither/resource_test.go b/context/readerioeither/resource_test.go new file mode 100644 index 0000000..9d5de7d --- /dev/null +++ b/context/readerioeither/resource_test.go @@ -0,0 +1,79 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package readerioeither + +import ( + "context" + "io" + "os" + + B "github.com/IBM/fp-go/bytes" + F "github.com/IBM/fp-go/function" + IO "github.com/IBM/fp-go/io" + IOE "github.com/IBM/fp-go/ioeither" +) + +var ( + openFile = F.Flow3( + IOE.Eitherize1(os.Open), + FromIOEither[*os.File], + ChainFirstIOK(F.Flow2( + (*os.File).Name, + IO.Logf[string]("Opened file [%s]"), + )), + ) +) + +func closeFile(f *os.File) ReaderIOEither[string] { + return F.Pipe1( + TryCatch(func(_ context.Context) func() (string, error) { + return func() (string, error) { + return f.Name(), f.Close() + } + }), + ChainFirstIOK(IO.Logf[string]("Closed file [%s]")), + ) +} + +func ExampleWithResource() { + + stringReader := WithResource[string](openFile("data/file.txt"), closeFile) + + rdr := stringReader(func(f *os.File) ReaderIOEither[string] { + return F.Pipe2( + TryCatch(func(_ context.Context) func() ([]byte, error) { + return func() ([]byte, error) { + return io.ReadAll(f) + } + }), + ChainFirstIOK(F.Flow2( + B.Size, + IO.Logf[int]("Read content of length [%d]"), + )), + Map(B.ToString), + ) + }) + + contentIOE := F.Pipe2( + context.Background(), + rdr, + IOE.ChainFirstIOK[error](IO.Printf[string]("Content: %s")), + ) + + contentIOE() + + // Output: Content: Carsten +} diff --git a/context/readerioeither/type.go b/context/readerioeither/type.go index 142cdfa..1c5b367 100644 --- a/context/readerioeither/type.go +++ b/context/readerioeither/type.go @@ -21,5 +21,6 @@ import ( RE "github.com/IBM/fp-go/readerioeither" ) -// ReaderIOEither is a specialization of the Reader monad for the typical golang scenario +// ReaderIOEither is a specialization of the [RE.ReaderIOEither] monad for the typical golang scenario in which the +// left value is an [error] and the context is a [context.Context] type ReaderIOEither[A any] RE.ReaderIOEither[context.Context, error, A] diff --git a/erasure/erasure.go b/erasure/erasure.go new file mode 100644 index 0000000..c217a81 --- /dev/null +++ b/erasure/erasure.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 erasure + +import ( + F "github.com/IBM/fp-go/function" +) + +// Erase converts a variable of type T to an any by returning a pointer to that variable +func Erase[T any](t T) any { + return &t +} + +// Unerase converts an erased variable back to its original value +func Unerase[T any](t any) T { + return *t.(*T) +} + +// Erase0 converts a type safe function into an erased function +func Erase0[T1 any](f func() T1) func() any { + return F.Nullary2(f, Erase[T1]) +} + +// Erase1 converts a type safe function into an erased function +func Erase1[T1, T2 any](f func(T1) T2) func(any) any { + return F.Flow3( + Unerase[T1], + f, + Erase[T2], + ) +} + +// Erase2 converts a type safe function into an erased function +func Erase2[T1, T2, T3 any](f func(T1, T2) T3) func(any, any) any { + return func(t1, t2 any) any { + return Erase(f(Unerase[T1](t1), Unerase[T2](t2))) + } +} diff --git a/erasure/erasure_test.go b/erasure/erasure_test.go new file mode 100644 index 0000000..b83986f --- /dev/null +++ b/erasure/erasure_test.go @@ -0,0 +1,39 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package erasure + +import ( + "fmt" + "strings" + "testing" + + E "github.com/IBM/fp-go/either" + F "github.com/IBM/fp-go/function" +) + +func TestEither(t *testing.T) { + + e1 := F.Pipe3( + E.Of[error](Erase("Carsten")), + E.Map[error](Erase1(strings.ToUpper)), + E.GetOrElse(func(e error) any { + return Erase("Error") + }), + Unerase[string], + ) + + fmt.Println(e1) +} diff --git a/internal/optiont/option.go b/internal/optiont/option.go new file mode 100644 index 0000000..b7b1936 --- /dev/null +++ b/internal/optiont/option.go @@ -0,0 +1,80 @@ +// 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 optiont + +import ( + F "github.com/IBM/fp-go/function" + "github.com/IBM/fp-go/internal/apply" + FC "github.com/IBM/fp-go/internal/functor" + O "github.com/IBM/fp-go/option" +) + +func Of[A, HKTA any](fof func(O.Option[A]) HKTA, a A) HKTA { + return F.Pipe2(a, O.Of[A], fof) +} + +func None[A, HKTA any](fof func(O.Option[A]) HKTA) HKTA { + return F.Pipe1(O.None[A](), fof) +} + +func OfF[A, HKTA, HKTEA any](fmap func(HKTA, func(A) O.Option[A]) HKTEA, fa HKTA) HKTEA { + return fmap(fa, O.Of[A]) +} + +func MonadMap[A, B, HKTFA, HKTFB any](fmap func(HKTFA, func(O.Option[A]) O.Option[B]) HKTFB, fa HKTFA, f func(A) B) HKTFB { + // HKTGA = Either[E, A] + // HKTGB = Either[E, B] + return FC.MonadMap(fmap, O.MonadMap[A, B], fa, f) +} + +func MonadChain[A, B, HKTFA, HKTFB any]( + fchain func(HKTFA, func(O.Option[A]) HKTFB) HKTFB, + fof func(O.Option[B]) HKTFB, + ma HKTFA, + f func(A) HKTFB) HKTFB { + // dispatch to the even more generic implementation + return fchain(ma, O.Fold(F.Nullary2(O.None[B], fof), f)) +} + +func MonadAp[A, B, HKTFAB, HKTFGAB, HKTFA, HKTFB any]( + fap func(HKTFGAB, HKTFA) HKTFB, + fmap func(HKTFAB, func(O.Option[func(A) B]) func(O.Option[A]) O.Option[B]) HKTFGAB, + fab HKTFAB, + fa HKTFA) HKTFB { + // HKTGA = O.Option[A] + // HKTGB = O.Option[B] + // HKTGAB = O.Option[func(a A) B] + return apply.MonadAp(fap, fmap, O.MonadAp[B, A], fab, fa) +} + +func MatchE[A, HKTEA, HKTB any](mchain func(HKTEA, func(O.Option[A]) HKTB) HKTB, onNone func() HKTB, onSome func(A) HKTB) func(HKTEA) HKTB { + return F.Bind2nd(mchain, O.Fold(onNone, onSome)) +} + +func FromOptionK[A, B, HKTB any]( + fof func(O.Option[B]) HKTB, + f func(A) O.Option[B]) func(A) HKTB { + return F.Flow2(f, fof) +} + +func MonadChainOptionK[A, B, HKTA, HKTB any]( + fchain func(HKTA, func(O.Option[A]) HKTB) HKTB, + fof func(O.Option[B]) HKTB, + ma HKTA, + f func(A) O.Option[B], +) HKTB { + return MonadChain(fchain, fof, ma, FromOptionK(fof, f)) +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 266b99c..9b8dd93 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -22,6 +22,14 @@ import ( var Upper = strings.ToUpper +func Inc(i int) int { + return i + 1 +} + +func Dec(i int) int { + return i - 1 +} + func Sum(left, right int) int { return left + right } diff --git a/io/generic/logging.go b/io/generic/logging.go index 1ad5825..d07eabc 100644 --- a/io/generic/logging.go +++ b/io/generic/logging.go @@ -16,6 +16,7 @@ package generic import ( + "fmt" "log" Logging "github.com/IBM/fp-go/logging" @@ -39,3 +40,11 @@ func Logf[GA ~func() any, A any](prefix string) func(A) GA { }) } } + +func Printf[GA ~func() any, A any](prefix string) func(A) GA { + return func(a A) GA { + return FromImpure[GA](func() { + fmt.Printf(prefix, a) + }) + } +} diff --git a/io/logging.go b/io/logging.go index 22830f1..d280eb2 100644 --- a/io/logging.go +++ b/io/logging.go @@ -31,3 +31,9 @@ func Logger[A any](loggers ...*log.Logger) func(string) func(A) IO[any] { func Logf[A any](prefix string) func(A) IO[any] { return G.Logf[IO[any], A](prefix) } + +// Printf constructs a printer function that can be used with ChainXXXIOK +// the string prefix contains the format string for the log value +func Printf[A any](prefix string) func(A) IO[any] { + return G.Printf[IO[any], A](prefix) +} diff --git a/ioeither/ap.go b/ioeither/ap.go index c587944..8cb043a 100644 --- a/ioeither/ap.go +++ b/ioeither/ap.go @@ -20,21 +20,21 @@ import ( ) // MonadApFirst combines two effectful actions, keeping only the result of the first. -func MonadApFirst[E, A, B any](first IOEither[E, A], second IOEither[E, B]) IOEither[E, A] { +func MonadApFirst[A, E, B any](first IOEither[E, A], second IOEither[E, B]) IOEither[E, A] { return G.MonadApFirst[IOEither[E, A], IOEither[E, B], IOEither[E, func(B) A]](first, second) } // ApFirst combines two effectful actions, keeping only the result of the first. -func ApFirst[E, A, B any](second IOEither[E, B]) func(IOEither[E, A]) IOEither[E, A] { +func ApFirst[A, E, B any](second IOEither[E, B]) func(IOEither[E, A]) IOEither[E, A] { return G.ApFirst[IOEither[E, A], IOEither[E, B], IOEither[E, func(B) A]](second) } // MonadApSecond combines two effectful actions, keeping only the result of the second. -func MonadApSecond[E, A, B any](first IOEither[E, A], second IOEither[E, B]) IOEither[E, B] { +func MonadApSecond[A, E, B any](first IOEither[E, A], second IOEither[E, B]) IOEither[E, B] { return G.MonadApSecond[IOEither[E, A], IOEither[E, B], IOEither[E, func(B) B]](first, second) } // ApSecond combines two effectful actions, keeping only the result of the second. -func ApSecond[E, A, B any](second IOEither[E, B]) func(IOEither[E, A]) IOEither[E, B] { +func ApSecond[A, E, B any](second IOEither[E, B]) func(IOEither[E, A]) IOEither[E, B] { return G.ApSecond[IOEither[E, A], IOEither[E, B], IOEither[E, func(B) B]](second) } diff --git a/ioeither/ioeither.go b/ioeither/ioeither.go index d42d531..2072f56 100644 --- a/ioeither/ioeither.go +++ b/ioeither/ioeither.go @@ -22,7 +22,7 @@ import ( O "github.com/IBM/fp-go/option" ) -// IO represents a synchronous computation that may fail +// IOEither represents a synchronous computation that may fail // refer to [https://andywhite.xyz/posts/2021-01-27-rte-foundations/#ioeitherlte-agt] for more details type IOEither[E, A any] I.IO[ET.Either[E, A]] diff --git a/ioeither/ioeither_test.go b/ioeither/ioeither_test.go index 5661706..e4b65d5 100644 --- a/ioeither/ioeither_test.go +++ b/ioeither/ioeither_test.go @@ -119,7 +119,7 @@ func TestApFirst(t *testing.T) { x := F.Pipe1( Of[error]("a"), - ApFirst[error, string](Of[error]("b")), + ApFirst[string](Of[error]("b")), ) assert.Equal(t, E.Of[error]("a"), x()) @@ -129,7 +129,7 @@ func TestApSecond(t *testing.T) { x := F.Pipe1( Of[error]("a"), - ApSecond[error, string](Of[error]("b")), + ApSecond[string](Of[error]("b")), ) assert.Equal(t, E.Of[error]("b"), x()) diff --git a/iooption/array.go b/iooption/array.go new file mode 100644 index 0000000..1b8b2d9 --- /dev/null +++ b/iooption/array.go @@ -0,0 +1,30 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package iooption + +import ( + G "github.com/IBM/fp-go/iooption/generic" +) + +// TraverseArray transforms an array +func TraverseArray[A, B any](f func(A) IOOption[B]) func([]A) IOOption[[]B] { + return G.TraverseArray[IOOption[B], IOOption[[]B], []A](f) +} + +// SequenceArray converts a homogeneous sequence of either into an either of sequence +func SequenceArray[A any](ma []IOOption[A]) IOOption[[]A] { + return G.SequenceArray[IOOption[A], IOOption[[]A], []IOOption[A], []A, A](ma) +} diff --git a/iooption/generic/iooption.go b/iooption/generic/iooption.go new file mode 100644 index 0000000..6388630 --- /dev/null +++ b/iooption/generic/iooption.go @@ -0,0 +1,180 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package generic + +import ( + "time" + + F "github.com/IBM/fp-go/function" + FI "github.com/IBM/fp-go/internal/fromio" + "github.com/IBM/fp-go/internal/optiont" + IO "github.com/IBM/fp-go/io/generic" + O "github.com/IBM/fp-go/option" +) + +// type IOOption[A any] = func() Option[A] + +func MakeIO[GA ~func() O.Option[A], A any](f GA) GA { + return f +} + +func Of[GA ~func() O.Option[A], A any](r A) GA { + return MakeIO(optiont.Of(IO.MonadOf[GA, O.Option[A]], r)) +} + +func Some[GA ~func() O.Option[A], A any](r A) GA { + return Of[GA](r) +} + +func None[GA ~func() O.Option[A], A any]() GA { + return MakeIO(optiont.None(IO.MonadOf[GA, O.Option[A]])) +} + +func MonadOf[GA ~func() O.Option[A], A any](r A) GA { + return Of[GA](r) +} + +func FromIO[GA ~func() O.Option[A], GR ~func() A, A any](mr GR) GA { + return MakeIO(optiont.OfF(IO.MonadMap[GR, GA, A, O.Option[A]], mr)) +} + +func FromOption[GA ~func() O.Option[A], A any](o O.Option[A]) GA { + return IO.Of[GA](o) +} + +func MonadMap[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](fa GA, f func(A) B) GB { + return optiont.MonadMap(IO.MonadMap[GA, GB, O.Option[A], O.Option[B]], fa, f) +} + +func Map[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](f func(A) B) func(GA) GB { + return F.Bind2nd(MonadMap[GA, GB, A, B], f) +} + +func MonadChain[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](fa GA, f func(A) GB) GB { + return optiont.MonadChain(IO.MonadChain[GA, GB, O.Option[A], O.Option[B]], IO.MonadOf[GB, O.Option[B]], fa, f) +} + +func Chain[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](f func(A) GB) func(GA) GB { + return F.Bind2nd(MonadChain[GA, GB, A, B], f) +} + +func MonadChainOptionK[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](ma GA, f func(A) O.Option[B]) GB { + return optiont.MonadChainOptionK( + IO.MonadChain[GA, GB, O.Option[A], O.Option[B]], + FromOption[GB, B], + ma, + f, + ) +} + +func ChainOptionK[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](f func(A) O.Option[B]) func(GA) GB { + return F.Bind2nd(MonadChainOptionK[GA, GB, A, B], f) +} + +func MonadChainIOK[GA ~func() O.Option[A], GB ~func() O.Option[B], GR ~func() B, A, B any](ma GA, f func(A) GR) GB { + return FI.MonadChainIOK( + MonadChain[GA, GB, A, B], + FromIO[GB, GR, B], + ma, + f, + ) +} + +func ChainIOK[GA ~func() O.Option[A], GB ~func() O.Option[B], GR ~func() B, A, B any](f func(A) GR) func(GA) GB { + return FI.ChainIOK( + MonadChain[GA, GB, A, B], + FromIO[GB, GR, B], + f, + ) +} + +func MonadAp[GA ~func() O.Option[A], GB ~func() O.Option[B], GAB ~func() O.Option[func(A) B], A, B any](mab GAB, ma GA) GB { + return optiont.MonadAp( + IO.MonadAp[GA, GB, func() func(O.Option[A]) O.Option[B], O.Option[A], O.Option[B]], + IO.MonadMap[GAB, func() func(O.Option[A]) O.Option[B], O.Option[func(A) B], func(O.Option[A]) O.Option[B]], + mab, ma) +} + +func Ap[GA ~func() O.Option[A], GB ~func() O.Option[B], GAB ~func() O.Option[func(A) B], A, B any](ma GA) func(GAB) GB { + return F.Bind2nd(MonadAp[GA, GB, GAB, A, B], ma) +} + +func Flatten[GA ~func() O.Option[A], GAA ~func() O.Option[GA], A any](mma GAA) GA { + return MonadChain(mma, F.Identity[GA]) +} + +func Optionize0[GA ~func() O.Option[A], A any](f func() (A, bool)) func() GA { + ef := O.Optionize0(f) + return func() GA { + return MakeIO[GA](ef) + } +} + +func Optionize1[GA ~func() O.Option[A], T1, A any](f func(t1 T1) (A, bool)) func(T1) GA { + ef := O.Optionize1(f) + return func(t1 T1) GA { + return MakeIO[GA](func() O.Option[A] { + return ef(t1) + }) + } +} + +func Optionize2[GA ~func() O.Option[A], T1, T2, A any](f func(t1 T1, t2 T2) (A, bool)) func(T1, T2) GA { + ef := O.Optionize2(f) + return func(t1 T1, t2 T2) GA { + return MakeIO[GA](func() O.Option[A] { + return ef(t1, t2) + }) + } +} + +func Optionize3[GA ~func() O.Option[A], T1, T2, T3, A any](f func(t1 T1, t2 T2, t3 T3) (A, bool)) func(T1, T2, T3) GA { + ef := O.Optionize3(f) + return func(t1 T1, t2 T2, t3 T3) GA { + return MakeIO[GA](func() O.Option[A] { + return ef(t1, t2, t3) + }) + } +} + +func Optionize4[GA ~func() O.Option[A], T1, T2, T3, T4, A any](f func(t1 T1, t2 T2, t3 T3, t4 T4) (A, bool)) func(T1, T2, T3, T4) GA { + ef := O.Optionize4(f) + return func(t1 T1, t2 T2, t3 T3, t4 T4) GA { + return MakeIO[GA](func() O.Option[A] { + return ef(t1, t2, t3, t4) + }) + } +} + +// Memoize computes the value of the provided IO monad lazily but exactly once +func Memoize[GA ~func() O.Option[A], A any](ma GA) GA { + return IO.Memoize(ma) +} + +// Delay creates an operation that passes in the value after some delay +func Delay[GA ~func() O.Option[A], A any](delay time.Duration) func(GA) GA { + return IO.Delay[GA](delay) +} + +// Fold convers an IOOption into an IO +func Fold[GA ~func() O.Option[A], GB ~func() B, A, B any](onNone func() GB, onSome func(A) GB) func(GA) GB { + return optiont.MatchE(IO.MonadChain[GA, GB, O.Option[A], B], onNone, onSome) +} + +// Defer creates an IO by creating a brand new IO via a generator function, each time +func Defer[GA ~func() O.Option[A], A any](gen func() GA) GA { + return IO.Defer[GA](gen) +} diff --git a/iooption/generic/retry.go b/iooption/generic/retry.go new file mode 100644 index 0000000..f8c5679 --- /dev/null +++ b/iooption/generic/retry.go @@ -0,0 +1,41 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package generic + +import ( + O "github.com/IBM/fp-go/option" + R "github.com/IBM/fp-go/retry" + G "github.com/IBM/fp-go/retry/generic" +) + +// Retry combinator for actions that don't raise exceptions, but +// signal in their type the outcome has failed. Examples are the +// `Option`, `Either` and `EitherT` monads. +func Retrying[GA ~func() O.Option[A], A any]( + policy R.RetryPolicy, + action func(R.RetryStatus) GA, + check func(A) bool, +) GA { + // get an implementation for the types + return G.Retrying( + Chain[GA, GA, A, A], + Chain[func() O.Option[R.RetryStatus], GA, R.RetryStatus, A], + Of[GA, A], + Of[func() O.Option[R.RetryStatus], R.RetryStatus], + Delay[func() O.Option[R.RetryStatus], R.RetryStatus], + + policy, action, check) +} diff --git a/iooption/generic/sequence.go b/iooption/generic/sequence.go new file mode 100644 index 0000000..468ad2d --- /dev/null +++ b/iooption/generic/sequence.go @@ -0,0 +1,62 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package generic + +import ( + "github.com/IBM/fp-go/internal/apply" + O "github.com/IBM/fp-go/option" + 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() O.Option[A], GTA ~func() O.Option[T.Tuple1[A]], A any](a GA) GTA { + return apply.SequenceT1( + Map[GA, GTA, A, T.Tuple1[A]], + + a, + ) +} + +func SequenceT2[GA ~func() O.Option[A], GB ~func() O.Option[B], GTAB ~func() O.Option[T.Tuple2[A, B]], A, B any](a GA, b GB) GTAB { + return apply.SequenceT2( + Map[GA, func() O.Option[func(B) T.Tuple2[A, B]], A, func(B) T.Tuple2[A, B]], + Ap[GB, GTAB, func() O.Option[func(B) T.Tuple2[A, B]], B, T.Tuple2[A, B]], + + a, b, + ) +} + +func SequenceT3[GA ~func() O.Option[A], GB ~func() O.Option[B], GC ~func() O.Option[C], GTABC ~func() O.Option[T.Tuple3[A, B, C]], A, B, C any](a GA, b GB, c GC) GTABC { + return apply.SequenceT3( + Map[GA, func() O.Option[func(B) func(C) T.Tuple3[A, B, C]], A, func(B) func(C) T.Tuple3[A, B, C]], + Ap[GB, func() O.Option[func(C) T.Tuple3[A, B, C]], func() O.Option[func(B) func(C) T.Tuple3[A, B, C]], B, func(C) T.Tuple3[A, B, C]], + Ap[GC, GTABC, func() O.Option[func(C) T.Tuple3[A, B, C]], C, T.Tuple3[A, B, C]], + + a, b, c, + ) +} + +func SequenceT4[GA ~func() O.Option[A], GB ~func() O.Option[B], GC ~func() O.Option[C], GD ~func() O.Option[D], GTABCD ~func() O.Option[T.Tuple4[A, B, C, D]], A, B, C, D any](a GA, b GB, c GC, d GD) GTABCD { + return apply.SequenceT4( + Map[GA, func() O.Option[func(B) func(C) func(D) T.Tuple4[A, B, C, D]], A, func(B) func(C) func(D) T.Tuple4[A, B, C, D]], + Ap[GB, func() O.Option[func(C) func(D) T.Tuple4[A, B, C, D]], func() O.Option[func(B) func(C) func(D) T.Tuple4[A, B, C, D]], B, func(C) func(D) T.Tuple4[A, B, C, D]], + Ap[GC, func() O.Option[func(D) T.Tuple4[A, B, C, D]], func() O.Option[func(C) func(D) T.Tuple4[A, B, C, D]], C, func(D) T.Tuple4[A, B, C, D]], + Ap[GD, GTABCD, func() O.Option[func(D) T.Tuple4[A, B, C, D]], D, T.Tuple4[A, B, C, D]], + + a, b, c, d, + ) +} diff --git a/iooption/generic/traverse.go b/iooption/generic/traverse.go new file mode 100644 index 0000000..9c51fdf --- /dev/null +++ b/iooption/generic/traverse.go @@ -0,0 +1,33 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package generic + +import ( + F "github.com/IBM/fp-go/function" + I "github.com/IBM/fp-go/io/generic" + O "github.com/IBM/fp-go/option" +) + +func TraverseArray[TB ~func() O.Option[B], TBS ~func() O.Option[GB], GA ~[]A, GB ~[]B, A, B any](f func(A) TB) func(GA) TBS { + return F.Flow2( + I.TraverseArray[TB, func() []O.Option[B], GA](f), + I.Map[func() []O.Option[B], TBS](O.SequenceArrayG[GB, []O.Option[B], B]), + ) +} + +func SequenceArray[TB ~func() O.Option[B], TBS ~func() O.Option[GB], GA ~[]TB, GB ~[]B, A, B any](ma GA) TBS { + return TraverseArray[TB, TBS, GA](F.Identity[TB])(ma) +} diff --git a/iooption/iooption.go b/iooption/iooption.go new file mode 100644 index 0000000..d3eb40a --- /dev/null +++ b/iooption/iooption.go @@ -0,0 +1,128 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package iooption + +import ( + I "github.com/IBM/fp-go/io" + G "github.com/IBM/fp-go/iooption/generic" + O "github.com/IBM/fp-go/option" +) + +// IO represents a synchronous computation that may fail +// refer to [https://andywhite.xyz/posts/2021-01-27-rte-foundations/#ioeitherlte-agt] for more details +type IOOption[A any] I.IO[O.Option[A]] + +func MakeIO[A any](f IOOption[A]) IOOption[A] { + return G.MakeIO(f) +} + +func Of[A any](r A) IOOption[A] { + return G.Of[IOOption[A]](r) +} + +func Some[A any](r A) IOOption[A] { + return G.Some[IOOption[A]](r) +} + +func None[A any]() IOOption[A] { + return G.None[IOOption[A]]() +} + +func MonadOf[A any](r A) IOOption[A] { + return G.MonadOf[IOOption[A]](r) +} + +func FromOption[A any](o O.Option[A]) IOOption[A] { + return G.FromOption[IOOption[A]](o) +} + +func ChainOptionK[A, B any](f func(A) O.Option[B]) func(IOOption[A]) IOOption[B] { + return G.ChainOptionK[IOOption[A], IOOption[B]](f) +} + +func MonadChainIOK[A, B any](ma IOOption[A], f func(A) I.IO[B]) IOOption[B] { + return G.MonadChainIOK[IOOption[A], IOOption[B]](ma, f) +} + +func ChainIOK[A, B any](f func(A) I.IO[B]) func(IOOption[A]) IOOption[B] { + return G.ChainIOK[IOOption[A], IOOption[B]](f) +} + +func FromIO[A any](mr I.IO[A]) IOOption[A] { + return G.FromIO[IOOption[A]](mr) +} + +func MonadMap[A, B any](fa IOOption[A], f func(A) B) IOOption[B] { + return G.MonadMap[IOOption[A], IOOption[B]](fa, f) +} + +func Map[A, B any](f func(A) B) func(IOOption[A]) IOOption[B] { + return G.Map[IOOption[A], IOOption[B]](f) +} + +func MonadChain[A, B any](fa IOOption[A], f func(A) IOOption[B]) IOOption[B] { + return G.MonadChain(fa, f) +} + +func Chain[A, B any](f func(A) IOOption[B]) func(IOOption[A]) IOOption[B] { + return G.Chain[IOOption[A]](f) +} + +func MonadAp[B, A any](mab IOOption[func(A) B], ma IOOption[A]) IOOption[B] { + return G.MonadAp[IOOption[A], IOOption[B]](mab, ma) +} + +func Ap[B, A any](ma IOOption[A]) func(IOOption[func(A) B]) IOOption[B] { + return G.Ap[IOOption[A], IOOption[B], IOOption[func(A) B]](ma) +} + +func Flatten[A any](mma IOOption[IOOption[A]]) IOOption[A] { + return G.Flatten(mma) +} + +func Optionize0[A any](f func() (A, bool)) func() IOOption[A] { + return G.Optionize0[IOOption[A]](f) +} + +func Optionize1[T1, A any](f func(t1 T1) (A, bool)) func(T1) IOOption[A] { + return G.Optionize1[IOOption[A]](f) +} + +func Optionize2[T1, T2, A any](f func(t1 T1, t2 T2) (A, bool)) func(T1, T2) IOOption[A] { + return G.Optionize2[IOOption[A]](f) +} + +func Optionize3[T1, T2, T3, A any](f func(t1 T1, t2 T2, t3 T3) (A, bool)) func(T1, T2, T3) IOOption[A] { + return G.Optionize3[IOOption[A]](f) +} + +func Optionize4[T1, T2, T3, T4, A any](f func(t1 T1, t2 T2, t3 T3, t4 T4) (A, bool)) func(T1, T2, T3, T4) IOOption[A] { + return G.Optionize4[IOOption[A]](f) +} + +func Memoize[A any](ma IOOption[A]) IOOption[A] { + return G.Memoize(ma) +} + +// Fold convers an IOOption into an IO +func Fold[A, B any](onNone func() I.IO[B], onSome func(A) I.IO[B]) func(IOOption[A]) I.IO[B] { + return G.Fold[IOOption[A]](onNone, onSome) +} + +// Defer creates an IO by creating a brand new IO via a generator function, each time +func Defer[A any](gen func() IOOption[A]) IOOption[A] { + return G.Defer[IOOption[A]](gen) +} diff --git a/iooption/iooption_test.go b/iooption/iooption_test.go new file mode 100644 index 0000000..f23ee75 --- /dev/null +++ b/iooption/iooption_test.go @@ -0,0 +1,73 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package iooption + +import ( + "fmt" + "os" + "testing" + + F "github.com/IBM/fp-go/function" + "github.com/IBM/fp-go/internal/utils" + I "github.com/IBM/fp-go/io" + O "github.com/IBM/fp-go/option" + "github.com/stretchr/testify/assert" +) + +func TestMap(t *testing.T) { + assert.Equal(t, O.Of(2), F.Pipe1( + Of(1), + Map(utils.Double), + )()) + +} + +func TestChainOptionK(t *testing.T) { + f := ChainOptionK(func(n int) O.Option[int] { + if n > 0 { + return O.Of(n) + } + return O.None[int]() + + }) + assert.Equal(t, O.Of(1), f(Of(1))()) + assert.Equal(t, O.None[int](), f(Of(-1))()) + assert.Equal(t, O.None[int](), f(None[int]())()) +} + +func TestFromOption(t *testing.T) { + f := FromOption[int] + assert.Equal(t, O.Of(1), f(O.Some(1))()) + assert.Equal(t, O.None[int](), f(O.None[int]())()) +} + +func TestChainIOK(t *testing.T) { + f := ChainIOK(func(n int) I.IO[string] { + return I.MakeIO(func() string { + return fmt.Sprintf("%d", n) + }) + }) + + assert.Equal(t, O.Of("1"), f(Of(1))()) + assert.Equal(t, O.None[string](), f(None[int]())()) +} + +func TestEnv(t *testing.T) { + env := Optionize1(os.LookupEnv) + + assert.True(t, O.IsSome(env("PATH")())) + assert.False(t, O.IsSome(env("PATHxyz")())) +} diff --git a/iooption/retry.go b/iooption/retry.go new file mode 100644 index 0000000..99c7fdf --- /dev/null +++ b/iooption/retry.go @@ -0,0 +1,30 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package iooption + +import ( + G "github.com/IBM/fp-go/iooption/generic" + R "github.com/IBM/fp-go/retry" +) + +// Retrying will retry the actions according to the check policy +func Retrying[A any]( + policy R.RetryPolicy, + action func(R.RetryStatus) IOOption[A], + check func(A) bool, +) IOOption[A] { + return G.Retrying(policy, action, check) +} diff --git a/iooption/sequence.go b/iooption/sequence.go new file mode 100644 index 0000000..d2864cc --- /dev/null +++ b/iooption/sequence.go @@ -0,0 +1,69 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package iooption + +import ( + G "github.com/IBM/fp-go/iooption/generic" + 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[A any](a IOOption[A]) IOOption[T.Tuple1[A]] { + return G.SequenceT1[ + IOOption[A], + IOOption[T.Tuple1[A]], + ](a) +} + +func SequenceT2[A, B any]( + a IOOption[A], + b IOOption[B], +) IOOption[T.Tuple2[A, B]] { + return G.SequenceT2[ + IOOption[A], + IOOption[B], + IOOption[T.Tuple2[A, B]], + ](a, b) +} + +func SequenceT3[A, B, C any]( + a IOOption[A], + b IOOption[B], + c IOOption[C], +) IOOption[T.Tuple3[A, B, C]] { + return G.SequenceT3[ + IOOption[A], + IOOption[B], + IOOption[C], + IOOption[T.Tuple3[A, B, C]], + ](a, b, c) +} + +func SequenceT4[A, B, C, D any]( + a IOOption[A], + b IOOption[B], + c IOOption[C], + d IOOption[D], +) IOOption[T.Tuple4[A, B, C, D]] { + return G.SequenceT4[ + IOOption[A], + IOOption[B], + IOOption[C], + IOOption[D], + IOOption[T.Tuple4[A, B, C, D]], + ](a, b, c, d) +} diff --git a/iterator/stateless/doc.go b/iterator/stateless/doc.go new file mode 100644 index 0000000..2843b3d --- /dev/null +++ b/iterator/stateless/doc.go @@ -0,0 +1,18 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package stateless defines a stateless (pure) iterator, i.e. one that can be iterated over multiple times without +// side effects, it is threadsafe +package stateless diff --git a/iterator/stateless/generic/iterator.go b/iterator/stateless/generic/iterator.go new file mode 100644 index 0000000..cf4790b --- /dev/null +++ b/iterator/stateless/generic/iterator.go @@ -0,0 +1,169 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package generic + +import ( + A "github.com/IBM/fp-go/array/generic" + F "github.com/IBM/fp-go/function" + "github.com/IBM/fp-go/internal/utils" + IO "github.com/IBM/fp-go/iooption/generic" + N "github.com/IBM/fp-go/number/integer" + O "github.com/IBM/fp-go/option" + T "github.com/IBM/fp-go/tuple" +) + +// From constructs an array from a set of variadic arguments +func From[GU ~func() O.Option[T.Tuple2[GU, U]], U any](data ...U) GU { + return FromArray[GU](data) +} + +// Empty returns the empty iterator +func Empty[GU ~func() O.Option[T.Tuple2[GU, U]], U any]() GU { + return IO.None[GU]() +} + +// Of returns an iterator with one single element +func Of[GU ~func() O.Option[T.Tuple2[GU, U]], U any](a U) GU { + return IO.Of[GU](T.MakeTuple2(Empty[GU](), a)) +} + +// FromArray returns an iterator from multiple elements +func FromArray[GU ~func() O.Option[T.Tuple2[GU, U]], US ~[]U, U any](as US) GU { + return A.MatchLeft(Empty[GU], func(head U, tail US) GU { + return func() O.Option[T.Tuple2[GU, U]] { + return O.Of(T.MakeTuple2(FromArray[GU](tail), head)) + } + })(as) +} + +// Reduce applies a function for each value of the iterator with a floating result +func Reduce[GU ~func() O.Option[T.Tuple2[GU, U]], U, V any](f func(V, U) V, initial V) func(GU) V { + return func(as GU) V { + next, ok := O.Unwrap(as()) + current := initial + for ok { + // next (with bad side effect) + current = f(current, next.F2) + next, ok = O.Unwrap(next.F1()) + } + return current + } +} + +// ToArray converts the iterator to an array +func ToArray[GU ~func() O.Option[T.Tuple2[GU, U]], US ~[]U, U any](u GU) US { + return Reduce[GU](A.Append[US], A.Empty[US]())(u) +} + +func Map[GV ~func() O.Option[T.Tuple2[GV, V]], GU ~func() O.Option[T.Tuple2[GU, U]], U, V any](f func(U) V) func(ma GU) GV { + // pre-declare to avoid cyclic reference + var m func(O.Option[T.Tuple2[GU, U]]) O.Option[T.Tuple2[GV, V]] + + recurse := func(ma GU) GV { + return F.Nullary2( + ma, + m, + ) + } + m = O.Map(T.Map2(recurse, f)) + + return recurse +} + +func MonadMap[GV ~func() O.Option[T.Tuple2[GV, V]], GU ~func() O.Option[T.Tuple2[GU, U]], U, V any](ma GU, f func(U) V) GV { + return Map[GV, GU](f)(ma) +} + +func concat[GU ~func() O.Option[T.Tuple2[GU, U]], U any](right, left GU) GU { + var m func(ma O.Option[T.Tuple2[GU, U]]) O.Option[T.Tuple2[GU, U]] + + recurse := func(left GU) GU { + return F.Nullary2(left, m) + } + + m = O.Fold( + right, + F.Flow2( + T.Map2(recurse, F.Identity[U]), + O.Some[T.Tuple2[GU, U]], + )) + + return recurse(left) +} + +func Chain[GV ~func() O.Option[T.Tuple2[GV, V]], GU ~func() O.Option[T.Tuple2[GU, U]], U, V any](f func(U) GV) func(GU) GV { + // pre-declare to avoid cyclic reference + var m func(O.Option[T.Tuple2[GU, U]]) O.Option[T.Tuple2[GV, V]] + + recurse := func(ma GU) GV { + return F.Nullary2( + ma, + m, + ) + } + m = O.Chain( + F.Flow3( + T.Map2(recurse, f), + T.Tupled2(concat[GV]), + func(v GV) O.Option[T.Tuple2[GV, V]] { + return v() + }, + ), + ) + + return recurse +} + +func MonadChain[GV ~func() O.Option[T.Tuple2[GV, V]], GU ~func() O.Option[T.Tuple2[GU, U]], U, V any](ma GU, f func(U) GV) GV { + return Chain[GV, GU](f)(ma) +} + +func Flatten[GV ~func() O.Option[T.Tuple2[GV, GU]], GU ~func() O.Option[T.Tuple2[GU, U]], U any](ma GV) GU { + return MonadChain(ma, F.Identity[GU]) +} + +// MakeBy returns an [Iterator] with `n` elements initialized with `f(i)` +func MakeBy[GU ~func() O.Option[T.Tuple2[GU, U]], FCT ~func(int) U, U any](n int, f FCT) GU { + + var m func(int) O.Option[T.Tuple2[GU, U]] + + recurse := func(i int) GU { + return func() O.Option[T.Tuple2[GU, U]] { + return F.Pipe1( + i, + m, + ) + } + } + + m = F.Flow2( + O.FromPredicate(N.Between(0, n)), + O.Map(F.Flow2( + T.Replicate2[int], + T.Map2(F.Flow2( + utils.Inc, + recurse), + f), + )), + ) + + return recurse(0) +} + +// Replicate creates an [Iterator] containing a value repeated the specified number of times. +func Replicate[GU ~func() O.Option[T.Tuple2[GU, U]], U any](n int, a U) GU { + return MakeBy[GU](n, F.Constant1[int](a)) +} diff --git a/iterator/stateless/generic/monoid.go b/iterator/stateless/generic/monoid.go new file mode 100644 index 0000000..da68831 --- /dev/null +++ b/iterator/stateless/generic/monoid.go @@ -0,0 +1,30 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package generic + +import ( + F "github.com/IBM/fp-go/function" + M "github.com/IBM/fp-go/monoid" + O "github.com/IBM/fp-go/option" + T "github.com/IBM/fp-go/tuple" +) + +func Monoid[GU ~func() O.Option[T.Tuple2[GU, U]], U any]() M.Monoid[GU] { + return M.MakeMonoid( + F.Swap(concat[GU]), + Empty[GU](), + ) +} diff --git a/iterator/stateless/iterator.go b/iterator/stateless/iterator.go new file mode 100644 index 0000000..97b34da --- /dev/null +++ b/iterator/stateless/iterator.go @@ -0,0 +1,84 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package stateless + +import ( + G "github.com/IBM/fp-go/iterator/stateless/generic" + L "github.com/IBM/fp-go/lazy" + O "github.com/IBM/fp-go/option" + T "github.com/IBM/fp-go/tuple" +) + +// Iterator represents a stateless, pure way to iterate over a sequence +type Iterator[U any] L.Lazy[O.Option[T.Tuple2[Iterator[U], U]]] + +// Empty returns the empty iterator +func Empty[U any]() Iterator[U] { + return G.Empty[Iterator[U]]() +} + +// Of returns an iterator with one single element +func Of[GU ~func() O.Option[T.Tuple2[GU, U]], U any](a U) Iterator[U] { + return G.Of[Iterator[U]](a) +} + +// FromArray returns an iterator from multiple elements +func FromArray[U any](as []U) Iterator[U] { + return G.FromArray[Iterator[U]](as) +} + +// ToArray converts the iterator to an array +func ToArray[U any](u Iterator[U]) []U { + return G.ToArray[Iterator[U], []U](u) +} + +// Reduce applies a function for each value of the iterator with a floating result +func Reduce[U, V any](f func(V, U) V, initial V) func(Iterator[U]) V { + return G.Reduce[Iterator[U]](f, initial) +} + +// MonadMap transforms an [Iterator] of type [U] into an [Iterator] of type [V] via a mapping function +func MonadMap[U, V any](ma Iterator[U], f func(U) V) Iterator[V] { + return G.MonadMap[Iterator[V], Iterator[U]](ma, f) +} + +// Map transforms an [Iterator] of type [U] into an [Iterator] of type [V] via a mapping function +func Map[U, V any](f func(U) V) func(ma Iterator[U]) Iterator[V] { + return G.Map[Iterator[V], Iterator[U]](f) +} + +func MonadChain[U, V any](ma Iterator[U], f func(U) Iterator[V]) Iterator[V] { + return G.MonadChain[Iterator[V], Iterator[U]](ma, f) +} + +func Chain[U, V any](f func(U) Iterator[V]) func(Iterator[U]) Iterator[V] { + return G.Chain[Iterator[V], Iterator[U]](f) +} + +// Flatten converts an [Iterator] of [Iterator] into a simple [Iterator] +func Flatten[U any](ma Iterator[Iterator[U]]) Iterator[U] { + return G.Flatten[Iterator[Iterator[U]], Iterator[U]](ma) +} + +// MakeBy returns an [Iterator] with `n` elements initialized with `f(i)` +func MakeBy[FCT ~func(int) U, U any](n int, f FCT) Iterator[U] { + return G.MakeBy[Iterator[U]](n, f) +} + +// Replicate creates an [Iterator] containing a value repeated the specified number of times. +func Replicate[U any](n int, a U) Iterator[U] { + return G.Replicate[Iterator[U]](n, a) +} diff --git a/iterator/stateless/iterator_test.go b/iterator/stateless/iterator_test.go new file mode 100644 index 0000000..09c9854 --- /dev/null +++ b/iterator/stateless/iterator_test.go @@ -0,0 +1,60 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package stateless + +import ( + "fmt" + "testing" + + A "github.com/IBM/fp-go/array" + F "github.com/IBM/fp-go/function" + "github.com/IBM/fp-go/internal/utils" + "github.com/stretchr/testify/assert" +) + +func TestIterator(t *testing.T) { + + result := F.Pipe2( + A.From(1, 2, 3), + FromArray[int], + Reduce(utils.Sum, 0), + ) + + assert.Equal(t, 6, result) +} + +func TestChain(t *testing.T) { + + outer := FromArray[int](A.From(1, 2, 3)) + + inner := func(data int) Iterator[string] { + return F.Pipe2( + A.From(0, 1), + FromArray[int], + Map(func(idx int) string { + return fmt.Sprintf("item[%d][%d]", data, idx) + }), + ) + } + + total := F.Pipe2( + outer, + Chain(inner), + ToArray[string], + ) + + assert.Equal(t, A.From("item[1][0]", "item[1][1]", "item[2][0]", "item[2][1]", "item[3][0]", "item[3][1]"), total) +} diff --git a/iterator/stateless/monoid.go b/iterator/stateless/monoid.go new file mode 100644 index 0000000..2641813 --- /dev/null +++ b/iterator/stateless/monoid.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package stateless + +import ( + G "github.com/IBM/fp-go/iterator/stateless/generic" + M "github.com/IBM/fp-go/monoid" +) + +// Monoid contructs a [M.Monoid] that concatenates two [Iterator]s +func Monoid[U any]() M.Monoid[Iterator[U]] { + return G.Monoid[Iterator[U]]() +} diff --git a/lambda/y.go b/lambda/y.go new file mode 100644 index 0000000..9faa480 --- /dev/null +++ b/lambda/y.go @@ -0,0 +1,36 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package lambda + +type ( + // RecFct is the function called recursively + RecFct[T, R any] func(T) R + + // transformer + Transformer[T, R any] func(RecFct[T, R]) RecFct[T, R] + + internalCombinator[T, R any] func(internalCombinator[T, R]) RecFct[T, R] +) + +// Y is the Y-combinator based on https://dreamsongs.com/Files/WhyOfY.pdf +func Y[T, R any](f Transformer[T, R]) RecFct[T, R] { + g := func(h internalCombinator[T, R]) RecFct[T, R] { + return func(t T) R { + return f(h(h))(t) + } + } + return g(g) +} diff --git a/lambda/y_test.go b/lambda/y_test.go new file mode 100644 index 0000000..c4f4d56 --- /dev/null +++ b/lambda/y_test.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package lambda + +import ( + "fmt" + "testing" +) + +func TestFactorial(t *testing.T) { + fct := Y(func(r RecFct[int, int]) RecFct[int, int] { + return func(n int) int { + if n <= 0 { + return 1 + } + return n * r(n-1) + } + }) + + fmt.Println(fct(10)) +} diff --git a/lazy/apply.go b/lazy/apply.go new file mode 100644 index 0000000..146d2a7 --- /dev/null +++ b/lazy/apply.go @@ -0,0 +1,30 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package lazy + +import ( + G "github.com/IBM/fp-go/io/generic" + M "github.com/IBM/fp-go/monoid" + S "github.com/IBM/fp-go/semigroup" +) + +func ApplySemigroup[A any](s S.Semigroup[A]) S.Semigroup[Lazy[A]] { + return G.ApplySemigroup[Lazy[A]](s) +} + +func ApplicativeMonoid[A any](m M.Monoid[A]) M.Monoid[Lazy[A]] { + return G.ApplicativeMonoid[Lazy[A]](m) +} diff --git a/lazy/eq.go b/lazy/eq.go new file mode 100644 index 0000000..7f0cfea --- /dev/null +++ b/lazy/eq.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package lazy + +import ( + EQ "github.com/IBM/fp-go/eq" + G "github.com/IBM/fp-go/io/generic" +) + +// Eq implements the equals predicate for values contained in the IO monad +func Eq[A any](e EQ.Eq[A]) EQ.Eq[Lazy[A]] { + return G.Eq[Lazy[A]](e) +} diff --git a/lazy/lazy.go b/lazy/lazy.go new file mode 100644 index 0000000..8629cb1 --- /dev/null +++ b/lazy/lazy.go @@ -0,0 +1,139 @@ +// 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 lazy + +import ( + "time" + + G "github.com/IBM/fp-go/io/generic" +) + +// Lazy represents a synchronous computation without side effects +type Lazy[A any] func() A + +func MakeLazy[A any](f func() A) Lazy[A] { + return G.MakeIO[Lazy[A]](f) +} + +func Of[A any](a A) Lazy[A] { + return G.Of[Lazy[A]](a) +} + +func FromLazy[A any](a Lazy[A]) Lazy[A] { + return G.FromIO(a) +} + +// FromImpure converts a side effect without a return value into a side effect that returns any +func FromImpure(f func()) Lazy[any] { + return G.FromImpure[Lazy[any]](f) +} + +func MonadOf[A any](a A) Lazy[A] { + return G.MonadOf[Lazy[A]](a) +} + +func MonadMap[A, B any](fa Lazy[A], f func(A) B) Lazy[B] { + return G.MonadMap[Lazy[A], Lazy[B]](fa, f) +} + +func Map[A, B any](f func(A) B) func(fa Lazy[A]) Lazy[B] { + return G.Map[Lazy[A], Lazy[B]](f) +} + +func MonadMapTo[A, B any](fa Lazy[A], b B) Lazy[B] { + return G.MonadMapTo[Lazy[A], Lazy[B]](fa, b) +} + +func MapTo[A, B any](b B) func(Lazy[A]) Lazy[B] { + return G.MapTo[Lazy[A], Lazy[B]](b) +} + +// MonadChain composes computations in sequence, using the return value of one computation to determine the next computation. +func MonadChain[A, B any](fa Lazy[A], f func(A) Lazy[B]) Lazy[B] { + return G.MonadChain(fa, f) +} + +// Chain composes computations in sequence, using the return value of one computation to determine the next computation. +func Chain[A, B any](f func(A) Lazy[B]) func(Lazy[A]) Lazy[B] { + return G.Chain[Lazy[A]](f) +} + +func MonadAp[B, A any](mab Lazy[func(A) B], ma Lazy[A]) Lazy[B] { + return G.MonadAp[Lazy[A], Lazy[B]](mab, ma) +} + +func Ap[B, A any](ma Lazy[A]) func(Lazy[func(A) B]) Lazy[B] { + return G.Ap[Lazy[B], Lazy[func(A) B], Lazy[A]](ma) +} + +func Flatten[A any](mma Lazy[Lazy[A]]) Lazy[A] { + return G.Flatten(mma) +} + +// Memoize computes the value of the provided IO monad lazily but exactly once +func Memoize[A any](ma Lazy[A]) Lazy[A] { + return G.Memoize(ma) +} + +// MonadChainFirst composes computations in sequence, using the return value of one computation to determine the next computation and +// keeping only the result of the first. +func MonadChainFirst[A, B any](fa Lazy[A], f func(A) Lazy[B]) Lazy[A] { + return G.MonadChainFirst(fa, f) +} + +// ChainFirst composes computations in sequence, using the return value of one computation to determine the next computation and +// keeping only the result of the first. +func ChainFirst[A, B any](f func(A) Lazy[B]) func(Lazy[A]) Lazy[A] { + return G.ChainFirst[Lazy[A]](f) +} + +// MonadApFirst combines two effectful actions, keeping only the result of the first. +func MonadApFirst[A, B any](first Lazy[A], second Lazy[B]) Lazy[A] { + return G.MonadApFirst[Lazy[A], Lazy[B], Lazy[func(B) A]](first, second) +} + +// ApFirst combines two effectful actions, keeping only the result of the first. +func ApFirst[A, B any](second Lazy[B]) func(Lazy[A]) Lazy[A] { + return G.ApFirst[Lazy[A], Lazy[B], Lazy[func(B) A]](second) +} + +// MonadApSecond combines two effectful actions, keeping only the result of the second. +func MonadApSecond[A, B any](first Lazy[A], second Lazy[B]) Lazy[B] { + return G.MonadApSecond[Lazy[A], Lazy[B], Lazy[func(B) B]](first, second) +} + +// ApSecond combines two effectful actions, keeping only the result of the second. +func ApSecond[A, B any](second Lazy[B]) func(Lazy[A]) Lazy[B] { + return G.ApSecond[Lazy[A], Lazy[B], Lazy[func(B) B]](second) +} + +// MonadChainTo composes computations in sequence, ignoring the return value of the first computation +func MonadChainTo[A, B any](fa Lazy[A], fb Lazy[B]) Lazy[B] { + return G.MonadChainTo(fa, fb) +} + +// ChainTo composes computations in sequence, ignoring the return value of the first computation +func ChainTo[A, B any](fb Lazy[B]) func(Lazy[A]) Lazy[B] { + return G.ChainTo[Lazy[A]](fb) +} + +// Now returns the current timestamp +var Now = G.Now[Lazy[time.Time]]() + +// Defer creates an IO by creating a brand new IO via a generator function, each time +func Defer[A any](gen func() Lazy[A]) Lazy[A] { + return G.Defer[Lazy[A]](gen) +} diff --git a/lazy/lazy_test.go b/lazy/lazy_test.go new file mode 100644 index 0000000..287ab07 --- /dev/null +++ b/lazy/lazy_test.go @@ -0,0 +1,73 @@ +// 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 lazy + +import ( + "math/rand" + "testing" + + F "github.com/IBM/fp-go/function" + "github.com/IBM/fp-go/internal/utils" + "github.com/stretchr/testify/assert" +) + +func TestMap(t *testing.T) { + assert.Equal(t, 2, F.Pipe1(Of(1), Map(utils.Double))()) +} + +func TestChain(t *testing.T) { + f := func(n int) Lazy[int] { + return Of(n * 2) + } + assert.Equal(t, 2, F.Pipe1(Of(1), Chain(f))()) +} + +func TestAp(t *testing.T) { + assert.Equal(t, 2, F.Pipe1(Of(utils.Double), Ap[int, int](Of(1)))()) +} + +func TestFlatten(t *testing.T) { + assert.Equal(t, 1, F.Pipe1(Of(Of(1)), Flatten[int])()) +} + +func TestMemoize(t *testing.T) { + data := Memoize(MakeLazy(rand.Int)) + + value1 := data() + value2 := data() + + assert.Equal(t, value1, value2) +} + +func TestApFirst(t *testing.T) { + + x := F.Pipe1( + Of("a"), + ApFirst[string](Of("b")), + ) + + assert.Equal(t, "a", x()) +} + +func TestApSecond(t *testing.T) { + + x := F.Pipe1( + Of("a"), + ApSecond[string](Of("b")), + ) + + assert.Equal(t, "b", x()) +} diff --git a/lazy/retry.go b/lazy/retry.go new file mode 100644 index 0000000..2d833dd --- /dev/null +++ b/lazy/retry.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package lazy + +import ( + G "github.com/IBM/fp-go/io/generic" + R "github.com/IBM/fp-go/retry" +) + +// Retrying will retry the actions according to the check policy +// +// policy - refers to the retry policy +// action - converts a status into an operation to be executed +// check - checks if the result of the action needs to be retried +func Retrying[A any]( + policy R.RetryPolicy, + action func(R.RetryStatus) Lazy[A], + check func(A) bool, +) Lazy[A] { + return G.Retrying(policy, action, check) +} diff --git a/lazy/retry_test.go b/lazy/retry_test.go new file mode 100644 index 0000000..c465886 --- /dev/null +++ b/lazy/retry_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package lazy + +import ( + "fmt" + "strings" + "testing" + "time" + + R "github.com/IBM/fp-go/retry" + "github.com/stretchr/testify/assert" +) + +var expLogBackoff = R.ExponentialBackoff(10) + +// our retry policy with a 1s cap +var testLogPolicy = R.CapDelay( + 2*time.Second, + R.Monoid.Concat(expLogBackoff, R.LimitRetries(20)), +) + +func TestRetry(t *testing.T) { + action := func(status R.RetryStatus) Lazy[string] { + return Of(fmt.Sprintf("Retrying %d", status.IterNumber)) + } + check := func(value string) bool { + return !strings.Contains(value, "5") + } + + r := Retrying(testLogPolicy, action, check) + + assert.Equal(t, "Retrying 5", r()) +} diff --git a/lazy/sequence.go b/lazy/sequence.go new file mode 100644 index 0000000..4a5e92c --- /dev/null +++ b/lazy/sequence.go @@ -0,0 +1,39 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package lazy + +import ( + G "github.com/IBM/fp-go/io/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[A any](a Lazy[A]) Lazy[T.Tuple1[A]] { + return G.SequenceT1[Lazy[A], Lazy[T.Tuple1[A]]](a) +} + +func SequenceT2[A, B any](a Lazy[A], b Lazy[B]) Lazy[T.Tuple2[A, B]] { + return G.SequenceT2[Lazy[A], Lazy[B], Lazy[T.Tuple2[A, B]]](a, b) +} + +func SequenceT3[A, B, C any](a Lazy[A], b Lazy[B], c Lazy[C]) Lazy[T.Tuple3[A, B, C]] { + return G.SequenceT3[Lazy[A], Lazy[B], Lazy[C], Lazy[T.Tuple3[A, B, C]]](a, b, c) +} + +func SequenceT4[A, B, C, D any](a Lazy[A], b Lazy[B], c Lazy[C], d Lazy[D]) Lazy[T.Tuple4[A, B, C, D]] { + return G.SequenceT4[Lazy[A], Lazy[B], Lazy[C], Lazy[D], Lazy[T.Tuple4[A, B, C, D]]](a, b, c, d) +} diff --git a/lazy/testing/laws.go b/lazy/testing/laws.go new file mode 100644 index 0000000..7466da9 --- /dev/null +++ b/lazy/testing/laws.go @@ -0,0 +1,74 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testing + +import ( + "testing" + + EQ "github.com/IBM/fp-go/eq" + L "github.com/IBM/fp-go/internal/monad/testing" + "github.com/IBM/fp-go/lazy" +) + +// AssertLaws asserts the apply monad laws for the `Either` monad +func AssertLaws[A, B, C any](t *testing.T, + eqa EQ.Eq[A], + eqb EQ.Eq[B], + eqc EQ.Eq[C], + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + + return L.AssertLaws(t, + lazy.Eq(eqa), + lazy.Eq(eqb), + lazy.Eq(eqc), + + lazy.Of[A], + lazy.Of[B], + lazy.Of[C], + + lazy.Of[func(A) A], + lazy.Of[func(A) B], + lazy.Of[func(B) C], + lazy.Of[func(func(A) B) B], + + lazy.MonadMap[A, A], + lazy.MonadMap[A, B], + lazy.MonadMap[A, C], + lazy.MonadMap[B, C], + + lazy.MonadMap[func(B) C, func(func(A) B) func(A) C], + + lazy.MonadChain[A, A], + lazy.MonadChain[A, B], + lazy.MonadChain[A, C], + lazy.MonadChain[B, C], + + lazy.MonadAp[A, A], + lazy.MonadAp[B, A], + lazy.MonadAp[C, B], + lazy.MonadAp[C, A], + + lazy.MonadAp[B, func(A) B], + lazy.MonadAp[func(A) C, func(A) B], + + ab, + bc, + ) + +} diff --git a/lazy/testing/laws_test.go b/lazy/testing/laws_test.go new file mode 100644 index 0000000..23258c1 --- /dev/null +++ b/lazy/testing/laws_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testing + +import ( + "fmt" + "testing" + + EQ "github.com/IBM/fp-go/eq" + "github.com/stretchr/testify/assert" +) + +func TestMonadLaws(t *testing.T) { + // some comparison + eqa := EQ.FromStrictEquals[bool]() + eqb := EQ.FromStrictEquals[int]() + eqc := EQ.FromStrictEquals[string]() + + 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, eqa, eqb, eqc, ab, bc) + + assert.True(t, laws(true)) + assert.True(t, laws(false)) +} diff --git a/lazy/traverse.go b/lazy/traverse.go new file mode 100644 index 0000000..3e3ee0f --- /dev/null +++ b/lazy/traverse.go @@ -0,0 +1,50 @@ +// 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 lazy + +import ( + G "github.com/IBM/fp-go/io/generic" +) + +func MonadTraverseArray[A, B any](tas []A, f func(A) Lazy[B]) Lazy[[]B] { + return G.MonadTraverseArray[Lazy[B], Lazy[[]B]](tas, f) +} + +// TraverseArray applies a function returning an [IO] to all elements in an array and the +// transforms this into an [IO] of that array +func TraverseArray[A, B any](f func(A) Lazy[B]) func([]A) Lazy[[]B] { + return G.TraverseArray[Lazy[B], Lazy[[]B], []A](f) +} + +// SequenceArray converts an array of [IO] to an [IO] of an array +func SequenceArray[A any](tas []Lazy[A]) Lazy[[]A] { + return G.SequenceArray[Lazy[A], Lazy[[]A]](tas) +} + +func MonadTraverseRecord[K comparable, A, B any](tas map[K]A, f func(A) Lazy[B]) Lazy[map[K]B] { + return G.MonadTraverseRecord[Lazy[B], Lazy[map[K]B]](tas, f) +} + +// TraverseArray applies a function returning an [IO] to all elements in a record and the +// transforms this into an [IO] of that record +func TraverseRecord[K comparable, A, B any](f func(A) Lazy[B]) func(map[K]A) Lazy[map[K]B] { + return G.TraverseRecord[Lazy[B], Lazy[map[K]B], map[K]A](f) +} + +// SequenceRecord converts a record of [IO] to an [IO] of a record +func SequenceRecord[K comparable, A any](tas map[K]Lazy[A]) Lazy[map[K]A] { + return G.SequenceRecord[Lazy[A], Lazy[map[K]A]](tas) +} diff --git a/number/integer/ord.go b/number/integer/ord.go new file mode 100644 index 0000000..e9e0b49 --- /dev/null +++ b/number/integer/ord.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package integer + +import ( + O "github.com/IBM/fp-go/ord" +) + +// Ord is the strict ordering for integers +var Ord = O.FromStrictCompare[int]() + +// Between checks if an integer is between two values +var Between = O.Between[int](Ord) diff --git a/ord/ord.go b/ord/ord.go index 7d44890..af68488 100644 --- a/ord/ord.go +++ b/ord/ord.go @@ -170,7 +170,7 @@ func Geq[A any](O Ord[A]) func(A) func(A) bool { } } -// Test whether a value is between a minimum (inclusive) and a maximum (exclusive) +// Between tests whether a value is between a minimum (inclusive) and a maximum (exclusive) func Between[A any](O Ord[A]) func(A, A) func(A) bool { lt := Lt(O) geq := Geq(O) diff --git a/readerio/ap.go b/readerio/ap.go index 346d56c..471fb5e 100644 --- a/readerio/ap.go +++ b/readerio/ap.go @@ -20,21 +20,21 @@ import ( ) // MonadApFirst combines two effectful actions, keeping only the result of the first. -func MonadApFirst[R, A, B any](first ReaderIO[R, A], second ReaderIO[R, B]) ReaderIO[R, A] { +func MonadApFirst[A, R, B any](first ReaderIO[R, A], second ReaderIO[R, B]) ReaderIO[R, A] { return G.MonadApFirst[ReaderIO[R, A], ReaderIO[R, B], ReaderIO[R, func(B) A]](first, second) } // ApFirst combines two effectful actions, keeping only the result of the first. -func ApFirst[R, A, B any](second ReaderIO[R, B]) func(ReaderIO[R, A]) ReaderIO[R, A] { +func ApFirst[A, R, B any](second ReaderIO[R, B]) func(ReaderIO[R, A]) ReaderIO[R, A] { return G.ApFirst[ReaderIO[R, A], ReaderIO[R, B], ReaderIO[R, func(B) A]](second) } // MonadApSecond combines two effectful actions, keeping only the result of the second. -func MonadApSecond[R, A, B any](first ReaderIO[R, A], second ReaderIO[R, B]) ReaderIO[R, B] { +func MonadApSecond[A, R, B any](first ReaderIO[R, A], second ReaderIO[R, B]) ReaderIO[R, B] { return G.MonadApSecond[ReaderIO[R, A], ReaderIO[R, B], ReaderIO[R, func(B) B]](first, second) } // ApSecond combines two effectful actions, keeping only the result of the second. -func ApSecond[R, A, B any](second ReaderIO[R, B]) func(ReaderIO[R, A]) ReaderIO[R, B] { +func ApSecond[A, R, B any](second ReaderIO[R, B]) func(ReaderIO[R, A]) ReaderIO[R, B] { return G.ApSecond[ReaderIO[R, A], ReaderIO[R, B], ReaderIO[R, func(B) B]](second) } diff --git a/readerioeither/resource.go b/readerioeither/resource.go index 701909f..0a3623a 100644 --- a/readerioeither/resource.go +++ b/readerioeither/resource.go @@ -20,6 +20,6 @@ import ( ) // WithResource constructs a function that creates a resource, then operates on it and then releases the resource -func WithResource[L, E, R, A any](onCreate ReaderIOEither[L, E, R], onRelease func(R) ReaderIOEither[L, E, any]) func(func(R) ReaderIOEither[L, E, A]) ReaderIOEither[L, E, A] { +func WithResource[A, L, E, R any](onCreate ReaderIOEither[L, E, R], onRelease func(R) ReaderIOEither[L, E, any]) func(func(R) ReaderIOEither[L, E, A]) ReaderIOEither[L, E, A] { return G.WithResource[ReaderIOEither[L, E, A]](onCreate, onRelease) }