mirror of
https://github.com/IBM/fp-go.git
synced 2025-08-28 19:49:07 +02:00
Compare commits
5 Commits
v1.0.34
...
cleue-add-
Author | SHA1 | Date | |
---|---|---|---|
|
e8fdbe9f87 | ||
|
226c7039de | ||
|
943ae8e009 | ||
|
44c8441b07 | ||
|
600aeae770 |
@@ -18,6 +18,7 @@ package readerio
|
||||
import (
|
||||
"context"
|
||||
|
||||
L "github.com/IBM/fp-go/lazy"
|
||||
R "github.com/IBM/fp-go/readerio/generic"
|
||||
)
|
||||
|
||||
@@ -54,6 +55,18 @@ func Ask() ReaderIO[context.Context] {
|
||||
}
|
||||
|
||||
// Defer creates an IO by creating a brand new IO via a generator function, each time
|
||||
func Defer[A any](gen func() ReaderIO[A]) ReaderIO[A] {
|
||||
func Defer[A any](gen L.Lazy[ReaderIO[A]]) ReaderIO[A] {
|
||||
return R.Defer[ReaderIO[A]](gen)
|
||||
}
|
||||
|
||||
// Memoize computes the value of the provided [ReaderIO] monad lazily but exactly once
|
||||
// The context used to compute the value is the context of the first call, so do not use this
|
||||
// method if the value has a functional dependency on the content of the context
|
||||
func Memoize[A any](rdr ReaderIO[A]) ReaderIO[A] {
|
||||
return R.Memoize[ReaderIO[A]](rdr)
|
||||
}
|
||||
|
||||
// Flatten converts a nested [ReaderIO] into a [ReaderIO]
|
||||
func Flatten[A any](mma ReaderIO[ReaderIO[A]]) ReaderIO[A] {
|
||||
return R.Flatten[ReaderIO[A]](mma)
|
||||
}
|
||||
|
@@ -29,10 +29,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
openIOE = IOE.Eitherize1(os.Open)
|
||||
// Open opens a file for reading within the given context
|
||||
Open = F.Flow3(
|
||||
openIOE,
|
||||
IOEF.Open,
|
||||
RIOE.FromIOEither[*os.File],
|
||||
RIOE.WithContext[*os.File],
|
||||
)
|
||||
@@ -46,11 +45,11 @@ var (
|
||||
|
||||
// Close closes an object
|
||||
func Close[C io.Closer](c C) RIOE.ReaderIOEither[any] {
|
||||
return RIOE.FromIOEither(func() ET.Either[error, any] {
|
||||
return ET.TryCatchError(func() (any, error) {
|
||||
return c, c.Close()
|
||||
})
|
||||
})
|
||||
return F.Pipe2(
|
||||
c,
|
||||
IOEF.Close[C],
|
||||
RIOE.FromIOEither[any],
|
||||
)
|
||||
}
|
||||
|
||||
// ReadFile reads a file in the scope of a context
|
||||
|
47
context/readerioeither/file/tempfile_test.go
Normal file
47
context/readerioeither/file/tempfile_test.go
Normal file
@@ -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 file
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
RIOE "github.com/IBM/fp-go/context/readerioeither"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWithTempFile(t *testing.T) {
|
||||
|
||||
res := WithTempFile(onWriteAll[*os.File]([]byte("Carsten")))
|
||||
|
||||
assert.Equal(t, E.Of[error]([]byte("Carsten")), res(context.Background())())
|
||||
}
|
||||
|
||||
func TestWithTempFileOnClosedFile(t *testing.T) {
|
||||
|
||||
res := WithTempFile(func(f *os.File) RIOE.ReaderIOEither[[]byte] {
|
||||
return F.Pipe2(
|
||||
f,
|
||||
onWriteAll[*os.File]([]byte("Carsten")),
|
||||
RIOE.ChainFirst(F.Constant1[[]byte](Close(f))),
|
||||
)
|
||||
})
|
||||
|
||||
assert.Equal(t, E.Of[error]([]byte("Carsten")), res(context.Background())())
|
||||
}
|
57
context/readerioeither/file/write.go
Normal file
57
context/readerioeither/file/write.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// 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 file
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
RIOE "github.com/IBM/fp-go/context/readerioeither"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
)
|
||||
|
||||
func onWriteAll[W io.Writer](data []byte) func(w W) RIOE.ReaderIOEither[[]byte] {
|
||||
return func(w W) RIOE.ReaderIOEither[[]byte] {
|
||||
return F.Pipe1(
|
||||
RIOE.TryCatch(func(ctx context.Context) func() ([]byte, error) {
|
||||
return func() ([]byte, error) {
|
||||
_, err := w.Write(data)
|
||||
return data, err
|
||||
}
|
||||
}),
|
||||
RIOE.WithContext[[]byte],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteAll uses a generator function to create a stream, writes data to it and closes it
|
||||
func WriteAll[W io.WriteCloser](data []byte) func(acquire RIOE.ReaderIOEither[W]) RIOE.ReaderIOEither[[]byte] {
|
||||
onWrite := onWriteAll[W](data)
|
||||
return func(onCreate RIOE.ReaderIOEither[W]) RIOE.ReaderIOEither[[]byte] {
|
||||
return RIOE.WithResource[[]byte](
|
||||
onCreate,
|
||||
Close[W])(
|
||||
onWrite,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Write uses a generator function to create a stream, writes data to it and closes it
|
||||
func Write[R any, W io.WriteCloser](acquire RIOE.ReaderIOEither[W]) func(use func(W) RIOE.ReaderIOEither[R]) RIOE.ReaderIOEither[R] {
|
||||
return RIOE.WithResource[R](
|
||||
acquire,
|
||||
Close[W])
|
||||
}
|
@@ -573,3 +573,22 @@ func MonadAlt[LAZY ~func() GEA, GEA ~func(context.Context) GIOA, GIOA ~func() E.
|
||||
func Alt[LAZY ~func() GEA, GEA ~func(context.Context) GIOA, GIOA ~func() E.Either[error, A], A any](second LAZY) func(GEA) GEA {
|
||||
return RIE.Alt(second)
|
||||
}
|
||||
|
||||
// Memoize computes the value of the provided monad lazily but exactly once
|
||||
// The context used to compute the value is the context of the first call, so do not use this
|
||||
// method if the value has a functional dependency on the content of the context
|
||||
func Memoize[
|
||||
GRA ~func(context.Context) GIOA,
|
||||
GIOA ~func() E.Either[error, A],
|
||||
A any](rdr GRA) GRA {
|
||||
return RIE.Memoize[GRA](rdr)
|
||||
}
|
||||
|
||||
func Flatten[
|
||||
GGRA ~func(context.Context) GGIOA,
|
||||
GGIOA ~func() E.Either[error, GRA],
|
||||
GRA ~func(context.Context) GIOA,
|
||||
GIOA ~func() E.Either[error, A],
|
||||
A any](rdr GGRA) GRA {
|
||||
return RIE.Flatten[GRA](rdr)
|
||||
}
|
||||
|
@@ -155,6 +155,10 @@ func FromIO[A any](t IO.IO[A]) ReaderIOEither[A] {
|
||||
return G.FromIO[ReaderIOEither[A]](t)
|
||||
}
|
||||
|
||||
func FromLazy[A any](t L.Lazy[A]) ReaderIOEither[A] {
|
||||
return G.FromIO[ReaderIOEither[A]](t)
|
||||
}
|
||||
|
||||
// Never returns a 'ReaderIOEither' that never returns, except if its context gets canceled
|
||||
func Never[A any]() ReaderIOEither[A] {
|
||||
return G.Never[ReaderIOEither[A]]()
|
||||
@@ -209,3 +213,16 @@ func MonadAlt[A any](first ReaderIOEither[A], second L.Lazy[ReaderIOEither[A]])
|
||||
func Alt[A any](second L.Lazy[ReaderIOEither[A]]) func(ReaderIOEither[A]) ReaderIOEither[A] {
|
||||
return G.Alt(second)
|
||||
}
|
||||
|
||||
// Memoize computes the value of the provided [ReaderIOEither] monad lazily but exactly once
|
||||
// The context used to compute the value is the context of the first call, so do not use this
|
||||
// method if the value has a functional dependency on the content of the context
|
||||
func Memoize[A any](rdr ReaderIOEither[A]) ReaderIOEither[A] {
|
||||
return G.Memoize[ReaderIOEither[A]](rdr)
|
||||
}
|
||||
|
||||
// Flatten converts a nested [ReaderIOEither] into a [ReaderIOEither]
|
||||
func Flatten[
|
||||
A any](rdr ReaderIOEither[ReaderIOEither[A]]) ReaderIOEither[A] {
|
||||
return G.Flatten[ReaderIOEither[ReaderIOEither[A]]](rdr)
|
||||
}
|
||||
|
@@ -162,3 +162,125 @@ func TestRegularApply(t *testing.T) {
|
||||
res := applied(context.Background())()
|
||||
assert.Equal(t, E.Of[error]("CARSTEN"), res)
|
||||
}
|
||||
|
||||
func TestWithResourceNoErrors(t *testing.T) {
|
||||
var countAcquire, countBody, countRelease int
|
||||
|
||||
acquire := FromLazy(func() int {
|
||||
countAcquire++
|
||||
return countAcquire
|
||||
})
|
||||
|
||||
release := func(int) ReaderIOEither[int] {
|
||||
return FromLazy(func() int {
|
||||
countRelease++
|
||||
return countRelease
|
||||
})
|
||||
}
|
||||
|
||||
body := func(int) ReaderIOEither[int] {
|
||||
return FromLazy(func() int {
|
||||
countBody++
|
||||
return countBody
|
||||
})
|
||||
}
|
||||
|
||||
resRIOE := WithResource[int](acquire, release)(body)
|
||||
|
||||
res := resRIOE(context.Background())()
|
||||
|
||||
assert.Equal(t, 1, countAcquire)
|
||||
assert.Equal(t, 1, countBody)
|
||||
assert.Equal(t, 1, countRelease)
|
||||
assert.Equal(t, E.Of[error](1), res)
|
||||
}
|
||||
|
||||
func TestWithResourceErrorInBody(t *testing.T) {
|
||||
var countAcquire, countBody, countRelease int
|
||||
|
||||
acquire := FromLazy(func() int {
|
||||
countAcquire++
|
||||
return countAcquire
|
||||
})
|
||||
|
||||
release := func(int) ReaderIOEither[int] {
|
||||
return FromLazy(func() int {
|
||||
countRelease++
|
||||
return countRelease
|
||||
})
|
||||
}
|
||||
|
||||
err := fmt.Errorf("error in body")
|
||||
body := func(int) ReaderIOEither[int] {
|
||||
return Left[int](err)
|
||||
}
|
||||
|
||||
resRIOE := WithResource[int](acquire, release)(body)
|
||||
|
||||
res := resRIOE(context.Background())()
|
||||
|
||||
assert.Equal(t, 1, countAcquire)
|
||||
assert.Equal(t, 0, countBody)
|
||||
assert.Equal(t, 1, countRelease)
|
||||
assert.Equal(t, E.Left[int](err), res)
|
||||
}
|
||||
|
||||
func TestWithResourceErrorInAcquire(t *testing.T) {
|
||||
var countAcquire, countBody, countRelease int
|
||||
|
||||
err := fmt.Errorf("error in acquire")
|
||||
acquire := Left[int](err)
|
||||
|
||||
release := func(int) ReaderIOEither[int] {
|
||||
return FromLazy(func() int {
|
||||
countRelease++
|
||||
return countRelease
|
||||
})
|
||||
}
|
||||
|
||||
body := func(int) ReaderIOEither[int] {
|
||||
return FromLazy(func() int {
|
||||
countBody++
|
||||
return countBody
|
||||
})
|
||||
}
|
||||
|
||||
resRIOE := WithResource[int](acquire, release)(body)
|
||||
|
||||
res := resRIOE(context.Background())()
|
||||
|
||||
assert.Equal(t, 0, countAcquire)
|
||||
assert.Equal(t, 0, countBody)
|
||||
assert.Equal(t, 0, countRelease)
|
||||
assert.Equal(t, E.Left[int](err), res)
|
||||
}
|
||||
|
||||
func TestWithResourceErrorInRelease(t *testing.T) {
|
||||
var countAcquire, countBody, countRelease int
|
||||
|
||||
acquire := FromLazy(func() int {
|
||||
countAcquire++
|
||||
return countAcquire
|
||||
})
|
||||
|
||||
err := fmt.Errorf("error in release")
|
||||
release := func(int) ReaderIOEither[int] {
|
||||
return Left[int](err)
|
||||
}
|
||||
|
||||
body := func(int) ReaderIOEither[int] {
|
||||
return FromLazy(func() int {
|
||||
countBody++
|
||||
return countBody
|
||||
})
|
||||
}
|
||||
|
||||
resRIOE := WithResource[int](acquire, release)(body)
|
||||
|
||||
res := resRIOE(context.Background())()
|
||||
|
||||
assert.Equal(t, 1, countAcquire)
|
||||
assert.Equal(t, 1, countBody)
|
||||
assert.Equal(t, 0, countRelease)
|
||||
assert.Equal(t, E.Left[int](err), res)
|
||||
}
|
||||
|
2
io/io.go
2
io/io.go
@@ -84,7 +84,7 @@ func Flatten[A any](mma IO[IO[A]]) IO[A] {
|
||||
return G.Flatten(mma)
|
||||
}
|
||||
|
||||
// Memoize computes the value of the provided IO monad lazily but exactly once
|
||||
// Memoize computes the value of the provided [IO] monad lazily but exactly once
|
||||
func Memoize[A any](ma IO[A]) IO[A] {
|
||||
return G.Memoize(ma)
|
||||
}
|
||||
|
@@ -1,29 +0,0 @@
|
||||
// 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 file
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
)
|
||||
|
||||
// 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()
|
||||
})
|
||||
}
|
@@ -16,6 +16,7 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
@@ -45,3 +46,10 @@ func Remove(name string) IOE.IOEither[error, string] {
|
||||
return name, os.Remove(name)
|
||||
})
|
||||
}
|
||||
|
||||
// Close closes an object
|
||||
func Close[C io.Closer](c C) IOE.IOEither[error, any] {
|
||||
return IOE.TryCatchError(func() (any, error) {
|
||||
return c, c.Close()
|
||||
})
|
||||
}
|
||||
|
@@ -31,7 +31,7 @@ func onReadAll[R io.Reader](r R) IOE.IOEither[error, []byte] {
|
||||
func ReadAll[R io.ReadCloser](acquire IOE.IOEither[error, R]) IOE.IOEither[error, []byte] {
|
||||
return IOE.WithResource[[]byte](
|
||||
acquire,
|
||||
onClose[R])(
|
||||
Close[R])(
|
||||
onReadAll[R],
|
||||
)
|
||||
}
|
||||
|
@@ -38,7 +38,7 @@ func TestWithTempFileOnClosedFile(t *testing.T) {
|
||||
return F.Pipe2(
|
||||
f,
|
||||
onWriteAll[*os.File]([]byte("Carsten")),
|
||||
IOE.ChainFirst(F.Constant1[[]byte](onClose(f))),
|
||||
IOE.ChainFirst(F.Constant1[[]byte](Close(f))),
|
||||
)
|
||||
})
|
||||
|
||||
|
@@ -36,7 +36,7 @@ func WriteAll[W io.WriteCloser](data []byte) func(acquire IOE.IOEither[error, W]
|
||||
return func(onCreate IOE.IOEither[error, W]) IOE.IOEither[error, []byte] {
|
||||
return IOE.WithResource[[]byte](
|
||||
onCreate,
|
||||
onClose[W])(
|
||||
Close[W])(
|
||||
onWrite,
|
||||
)
|
||||
}
|
||||
@@ -46,5 +46,5 @@ func WriteAll[W io.WriteCloser](data []byte) func(acquire IOE.IOEither[error, W]
|
||||
func Write[R any, W io.WriteCloser](acquire IOE.IOEither[error, W]) func(use func(W) IOE.IOEither[error, R]) IOE.IOEither[error, R] {
|
||||
return IOE.WithResource[R](
|
||||
acquire,
|
||||
onClose[W])
|
||||
Close[W])
|
||||
}
|
||||
|
@@ -16,6 +16,8 @@
|
||||
package generic
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
F "github.com/IBM/fp-go/function"
|
||||
FR "github.com/IBM/fp-go/internal/fromreader"
|
||||
"github.com/IBM/fp-go/internal/readert"
|
||||
@@ -99,3 +101,30 @@ func Defer[GEA ~func(E) GA, GA ~func() A, E, A any](gen func() GEA) GEA {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Memoize computes the value of the provided reader monad lazily but exactly once
|
||||
// The context used to compute the value is the context of the first call, so do not use this
|
||||
// method if the value has a functional dependency on the content of the context
|
||||
func Memoize[GEA ~func(E) GA, GA ~func() A, E, A any](rdr GEA) GEA {
|
||||
// synchronization primitives
|
||||
var once sync.Once
|
||||
var result A
|
||||
// callback
|
||||
gen := func(e E) func() {
|
||||
return func() {
|
||||
result = rdr(e)()
|
||||
}
|
||||
}
|
||||
// returns our memoized wrapper
|
||||
return func(e E) GA {
|
||||
io := gen(e)
|
||||
return func() A {
|
||||
once.Do(io)
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Flatten[GEA ~func(R) GIOA, GGEA ~func(R) GIOEA, GIOA ~func() A, GIOEA ~func() GEA, R, A any](mma GGEA) GEA {
|
||||
return MonadChain(mma, F.Identity[GEA])
|
||||
}
|
||||
|
@@ -75,3 +75,14 @@ func ChainIOK[E, A, B any](f func(A) IO.IO[B]) func(ReaderIO[E, A]) ReaderIO[E,
|
||||
func Defer[E, A any](gen func() ReaderIO[E, A]) ReaderIO[E, A] {
|
||||
return G.Defer[ReaderIO[E, A]](gen)
|
||||
}
|
||||
|
||||
// Memoize computes the value of the provided [ReaderIO] monad lazily but exactly once
|
||||
// The context used to compute the value is the context of the first call, so do not use this
|
||||
// method if the value has a functional dependency on the content of the context
|
||||
func Memoize[E, A any](rdr ReaderIO[E, A]) ReaderIO[E, A] {
|
||||
return G.Memoize[ReaderIO[E, A]](rdr)
|
||||
}
|
||||
|
||||
func Flatten[E, A any](mma ReaderIO[E, ReaderIO[E, A]]) ReaderIO[E, A] {
|
||||
return G.Flatten[ReaderIO[E, A], ReaderIO[E, ReaderIO[E, A]]](mma)
|
||||
}
|
||||
|
@@ -406,3 +406,11 @@ func TryCatch[GEA ~func(R) GA, GA ~func() ET.Either[E, A], R, E, A any](f func(R
|
||||
return IOE.TryCatch[GA](f(r), onThrow)
|
||||
}
|
||||
}
|
||||
|
||||
// Memoize computes the value of the provided monad lazily but exactly once
|
||||
// The context used to compute the value is the context of the first call, so do not use this
|
||||
// method if the value has a functional dependency on the content of the context
|
||||
func Memoize[
|
||||
GEA ~func(R) GIOA, GIOA ~func() ET.Either[E, A], R, E, A any](rdr GEA) GEA {
|
||||
return G.Memoize[GEA](rdr)
|
||||
}
|
||||
|
@@ -264,3 +264,11 @@ func MonadAlt[R, E, A any](first ReaderIOEither[R, E, A], second L.Lazy[ReaderIO
|
||||
func Alt[R, E, A any](second L.Lazy[ReaderIOEither[R, E, A]]) func(ReaderIOEither[R, E, A]) ReaderIOEither[R, E, A] {
|
||||
return G.Alt(second)
|
||||
}
|
||||
|
||||
// Memoize computes the value of the provided [ReaderIOEither] monad lazily but exactly once
|
||||
// The context used to compute the value is the context of the first call, so do not use this
|
||||
// method if the value has a functional dependency on the content of the context
|
||||
func Memoize[
|
||||
R, E, A any](rdr ReaderIOEither[R, E, A]) ReaderIOEither[R, E, A] {
|
||||
return G.Memoize[ReaderIOEither[R, E, A]](rdr)
|
||||
}
|
||||
|
Reference in New Issue
Block a user