mirror of
https://github.com/IBM/fp-go.git
synced 2025-08-10 22:31:32 +02:00
Merge pull request #49 from IBM/cleue-add-ioeither-sample-with-return-tuple
fix: add missing IOO.FromIOEither
This commit is contained in:
@@ -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)
|
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] {
|
func FromIO[E, A any](mr I.IO[A]) IOEither[E, A] {
|
||||||
return G.FromIO[IOEither[E, A]](mr)
|
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] {
|
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)
|
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)
|
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] {
|
func ChainTo[E, A, B any](fb IOEither[E, B]) func(IOEither[E, A]) IOEither[E, B] {
|
||||||
return G.ChainTo[IOEither[E, A]](fb)
|
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] {
|
func MonadChainFirst[E, A, B any](ma IOEither[E, A], f func(A) IOEither[E, B]) IOEither[E, A] {
|
||||||
return G.MonadChainFirst(ma, f)
|
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] {
|
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)
|
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] {
|
func MonadChainFirstIOK[E, A, B any](ma IOEither[E, A], f func(A) I.IO[B]) IOEither[E, A] {
|
||||||
return G.MonadChainFirstIOK(ma, f)
|
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] {
|
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)
|
return G.ChainFirstIOK[IOEither[E, A]](f)
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,7 @@ package generic
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
ET "github.com/IBM/fp-go/either"
|
||||||
F "github.com/IBM/fp-go/function"
|
F "github.com/IBM/fp-go/function"
|
||||||
FI "github.com/IBM/fp-go/internal/fromio"
|
FI "github.com/IBM/fp-go/internal/fromio"
|
||||||
"github.com/IBM/fp-go/internal/optiont"
|
"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)
|
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 {
|
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)
|
return optiont.MonadMap(IO.MonadMap[GA, GB, O.Option[A], O.Option[B]], fa, f)
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,9 @@
|
|||||||
package iooption
|
package iooption
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
ET "github.com/IBM/fp-go/either"
|
||||||
I "github.com/IBM/fp-go/io"
|
I "github.com/IBM/fp-go/io"
|
||||||
|
IOE "github.com/IBM/fp-go/ioeither"
|
||||||
G "github.com/IBM/fp-go/iooption/generic"
|
G "github.com/IBM/fp-go/iooption/generic"
|
||||||
O "github.com/IBM/fp-go/option"
|
O "github.com/IBM/fp-go/option"
|
||||||
)
|
)
|
||||||
@@ -117,7 +119,7 @@ func Memoize[A any](ma IOOption[A]) IOOption[A] {
|
|||||||
return G.Memoize(ma)
|
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] {
|
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)
|
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] {
|
func Defer[A any](gen func() IOOption[A]) IOOption[A] {
|
||||||
return G.Defer[IOOption[A]](gen)
|
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)
|
||||||
|
}
|
||||||
|
59
samples/tuples/option_test.go
Normal file
59
samples/tuples/option_test.go
Normal file
@@ -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())
|
||||||
|
}
|
1
samples/tuples/samples/data.txt
Normal file
1
samples/tuples/samples/data.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
abc
|
130
samples/tuples/tuple_test.go
Normal file
130
samples/tuples/tuple_test.go
Normal file
@@ -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))
|
||||||
|
}
|
Reference in New Issue
Block a user