diff --git a/ioeither/ioeither.go b/ioeither/ioeither.go index a04fd9b..cfd76e6 100644 --- a/ioeither/ioeither.go +++ b/ioeither/ioeither.go @@ -75,10 +75,20 @@ func ChainIOK[E, A, B any](f func(A) I.IO[B]) func(IOEither[E, A]) IOEither[E, B return G.ChainIOK[IOEither[E, A], IOEither[E, B]](f) } +func ChainLazyK[E, A, B any](f func(A) L.Lazy[B]) func(IOEither[E, A]) IOEither[E, B] { + return G.ChainIOK[IOEither[E, A], IOEither[E, B]](f) +} + +// FromIO creates an [IOEither] from an [IO] instance, invoking [IO] for each invocation of [IOEither] func FromIO[E, A any](mr I.IO[A]) IOEither[E, A] { return G.FromIO[IOEither[E, A]](mr) } +// FromLazy creates an [IOEither] from a [Lazy] instance, invoking [Lazy] for each invocation of [IOEither] +func FromLazy[E, A any](mr L.Lazy[A]) IOEither[E, A] { + return G.FromIO[IOEither[E, A]](mr) +} + func MonadMap[E, A, B any](fa IOEither[E, A], f func(A) B) IOEither[E, B] { return G.MonadMap[IOEither[E, A], IOEither[E, B]](fa, f) } @@ -167,27 +177,27 @@ func MonadChainTo[E, A, B any](fa IOEither[E, A], fb IOEither[E, B]) IOEither[E, return G.MonadChainTo(fa, fb) } -// ChainTo composes to the second monad ignoring the return value of the first +// ChainTo composes to the second [IOEither] monad ignoring the return value of the first func ChainTo[E, A, B any](fb IOEither[E, B]) func(IOEither[E, A]) IOEither[E, B] { return G.ChainTo[IOEither[E, A]](fb) } -// MonadChainFirst runs the monad returned by the function but returns the result of the original monad +// MonadChainFirst runs the [IOEither] monad returned by the function but returns the result of the original monad func MonadChainFirst[E, A, B any](ma IOEither[E, A], f func(A) IOEither[E, B]) IOEither[E, A] { return G.MonadChainFirst(ma, f) } -// ChainFirst runs the monad returned by the function but returns the result of the original monad +// ChainFirst runs the [IOEither] monad returned by the function but returns the result of the original monad func ChainFirst[E, A, B any](f func(A) IOEither[E, B]) func(IOEither[E, A]) IOEither[E, A] { return G.ChainFirst[IOEither[E, A]](f) } -// MonadChainFirstIOK runs the monad returned by the function but returns the result of the original monad +// MonadChainFirstIOK runs [IO] the monad returned by the function but returns the result of the original monad func MonadChainFirstIOK[E, A, B any](ma IOEither[E, A], f func(A) I.IO[B]) IOEither[E, A] { return G.MonadChainFirstIOK(ma, f) } -// ChainFirsIOK runs the monad returned by the function but returns the result of the original monad +// ChainFirstIOK runs the [IO] monad returned by the function but returns the result of the original monad func ChainFirstIOK[E, A, B any](f func(A) I.IO[B]) func(IOEither[E, A]) IOEither[E, A] { return G.ChainFirstIOK[IOEither[E, A]](f) } diff --git a/iooption/generic/iooption.go b/iooption/generic/iooption.go index c43716e..6918d47 100644 --- a/iooption/generic/iooption.go +++ b/iooption/generic/iooption.go @@ -18,6 +18,7 @@ package generic import ( "time" + ET "github.com/IBM/fp-go/either" F "github.com/IBM/fp-go/function" FI "github.com/IBM/fp-go/internal/fromio" "github.com/IBM/fp-go/internal/optiont" @@ -55,6 +56,21 @@ func FromOption[GA ~func() O.Option[A], A any](o O.Option[A]) GA { return IO.Of[GA](o) } +func FromEither[GA ~func() O.Option[A], E, A any](e ET.Either[E, A]) GA { + return F.Pipe2( + e, + ET.ToOption[E, A], + FromOption[GA], + ) +} + +func FromIOEither[GA ~func() O.Option[A], GEA ~func() ET.Either[E, A], E, A any](ioe GEA) GA { + return F.Pipe1( + ioe, + IO.Map[GEA, GA](ET.ToOption[E, A]), + ) +} + func MonadMap[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](fa GA, f func(A) B) GB { return optiont.MonadMap(IO.MonadMap[GA, GB, O.Option[A], O.Option[B]], fa, f) } diff --git a/iooption/iooption.go b/iooption/iooption.go index 480fbac..fe5eb44 100644 --- a/iooption/iooption.go +++ b/iooption/iooption.go @@ -16,7 +16,9 @@ package iooption import ( + ET "github.com/IBM/fp-go/either" I "github.com/IBM/fp-go/io" + IOE "github.com/IBM/fp-go/ioeither" G "github.com/IBM/fp-go/iooption/generic" O "github.com/IBM/fp-go/option" ) @@ -117,7 +119,7 @@ func Memoize[A any](ma IOOption[A]) IOOption[A] { return G.Memoize(ma) } -// Fold convers an IOOption into an IO +// 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) } @@ -126,3 +128,13 @@ func Fold[A, B any](onNone func() I.IO[B], onSome func(A) I.IO[B]) func(IOOption func Defer[A any](gen func() IOOption[A]) IOOption[A] { return G.Defer[IOOption[A]](gen) } + +// FromIOEither converts an [IOEither] into an [IOOption] +func FromIOEither[E, A any](ioe IOE.IOEither[E, A]) IOOption[A] { + return G.FromIOEither[IOOption[A]](ioe) +} + +// FromEither converts an [Either] into an [IOOption] +func FromEither[E, A any](e ET.Either[E, A]) IOOption[A] { + return G.FromEither[IOOption[A]](e) +} diff --git a/samples/tuples/option_test.go b/samples/tuples/option_test.go new file mode 100644 index 0000000..d1ccd55 --- /dev/null +++ b/samples/tuples/option_test.go @@ -0,0 +1,59 @@ +// 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 tuples + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + F "github.com/IBM/fp-go/function" + IOE "github.com/IBM/fp-go/ioeither" + IOEF "github.com/IBM/fp-go/ioeither/file" + IOEG "github.com/IBM/fp-go/ioeither/generic" + IOO "github.com/IBM/fp-go/iooption" +) + +func TestIOEitherToOption1(t *testing.T) { + tmpDir := t.TempDir() + content := []byte("abc") + + resIOO := F.Pipe2( + content, + IOEF.WriteFile(filepath.Join(tmpDir, "test.txt"), os.ModePerm), + IOEG.Fold[IOE.IOEither[error, []byte]]( + IOO.Of[error], + F.Ignore1of1[[]byte](IOO.None[error]), + ), + ) + + fmt.Println(resIOO()) +} + +func TestIOEitherToOption2(t *testing.T) { + tmpDir := t.TempDir() + content := []byte("abc") + + resIOO := F.Pipe3( + content, + IOEF.WriteFile(filepath.Join(tmpDir, "test.txt"), os.ModePerm), + IOE.Swap[error, []byte], + IOO.FromIOEither[[]byte, error], + ) + + fmt.Println(resIOO()) +} diff --git a/samples/tuples/samples/data.txt b/samples/tuples/samples/data.txt new file mode 100644 index 0000000..f2ba8f8 --- /dev/null +++ b/samples/tuples/samples/data.txt @@ -0,0 +1 @@ +abc \ No newline at end of file diff --git a/samples/tuples/tuple_test.go b/samples/tuples/tuple_test.go new file mode 100644 index 0000000..b9b3b01 --- /dev/null +++ b/samples/tuples/tuple_test.go @@ -0,0 +1,130 @@ +// 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 tuples + +import ( + "bytes" + "io" + "strings" + "testing" + + E "github.com/IBM/fp-go/either" + F "github.com/IBM/fp-go/function" + IOE "github.com/IBM/fp-go/ioeither" + IOEF "github.com/IBM/fp-go/ioeither/file" + T "github.com/IBM/fp-go/tuple" + "github.com/stretchr/testify/assert" +) + +func sampleConvertDocx(r io.Reader) (string, map[string]string, error) { + content, err := io.ReadAll(r) + return string(content), map[string]string{}, err +} + +func TestSampleConvertDocx1(t *testing.T) { + // this conversion approach has the disadvantage that it exhausts the reader + // so we cannot invoke the resulting IOEither multiple times + convertDocx := func(r io.Reader) IOE.IOEither[error, T.Tuple2[string, map[string]string]] { + return IOE.TryCatchError(func() (T.Tuple2[string, map[string]string], error) { + text, meta, err := sampleConvertDocx(r) + return T.MakeTuple2(text, meta), err + }) + } + + rdr := strings.NewReader("abc") + resIOE := convertDocx(rdr) + + resE := resIOE() + + assert.True(t, E.IsRight(resE)) +} + +func TestSampleConvertDocx2(t *testing.T) { + // this approach assumes that `sampleConvertDocx` does not have any side effects + // other than reading from a `Reader`. As a consequence it can be a pure function itself. + // The disadvantage is that its input has to exist in memory which is probably not a good + // idea for large inputs + convertDocx := func(data []byte) E.Either[error, T.Tuple2[string, map[string]string]] { + return E.TryCatchError(func() (T.Tuple2[string, map[string]string], error) { + text, meta, err := sampleConvertDocx(bytes.NewReader(data)) + return T.MakeTuple2(text, meta), err + }) + } + + resE := convertDocx([]byte("abc")) + + assert.True(t, E.IsRight(resE)) +} + +// onClose closes a closeable resource +func onClose[R io.Closer](r R) IOE.IOEither[error, R] { + return IOE.TryCatchError(func() (R, error) { + return r, r.Close() + }) +} + +// convertDocx3 takes an `acquire` function that creates an instance or a [ReaderCloser] whenever the resulting [IOEither] is invoked. Since +// we return a [Closer] the instance will be closed after use, automatically. This design makes sure that the resulting [IOEither] can be invoked +// as many times as necessary +func convertDocx3[R io.ReadCloser](acquire IOE.IOEither[error, R]) IOE.IOEither[error, T.Tuple2[string, map[string]string]] { + return IOE.WithResource[T.Tuple2[string, map[string]string]]( + acquire, + onClose[R])( + func(r R) IOE.IOEither[error, T.Tuple2[string, map[string]string]] { + return IOE.TryCatchError(func() (T.Tuple2[string, map[string]string], error) { + text, meta, err := sampleConvertDocx(r) + return T.MakeTuple2(text, meta), err + }) + }, + ) +} + +// convertDocx4 takes an `acquire` function that creates an instance or a [Reader] whenever the resulting [IOEither] is invoked. +// This design makes sure that the resulting [IOEither] can be invoked +// as many times as necessary +func convertDocx4[R io.Reader](acquire IOE.IOEither[error, R]) IOE.IOEither[error, T.Tuple2[string, map[string]string]] { + return F.Pipe1( + acquire, + IOE.Chain(func(r R) IOE.IOEither[error, T.Tuple2[string, map[string]string]] { + return IOE.TryCatchError(func() (T.Tuple2[string, map[string]string], error) { + text, meta, err := sampleConvertDocx(r) + return T.MakeTuple2(text, meta), err + }) + }), + ) +} + +func TestSampleConvertDocx3(t *testing.T) { + // IOEither that creates the reader + acquire := IOEF.Open("./samples/data.txt") + + resIOE := convertDocx3(acquire) + resE := resIOE() + + assert.True(t, E.IsRight(resE)) +} + +func TestSampleConvertDocx4(t *testing.T) { + // IOEither that creates the reader + acquire := IOE.FromIO[error](func() *strings.Reader { + return strings.NewReader("abc") + }) + + resIOE := convertDocx4(acquire) + resE := resIOE() + + assert.True(t, E.IsRight(resE)) +}