mirror of
https://github.com/IBM/fp-go.git
synced 2026-02-26 13:06:09 +02:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc0c14c7cf | ||
|
|
19159ad49e | ||
|
|
b9c8fb4ff1 |
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@@ -42,6 +42,7 @@ jobs:
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
- name: Upload coverage to Coveralls
|
||||
continue-on-error: true
|
||||
uses: coverallsapp/github-action@v2
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -81,6 +82,7 @@ jobs:
|
||||
go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
- name: Upload coverage to Coveralls
|
||||
continue-on-error: true
|
||||
uses: coverallsapp/github-action@v2
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -106,6 +108,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Coveralls Finished
|
||||
continue-on-error: true
|
||||
uses: coverallsapp/github-action@v2
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -83,6 +83,45 @@ var (
|
||||
RIOE.WithContext[*os.File],
|
||||
)
|
||||
|
||||
// Create creates or truncates a file for writing within the given context.
|
||||
// If the file already exists, it is truncated. If it doesn't exist, it is created
|
||||
// with mode 0666 (before umask).
|
||||
//
|
||||
// The operation respects context cancellation and returns a ReaderIOResult
|
||||
// that produces an os.File handle on success.
|
||||
//
|
||||
// The returned file handle should be closed using the Close function when no longer needed,
|
||||
// or managed automatically using WithResource or WriteFile.
|
||||
//
|
||||
// Parameters:
|
||||
// - path: The path to the file to create or truncate
|
||||
//
|
||||
// Returns:
|
||||
// - ReaderIOResult[*os.File]: A context-aware computation that creates the file
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// createFile := Create("output.txt")
|
||||
// result := createFile(ctx)()
|
||||
// either.Fold(
|
||||
// result,
|
||||
// func(err error) { log.Printf("Error: %v", err) },
|
||||
// func(f *os.File) {
|
||||
// defer f.Close()
|
||||
// f.WriteString("Hello, World!")
|
||||
// },
|
||||
// )
|
||||
//
|
||||
// See Also:
|
||||
// - WriteFile: For writing data to a file with automatic resource management
|
||||
// - Open: For opening files for reading
|
||||
// - Close: For closing file handles
|
||||
Create = F.Flow3(
|
||||
IOEF.Create,
|
||||
RIOE.FromIOEither[*os.File],
|
||||
RIOE.WithContext[*os.File],
|
||||
)
|
||||
|
||||
// Remove removes a file by name.
|
||||
// The operation returns the filename on success, allowing for easy composition
|
||||
// with other file operations.
|
||||
@@ -191,3 +230,48 @@ func ReadFile(path string) ReaderIOResult[[]byte] {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// WriteFile writes data to a file in a context-aware manner.
|
||||
// This function automatically manages the file resource using the RAII pattern,
|
||||
// ensuring the file is properly closed even if an error occurs or the context is canceled.
|
||||
//
|
||||
// If the file doesn't exist, it is created with mode 0666 (before umask).
|
||||
// If the file already exists, it is truncated before writing.
|
||||
//
|
||||
// The operation:
|
||||
// - Creates or truncates the file for writing
|
||||
// - Writes all data to the file
|
||||
// - Automatically closes the file when done
|
||||
// - Respects context cancellation during the write operation
|
||||
//
|
||||
// Parameters:
|
||||
// - data: The byte slice to write to the file
|
||||
//
|
||||
// Returns:
|
||||
// - Kleisli[string, []byte]: A function that takes a file path and returns a computation
|
||||
// that writes the data and returns the written bytes on success
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// writeOp := WriteFile([]byte("Hello, World!"))
|
||||
// result := writeOp("output.txt")(ctx)()
|
||||
// either.Fold(
|
||||
// result,
|
||||
// func(err error) { log.Printf("Write error: %v", err) },
|
||||
// func(data []byte) { log.Printf("Wrote %d bytes", len(data)) },
|
||||
// )
|
||||
//
|
||||
// The function uses WithResource internally to ensure proper cleanup:
|
||||
//
|
||||
// WriteFile(data) = Create >> WriteAll(data) >> Close
|
||||
//
|
||||
// See Also:
|
||||
// - ReadFile: For reading file contents with automatic resource management
|
||||
// - Create: For creating files without automatic writing
|
||||
// - WriteAll: For writing to an already-open file handle
|
||||
func WriteFile(data []byte) Kleisli[string, []byte] {
|
||||
return F.Flow2(
|
||||
Create,
|
||||
WriteAll[*os.File](data),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,11 +18,16 @@ package file
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
R "github.com/IBM/fp-go/v2/context/readerioresult"
|
||||
E "github.com/IBM/fp-go/v2/either"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/io"
|
||||
J "github.com/IBM/fp-go/v2/json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type RecordType struct {
|
||||
@@ -49,3 +54,267 @@ func ExampleReadFile() {
|
||||
// Output:
|
||||
// Right[string](Carsten)
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("Success - creates new file", func(t *testing.T) {
|
||||
tempFile := filepath.Join(t.TempDir(), "test_create.txt")
|
||||
|
||||
createOp := Create(tempFile)
|
||||
result := createOp(ctx)()
|
||||
|
||||
assert.True(t, E.IsRight(result))
|
||||
|
||||
// Verify file was created
|
||||
_, err := os.Stat(tempFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Clean up file handle
|
||||
E.MonadFold(result,
|
||||
func(error) *os.File { return nil },
|
||||
func(f *os.File) *os.File { f.Close(); return f },
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Success - truncates existing file", func(t *testing.T) {
|
||||
tempFile := filepath.Join(t.TempDir(), "test_truncate.txt")
|
||||
|
||||
// Create file with initial content
|
||||
err := os.WriteFile(tempFile, []byte("initial content"), 0644)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create should truncate
|
||||
createOp := Create(tempFile)
|
||||
result := createOp(ctx)()
|
||||
|
||||
assert.True(t, E.IsRight(result))
|
||||
|
||||
// Close the file
|
||||
E.MonadFold(result,
|
||||
func(error) *os.File { return nil },
|
||||
func(f *os.File) *os.File { f.Close(); return f },
|
||||
)
|
||||
|
||||
// Verify file was truncated
|
||||
content, err := os.ReadFile(tempFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, content)
|
||||
})
|
||||
|
||||
t.Run("Failure - invalid path", func(t *testing.T) {
|
||||
// Try to create file in non-existent directory
|
||||
invalidPath := filepath.Join(t.TempDir(), "nonexistent", "test.txt")
|
||||
|
||||
createOp := Create(invalidPath)
|
||||
result := createOp(ctx)()
|
||||
|
||||
assert.True(t, E.IsLeft(result))
|
||||
})
|
||||
|
||||
t.Run("Success - file can be written to", func(t *testing.T) {
|
||||
tempFile := filepath.Join(t.TempDir(), "test_write.txt")
|
||||
|
||||
createOp := Create(tempFile)
|
||||
result := createOp(ctx)()
|
||||
|
||||
assert.True(t, E.IsRight(result))
|
||||
|
||||
// Write to the file
|
||||
E.MonadFold(result,
|
||||
func(err error) *os.File { t.Fatalf("Unexpected error: %v", err); return nil },
|
||||
func(f *os.File) *os.File {
|
||||
defer f.Close()
|
||||
_, err := f.WriteString("test content")
|
||||
assert.NoError(t, err)
|
||||
return f
|
||||
},
|
||||
)
|
||||
|
||||
// Verify content was written
|
||||
content, err := os.ReadFile(tempFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test content", string(content))
|
||||
})
|
||||
|
||||
t.Run("Context cancellation", func(t *testing.T) {
|
||||
cancelCtx, cancel := context.WithCancel(context.Background())
|
||||
cancel() // Cancel immediately
|
||||
|
||||
tempFile := filepath.Join(t.TempDir(), "test_cancel.txt")
|
||||
|
||||
createOp := Create(tempFile)
|
||||
result := createOp(cancelCtx)()
|
||||
|
||||
// Note: File creation itself doesn't check context, but this tests the pattern
|
||||
// In practice, context cancellation would affect subsequent operations
|
||||
_ = result
|
||||
})
|
||||
}
|
||||
|
||||
func TestWriteFile(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("Success - writes data to new file", func(t *testing.T) {
|
||||
tempFile := filepath.Join(t.TempDir(), "test_write.txt")
|
||||
testData := []byte("Hello, World!")
|
||||
|
||||
writeOp := WriteFile(testData)
|
||||
result := writeOp(tempFile)(ctx)()
|
||||
|
||||
assert.True(t, E.IsRight(result))
|
||||
|
||||
// Verify returned data
|
||||
E.MonadFold(result,
|
||||
func(err error) []byte { t.Fatalf("Unexpected error: %v", err); return nil },
|
||||
func(data []byte) []byte {
|
||||
assert.Equal(t, testData, data)
|
||||
return data
|
||||
},
|
||||
)
|
||||
|
||||
// Verify file content
|
||||
content, err := os.ReadFile(tempFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testData, content)
|
||||
})
|
||||
|
||||
t.Run("Success - overwrites existing file", func(t *testing.T) {
|
||||
tempFile := filepath.Join(t.TempDir(), "test_overwrite.txt")
|
||||
|
||||
// Write initial content
|
||||
err := os.WriteFile(tempFile, []byte("old content"), 0644)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Overwrite with new content
|
||||
newData := []byte("new content")
|
||||
writeOp := WriteFile(newData)
|
||||
result := writeOp(tempFile)(ctx)()
|
||||
|
||||
assert.True(t, E.IsRight(result))
|
||||
|
||||
// Verify file was overwritten
|
||||
content, err := os.ReadFile(tempFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, newData, content)
|
||||
})
|
||||
|
||||
t.Run("Success - writes empty data", func(t *testing.T) {
|
||||
tempFile := filepath.Join(t.TempDir(), "test_empty.txt")
|
||||
emptyData := []byte{}
|
||||
|
||||
writeOp := WriteFile(emptyData)
|
||||
result := writeOp(tempFile)(ctx)()
|
||||
|
||||
assert.True(t, E.IsRight(result))
|
||||
|
||||
// Verify file is empty
|
||||
content, err := os.ReadFile(tempFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, content)
|
||||
})
|
||||
|
||||
t.Run("Success - writes large data", func(t *testing.T) {
|
||||
tempFile := filepath.Join(t.TempDir(), "test_large.txt")
|
||||
largeData := make([]byte, 1024*1024) // 1MB
|
||||
for i := range largeData {
|
||||
largeData[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
writeOp := WriteFile(largeData)
|
||||
result := writeOp(tempFile)(ctx)()
|
||||
|
||||
assert.True(t, E.IsRight(result))
|
||||
|
||||
// Verify file content
|
||||
content, err := os.ReadFile(tempFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, largeData, content)
|
||||
})
|
||||
|
||||
t.Run("Failure - invalid path", func(t *testing.T) {
|
||||
invalidPath := filepath.Join(t.TempDir(), "nonexistent", "test.txt")
|
||||
testData := []byte("test")
|
||||
|
||||
writeOp := WriteFile(testData)
|
||||
result := writeOp(invalidPath)(ctx)()
|
||||
|
||||
assert.True(t, E.IsLeft(result))
|
||||
})
|
||||
|
||||
t.Run("Success - writes binary data", func(t *testing.T) {
|
||||
tempFile := filepath.Join(t.TempDir(), "test_binary.bin")
|
||||
binaryData := []byte{0x00, 0x01, 0x02, 0xFF, 0xFE, 0xFD}
|
||||
|
||||
writeOp := WriteFile(binaryData)
|
||||
result := writeOp(tempFile)(ctx)()
|
||||
|
||||
assert.True(t, E.IsRight(result))
|
||||
|
||||
// Verify binary content
|
||||
content, err := os.ReadFile(tempFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, binaryData, content)
|
||||
})
|
||||
|
||||
t.Run("Integration - write then read", func(t *testing.T) {
|
||||
tempFile := filepath.Join(t.TempDir(), "test_roundtrip.txt")
|
||||
testData := []byte("Round trip test data")
|
||||
|
||||
// Write data
|
||||
writeOp := WriteFile(testData)
|
||||
writeResult := writeOp(tempFile)(ctx)()
|
||||
assert.True(t, E.IsRight(writeResult))
|
||||
|
||||
// Read data back
|
||||
readOp := ReadFile(tempFile)
|
||||
readResult := readOp(ctx)()
|
||||
assert.True(t, E.IsRight(readResult))
|
||||
|
||||
// Verify data matches
|
||||
E.MonadFold(readResult,
|
||||
func(err error) []byte { t.Fatalf("Unexpected error: %v", err); return nil },
|
||||
func(data []byte) []byte {
|
||||
assert.Equal(t, testData, data)
|
||||
return data
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Composition with Map", func(t *testing.T) {
|
||||
tempFile := filepath.Join(t.TempDir(), "test_compose.txt")
|
||||
testData := []byte("test data")
|
||||
|
||||
// Write and transform result
|
||||
pipeline := F.Pipe1(
|
||||
WriteFile(testData)(tempFile),
|
||||
R.Map(func(data []byte) int { return len(data) }),
|
||||
)
|
||||
|
||||
result := pipeline(ctx)()
|
||||
assert.True(t, E.IsRight(result))
|
||||
|
||||
E.MonadFold(result,
|
||||
func(err error) int { t.Fatalf("Unexpected error: %v", err); return 0 },
|
||||
func(length int) int {
|
||||
assert.Equal(t, len(testData), length)
|
||||
return length
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Context cancellation during write", func(t *testing.T) {
|
||||
cancelCtx, cancel := context.WithCancel(context.Background())
|
||||
cancel() // Cancel immediately
|
||||
|
||||
tempFile := filepath.Join(t.TempDir(), "test_cancel.txt")
|
||||
testData := []byte("test")
|
||||
|
||||
writeOp := WriteFile(testData)
|
||||
result := writeOp(tempFile)(cancelCtx)()
|
||||
|
||||
// Note: The actual write may complete before cancellation is checked
|
||||
// This test verifies the pattern works with cancelled contexts
|
||||
_ = result
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ type TestContext struct {
|
||||
|
||||
// runEffect is a helper function to run an effect with a context and return the result
|
||||
func runEffect[C, A any](eff Effect[C, A], ctx C) (A, error) {
|
||||
ioResult := Provide[C, A](ctx)(eff)
|
||||
ioResult := Provide[A, C](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
return readerResult(context.Background())
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ import (
|
||||
// )(dbEffect)
|
||||
//
|
||||
//go:inline
|
||||
func Local[C1, C2, A any](acc Reader[C1, C2]) Kleisli[C1, Effect[C2, A], A] {
|
||||
func Local[A, C1, C2 any](acc Reader[C1, C2]) Kleisli[C1, Effect[C2, A], A] {
|
||||
return readerreaderioresult.Local[A](acc)
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ func Local[C1, C2, A any](acc Reader[C1, C2]) Kleisli[C1, Effect[C2, A], A] {
|
||||
// - Kleisli[C1, Effect[C2, A], A]: A function that adapts the effect to use C1
|
||||
//
|
||||
//go:inline
|
||||
func Contramap[C1, C2, A any](acc Reader[C1, C2]) Kleisli[C1, Effect[C2, A], A] {
|
||||
func Contramap[A, C1, C2 any](acc Reader[C1, C2]) Kleisli[C1, Effect[C2, A], A] {
|
||||
return readerreaderioresult.Local[A](acc)
|
||||
}
|
||||
|
||||
|
||||
@@ -44,11 +44,11 @@ func TestLocal(t *testing.T) {
|
||||
}
|
||||
|
||||
// Apply Local to transform the context
|
||||
kleisli := Local[OuterContext, InnerContext, string](accessor)
|
||||
kleisli := Local[string, OuterContext, InnerContext](accessor)
|
||||
outerEffect := kleisli(innerEffect)
|
||||
|
||||
// Run with OuterContext
|
||||
ioResult := Provide[OuterContext, string](OuterContext{
|
||||
ioResult := Provide[string, OuterContext](OuterContext{
|
||||
Value: "test",
|
||||
Number: 42,
|
||||
})(outerEffect)
|
||||
@@ -70,11 +70,11 @@ func TestLocal(t *testing.T) {
|
||||
return InnerContext{Value: outer.Value + " transformed"}
|
||||
}
|
||||
|
||||
kleisli := Local[OuterContext, InnerContext, string](accessor)
|
||||
kleisli := Local[string, OuterContext, InnerContext](accessor)
|
||||
outerEffect := kleisli(innerEffect)
|
||||
|
||||
// Run with OuterContext
|
||||
ioResult := Provide[OuterContext, string](OuterContext{
|
||||
ioResult := Provide[string, OuterContext](OuterContext{
|
||||
Value: "original",
|
||||
Number: 100,
|
||||
})(outerEffect)
|
||||
@@ -93,10 +93,10 @@ func TestLocal(t *testing.T) {
|
||||
return InnerContext{Value: outer.Value}
|
||||
}
|
||||
|
||||
kleisli := Local[OuterContext, InnerContext, string](accessor)
|
||||
kleisli := Local[string, OuterContext, InnerContext](accessor)
|
||||
outerEffect := kleisli(innerEffect)
|
||||
|
||||
ioResult := Provide[OuterContext, string](OuterContext{
|
||||
ioResult := Provide[string, OuterContext](OuterContext{
|
||||
Value: "test",
|
||||
Number: 42,
|
||||
})(outerEffect)
|
||||
@@ -122,12 +122,12 @@ func TestLocal(t *testing.T) {
|
||||
level3Effect := Of[Level3]("deep result")
|
||||
|
||||
// Transform Level2 -> Level3
|
||||
local23 := Local[Level2, Level3, string](func(l2 Level2) Level3 {
|
||||
local23 := Local[string, Level2, Level3](func(l2 Level2) Level3 {
|
||||
return Level3{C: l2.B + "-c"}
|
||||
})
|
||||
|
||||
// Transform Level1 -> Level2
|
||||
local12 := Local[Level1, Level2, string](func(l1 Level1) Level2 {
|
||||
local12 := Local[string, Level1, Level2](func(l1 Level1) Level2 {
|
||||
return Level2{B: l1.A + "-b"}
|
||||
})
|
||||
|
||||
@@ -136,7 +136,7 @@ func TestLocal(t *testing.T) {
|
||||
level1Effect := local12(level2Effect)
|
||||
|
||||
// Run with Level1 context
|
||||
ioResult := Provide[Level1, string](Level1{A: "a"})(level1Effect)
|
||||
ioResult := Provide[string, Level1](Level1{A: "a"})(level1Effect)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -165,11 +165,11 @@ func TestLocal(t *testing.T) {
|
||||
return app.DB
|
||||
}
|
||||
|
||||
kleisli := Local[AppConfig, DatabaseConfig, string](accessor)
|
||||
kleisli := Local[string, AppConfig, DatabaseConfig](accessor)
|
||||
appEffect := kleisli(dbEffect)
|
||||
|
||||
// Run with full AppConfig
|
||||
ioResult := Provide[AppConfig, string](AppConfig{
|
||||
ioResult := Provide[string, AppConfig](AppConfig{
|
||||
DB: DatabaseConfig{
|
||||
Host: "localhost",
|
||||
Port: 5432,
|
||||
@@ -195,21 +195,21 @@ func TestContramap(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test Local
|
||||
localKleisli := Local[OuterContext, InnerContext, int](accessor)
|
||||
localKleisli := Local[int, OuterContext, InnerContext](accessor)
|
||||
localEffect := localKleisli(innerEffect)
|
||||
|
||||
// Test Contramap
|
||||
contramapKleisli := Contramap[OuterContext, InnerContext, int](accessor)
|
||||
contramapKleisli := Contramap[int, OuterContext, InnerContext](accessor)
|
||||
contramapEffect := contramapKleisli(innerEffect)
|
||||
|
||||
outerCtx := OuterContext{Value: "test", Number: 100}
|
||||
|
||||
// Run both
|
||||
localIO := Provide[OuterContext, int](outerCtx)(localEffect)
|
||||
localIO := Provide[int, OuterContext](outerCtx)(localEffect)
|
||||
localReader := RunSync(localIO)
|
||||
localResult, localErr := localReader(context.Background())
|
||||
|
||||
contramapIO := Provide[OuterContext, int](outerCtx)(contramapEffect)
|
||||
contramapIO := Provide[int, OuterContext](outerCtx)(contramapEffect)
|
||||
contramapReader := RunSync(contramapIO)
|
||||
contramapResult, contramapErr := contramapReader(context.Background())
|
||||
|
||||
@@ -225,10 +225,10 @@ func TestContramap(t *testing.T) {
|
||||
return InnerContext{Value: outer.Value + " modified"}
|
||||
}
|
||||
|
||||
kleisli := Contramap[OuterContext, InnerContext, string](accessor)
|
||||
kleisli := Contramap[string, OuterContext, InnerContext](accessor)
|
||||
outerEffect := kleisli(innerEffect)
|
||||
|
||||
ioResult := Provide[OuterContext, string](OuterContext{
|
||||
ioResult := Provide[string, OuterContext](OuterContext{
|
||||
Value: "original",
|
||||
Number: 50,
|
||||
})(outerEffect)
|
||||
@@ -247,10 +247,10 @@ func TestContramap(t *testing.T) {
|
||||
return InnerContext{Value: outer.Value}
|
||||
}
|
||||
|
||||
kleisli := Contramap[OuterContext, InnerContext, int](accessor)
|
||||
kleisli := Contramap[int, OuterContext, InnerContext](accessor)
|
||||
outerEffect := kleisli(innerEffect)
|
||||
|
||||
ioResult := Provide[OuterContext, int](OuterContext{
|
||||
ioResult := Provide[int, OuterContext](OuterContext{
|
||||
Value: "test",
|
||||
Number: 42,
|
||||
})(outerEffect)
|
||||
@@ -278,12 +278,12 @@ func TestLocalAndContramapInteroperability(t *testing.T) {
|
||||
effect3 := Of[Config3]("result")
|
||||
|
||||
// Use Local for first transformation
|
||||
local23 := Local[Config2, Config3, string](func(c2 Config2) Config3 {
|
||||
local23 := Local[string, Config2, Config3](func(c2 Config2) Config3 {
|
||||
return Config3{Info: c2.Data}
|
||||
})
|
||||
|
||||
// Use Contramap for second transformation
|
||||
contramap12 := Contramap[Config1, Config2, string](func(c1 Config1) Config2 {
|
||||
contramap12 := Contramap[string, Config1, Config2](func(c1 Config1) Config2 {
|
||||
return Config2{Data: c1.Value}
|
||||
})
|
||||
|
||||
@@ -292,7 +292,7 @@ func TestLocalAndContramapInteroperability(t *testing.T) {
|
||||
effect1 := contramap12(effect2)
|
||||
|
||||
// Run
|
||||
ioResult := Provide[Config1, string](Config1{Value: "test"})(effect1)
|
||||
ioResult := Provide[string, Config1](Config1{Value: "test"})(effect1)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -326,7 +326,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
appEffect := transform(dbEffect)
|
||||
|
||||
// Run with AppConfig
|
||||
ioResult := Provide[AppConfig, string](AppConfig{
|
||||
ioResult := Provide[string, AppConfig](AppConfig{
|
||||
ConfigPath: "/etc/app.conf",
|
||||
})(appEffect)
|
||||
readerResult := RunSync(ioResult)
|
||||
@@ -356,7 +356,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
transform := LocalEffectK[string](failingTransform)
|
||||
outerEffect := transform(innerEffect)
|
||||
|
||||
ioResult := Provide[OuterCtx, string](OuterCtx{Path: "test"})(outerEffect)
|
||||
ioResult := Provide[string, OuterCtx](OuterCtx{Path: "test"})(outerEffect)
|
||||
readerResult := RunSync(ioResult)
|
||||
_, err := readerResult(context.Background())
|
||||
|
||||
@@ -384,7 +384,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
transformK := LocalEffectK[string](transform)
|
||||
outerEffect := transformK(innerEffect)
|
||||
|
||||
ioResult := Provide[OuterCtx, string](OuterCtx{Path: "test"})(outerEffect)
|
||||
ioResult := Provide[string, OuterCtx](OuterCtx{Path: "test"})(outerEffect)
|
||||
readerResult := RunSync(ioResult)
|
||||
_, err := readerResult(context.Background())
|
||||
|
||||
@@ -417,7 +417,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
transform := LocalEffectK[string](loadConfigEffect)
|
||||
appEffect := transform(configEffect)
|
||||
|
||||
ioResult := Provide[AppContext, string](AppContext{
|
||||
ioResult := Provide[string, AppContext](AppContext{
|
||||
ConfigFile: "config.json",
|
||||
})(appEffect)
|
||||
readerResult := RunSync(ioResult)
|
||||
@@ -456,7 +456,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
level1Effect := transform12(level2Effect)
|
||||
|
||||
// Run with Level1 context
|
||||
ioResult := Provide[Level1, string](Level1{A: "a"})(level1Effect)
|
||||
ioResult := Provide[string, Level1](Level1{A: "a"})(level1Effect)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -497,7 +497,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
transform := LocalEffectK[string](transformWithContext)
|
||||
appEffect := transform(dbEffect)
|
||||
|
||||
ioResult := Provide[AppConfig, string](AppConfig{
|
||||
ioResult := Provide[string, AppConfig](AppConfig{
|
||||
Environment: "prod",
|
||||
DBHost: "localhost",
|
||||
DBPort: 5432,
|
||||
@@ -534,14 +534,14 @@ func TestLocalEffectK(t *testing.T) {
|
||||
outerEffect := transform(innerEffect)
|
||||
|
||||
// Test with invalid config
|
||||
ioResult := Provide[RawConfig, string](RawConfig{APIKey: ""})(outerEffect)
|
||||
ioResult := Provide[string, RawConfig](RawConfig{APIKey: ""})(outerEffect)
|
||||
readerResult := RunSync(ioResult)
|
||||
_, err := readerResult(context.Background())
|
||||
|
||||
assert.Error(t, err)
|
||||
|
||||
// Test with valid config
|
||||
ioResult2 := Provide[RawConfig, string](RawConfig{APIKey: "valid-key"})(outerEffect)
|
||||
ioResult2 := Provide[string, RawConfig](RawConfig{APIKey: "valid-key"})(outerEffect)
|
||||
readerResult2 := RunSync(ioResult2)
|
||||
result, err2 := readerResult2(context.Background())
|
||||
|
||||
@@ -569,7 +569,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
})
|
||||
|
||||
// Use Local for second transformation (pure)
|
||||
local12 := Local[Level1, Level2, string](func(l1 Level1) Level2 {
|
||||
local12 := Local[string, Level1, Level2](func(l1 Level1) Level2 {
|
||||
return Level2{Data: l1.Value}
|
||||
})
|
||||
|
||||
@@ -578,7 +578,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
effect1 := local12(effect2)
|
||||
|
||||
// Run
|
||||
ioResult := Provide[Level1, string](Level1{Value: "test"})(effect1)
|
||||
ioResult := Provide[string, Level1](Level1{Value: "test"})(effect1)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -610,7 +610,7 @@ func TestLocalEffectK(t *testing.T) {
|
||||
transform := LocalEffectK[int](complexTransform)
|
||||
outerEffect := transform(innerEffect)
|
||||
|
||||
ioResult := Provide[OuterCtx, int](OuterCtx{Multiplier: 3})(outerEffect)
|
||||
ioResult := Provide[int, OuterCtx](OuterCtx{Multiplier: 3})(outerEffect)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/IBM/fp-go/v2/context/readerreaderioresult"
|
||||
"github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/internal/fromreader"
|
||||
"github.com/IBM/fp-go/v2/io"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
"github.com/IBM/fp-go/v2/readerio"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
@@ -59,6 +60,11 @@ func FromThunk[C, A any](f Thunk[A]) Effect[C, A] {
|
||||
return reader.Of[C](f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func FromResult[C, A any](r Result[A]) Effect[C, A] {
|
||||
return readerreaderioresult.FromEither[C](r)
|
||||
}
|
||||
|
||||
// Succeed creates a successful Effect that produces the given value.
|
||||
// This is the primary way to lift a pure value into the Effect context.
|
||||
//
|
||||
@@ -187,10 +193,126 @@ func Map[C, A, B any](f func(A) B) Operator[C, A, B] {
|
||||
// return effect.Of[MyContext](strconv.Itoa(x * 2))
|
||||
// })(eff)
|
||||
// // chained produces "84"
|
||||
//
|
||||
//go:inline
|
||||
func Chain[C, A, B any](f Kleisli[C, A, B]) Operator[C, A, B] {
|
||||
return readerreaderioresult.Chain(f)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func ChainFirst[C, A, B any](f Kleisli[C, A, B]) Operator[C, A, A] {
|
||||
return readerreaderioresult.ChainFirst(f)
|
||||
}
|
||||
|
||||
// ChainIOK chains an effect with a function that returns an IO action.
|
||||
// This is useful for integrating IO-based computations (synchronous side effects)
|
||||
// into effect chains. The IO action is automatically lifted into the Effect context.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - C: The context type required by the effect
|
||||
// - A: The input value type
|
||||
// - B: The output value type
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: A function that takes A and returns IO[B]
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Operator[C, A, B]: A function that chains the IO-returning function with the effect
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// performIO := func(n int) io.IO[string] {
|
||||
// return func() string {
|
||||
// // Perform synchronous side effect
|
||||
// return fmt.Sprintf("Value: %d", n)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// eff := effect.Of[MyContext](42)
|
||||
// chained := effect.ChainIOK[MyContext](performIO)(eff)
|
||||
// // chained produces "Value: 42"
|
||||
//
|
||||
//go:inline
|
||||
func ChainIOK[C, A, B any](f io.Kleisli[A, B]) Operator[C, A, B] {
|
||||
return readerreaderioresult.ChainIOK[C](f)
|
||||
}
|
||||
|
||||
// ChainFirstIOK chains an effect with a function that returns an IO action,
|
||||
// but discards the result and returns the original value.
|
||||
// This is useful for performing side effects (like logging) without changing the value.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - C: The context type required by the effect
|
||||
// - A: The value type (preserved)
|
||||
// - B: The type produced by the IO action (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: A function that takes A and returns IO[B] for side effects
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Operator[C, A, A]: A function that executes the IO action but preserves the original value
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// logValue := func(n int) io.IO[any] {
|
||||
// return func() any {
|
||||
// fmt.Printf("Processing: %d\n", n)
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// eff := effect.Of[MyContext](42)
|
||||
// logged := effect.ChainFirstIOK[MyContext](logValue)(eff)
|
||||
// // Prints "Processing: 42" but still produces 42
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstIOK[C, A, B any](f io.Kleisli[A, B]) Operator[C, A, A] {
|
||||
return readerreaderioresult.ChainFirstIOK[C](f)
|
||||
}
|
||||
|
||||
// TapIOK is an alias for ChainFirstIOK.
|
||||
// It chains an effect with a function that returns an IO action for side effects,
|
||||
// but preserves the original value. This is useful for logging, debugging, or
|
||||
// performing actions without changing the result.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - C: The context type required by the effect
|
||||
// - A: The value type (preserved)
|
||||
// - B: The type produced by the IO action (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: A function that takes A and returns IO[B] for side effects
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Operator[C, A, A]: A function that executes the IO action but preserves the original value
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// logValue := func(n int) io.IO[any] {
|
||||
// return func() any {
|
||||
// fmt.Printf("Value: %d\n", n)
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// eff := effect.Of[MyContext](42)
|
||||
// tapped := effect.TapIOK[MyContext](logValue)(eff)
|
||||
// // Prints "Value: 42" but still produces 42
|
||||
//
|
||||
//go:inline
|
||||
func TapIOK[C, A, B any](f io.Kleisli[A, B]) Operator[C, A, A] {
|
||||
return readerreaderioresult.ChainFirstIOK[C](f)
|
||||
}
|
||||
|
||||
// Ap applies a function wrapped in an Effect to a value wrapped in an Effect.
|
||||
// This is the applicative apply operation, useful for applying effects in parallel.
|
||||
//
|
||||
|
||||
649
v2/effect/effect_additional_test.go
Normal file
649
v2/effect/effect_additional_test.go
Normal file
@@ -0,0 +1,649 @@
|
||||
// Copyright (c) 2023 - 2025 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 effect
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/io"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestSucceed tests the Succeed function
|
||||
func TestSucceed_Success(t *testing.T) {
|
||||
t.Run("creates successful effect with int", func(t *testing.T) {
|
||||
eff := Succeed[TestConfig](42)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of(42), outcome)
|
||||
})
|
||||
|
||||
t.Run("creates successful effect with string", func(t *testing.T) {
|
||||
eff := Succeed[TestConfig]("hello")
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of("hello"), outcome)
|
||||
})
|
||||
|
||||
t.Run("creates successful effect with zero value", func(t *testing.T) {
|
||||
eff := Succeed[TestConfig](0)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of(0), outcome)
|
||||
})
|
||||
}
|
||||
|
||||
// TestFail tests the Fail function
|
||||
func TestFail_Failure(t *testing.T) {
|
||||
t.Run("creates failed effect with error", func(t *testing.T) {
|
||||
testErr := errors.New("test error")
|
||||
eff := Fail[TestConfig, int](testErr)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Left[int](testErr), outcome)
|
||||
})
|
||||
|
||||
t.Run("preserves error message", func(t *testing.T) {
|
||||
testErr := errors.New("specific error message")
|
||||
eff := Fail[TestConfig, string](testErr)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.True(t, result.IsLeft(outcome))
|
||||
extractedErr := result.MonadFold(outcome,
|
||||
F.Identity[error],
|
||||
func(string) error { return nil },
|
||||
)
|
||||
assert.Equal(t, testErr, extractedErr)
|
||||
})
|
||||
}
|
||||
|
||||
// TestOf tests the Of function
|
||||
func TestOf_Success(t *testing.T) {
|
||||
t.Run("creates successful effect with value", func(t *testing.T) {
|
||||
eff := Of[TestConfig](100)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of(100), outcome)
|
||||
})
|
||||
|
||||
t.Run("is equivalent to Succeed", func(t *testing.T) {
|
||||
value := "test"
|
||||
eff1 := Of[TestConfig](value)
|
||||
eff2 := Succeed[TestConfig](value)
|
||||
outcome1 := eff1(testConfig)(context.Background())()
|
||||
outcome2 := eff2(testConfig)(context.Background())()
|
||||
assert.Equal(t, outcome1, outcome2)
|
||||
})
|
||||
}
|
||||
|
||||
// TestMap tests the Map function
|
||||
func TestMap_Success(t *testing.T) {
|
||||
t.Run("transforms success value", func(t *testing.T) {
|
||||
eff := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
Map[TestConfig](func(x int) int { return x * 2 }),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of(84), outcome)
|
||||
})
|
||||
|
||||
t.Run("transforms type", func(t *testing.T) {
|
||||
eff := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
Map[TestConfig](func(x int) string { return strconv.Itoa(x) }),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of("42"), outcome)
|
||||
})
|
||||
|
||||
t.Run("chains multiple maps", func(t *testing.T) {
|
||||
eff := F.Pipe2(
|
||||
Of[TestConfig](10),
|
||||
Map[TestConfig](func(x int) int { return x + 5 }),
|
||||
Map[TestConfig](func(x int) int { return x * 2 }),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of(30), outcome)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMap_Failure(t *testing.T) {
|
||||
t.Run("propagates error unchanged", func(t *testing.T) {
|
||||
testErr := errors.New("test error")
|
||||
eff := F.Pipe1(
|
||||
Fail[TestConfig, int](testErr),
|
||||
Map[TestConfig](func(x int) int { return x * 2 }),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Left[int](testErr), outcome)
|
||||
})
|
||||
}
|
||||
|
||||
// TestChain tests the Chain function
|
||||
func TestChain_Success(t *testing.T) {
|
||||
t.Run("sequences two effects", func(t *testing.T) {
|
||||
eff := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
Chain[TestConfig](func(x int) Effect[TestConfig, string] {
|
||||
return Of[TestConfig](strconv.Itoa(x))
|
||||
}),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of("42"), outcome)
|
||||
})
|
||||
|
||||
t.Run("chains multiple effects", func(t *testing.T) {
|
||||
eff := F.Pipe2(
|
||||
Of[TestConfig](10),
|
||||
Chain[TestConfig](func(x int) Effect[TestConfig, int] {
|
||||
return Of[TestConfig](x + 5)
|
||||
}),
|
||||
Chain[TestConfig](func(x int) Effect[TestConfig, int] {
|
||||
return Of[TestConfig](x * 2)
|
||||
}),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of(30), outcome)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChain_Failure(t *testing.T) {
|
||||
t.Run("propagates error from first effect", func(t *testing.T) {
|
||||
testErr := errors.New("first error")
|
||||
eff := F.Pipe1(
|
||||
Fail[TestConfig, int](testErr),
|
||||
Chain[TestConfig](func(x int) Effect[TestConfig, string] {
|
||||
return Of[TestConfig]("should not execute")
|
||||
}),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Left[string](testErr), outcome)
|
||||
})
|
||||
|
||||
t.Run("propagates error from second effect", func(t *testing.T) {
|
||||
testErr := errors.New("second error")
|
||||
eff := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
Chain[TestConfig](func(x int) Effect[TestConfig, string] {
|
||||
return Fail[TestConfig, string](testErr)
|
||||
}),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Left[string](testErr), outcome)
|
||||
})
|
||||
}
|
||||
|
||||
// TestChainIOK tests the ChainIOK function
|
||||
func TestChainIOK_Success(t *testing.T) {
|
||||
t.Run("chains with IO action", func(t *testing.T) {
|
||||
counter := 0
|
||||
eff := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
ChainIOK[TestConfig](func(x int) io.IO[string] {
|
||||
return func() string {
|
||||
counter++
|
||||
return fmt.Sprintf("Value: %d", x)
|
||||
}
|
||||
}),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of("Value: 42"), outcome)
|
||||
assert.Equal(t, 1, counter)
|
||||
})
|
||||
|
||||
t.Run("chains multiple IO actions", func(t *testing.T) {
|
||||
log := []string{}
|
||||
eff := F.Pipe2(
|
||||
Of[TestConfig](10),
|
||||
ChainIOK[TestConfig](func(x int) io.IO[int] {
|
||||
return func() int {
|
||||
log = append(log, "first")
|
||||
return x + 5
|
||||
}
|
||||
}),
|
||||
ChainIOK[TestConfig](func(x int) io.IO[int] {
|
||||
return func() int {
|
||||
log = append(log, "second")
|
||||
return x * 2
|
||||
}
|
||||
}),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of(30), outcome)
|
||||
assert.Equal(t, []string{"first", "second"}, log)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChainIOK_Failure(t *testing.T) {
|
||||
t.Run("propagates error from previous effect", func(t *testing.T) {
|
||||
testErr := errors.New("test error")
|
||||
executed := false
|
||||
eff := F.Pipe1(
|
||||
Fail[TestConfig, int](testErr),
|
||||
ChainIOK[TestConfig](func(x int) io.IO[string] {
|
||||
return func() string {
|
||||
executed = true
|
||||
return "should not execute"
|
||||
}
|
||||
}),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Left[string](testErr), outcome)
|
||||
assert.False(t, executed)
|
||||
})
|
||||
}
|
||||
|
||||
// TestChainFirstIOK tests the ChainFirstIOK function
|
||||
func TestChainFirstIOK_Success(t *testing.T) {
|
||||
t.Run("executes IO but preserves value", func(t *testing.T) {
|
||||
log := []string{}
|
||||
eff := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
ChainFirstIOK[TestConfig](func(x int) io.IO[any] {
|
||||
return func() any {
|
||||
log = append(log, fmt.Sprintf("logged: %d", x))
|
||||
return nil
|
||||
}
|
||||
}),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of(42), outcome)
|
||||
assert.Equal(t, []string{"logged: 42"}, log)
|
||||
})
|
||||
|
||||
t.Run("chains multiple side effects", func(t *testing.T) {
|
||||
log := []string{}
|
||||
eff := F.Pipe2(
|
||||
Of[TestConfig](10),
|
||||
ChainFirstIOK[TestConfig](func(x int) io.IO[any] {
|
||||
return func() any {
|
||||
log = append(log, "first")
|
||||
return nil
|
||||
}
|
||||
}),
|
||||
ChainFirstIOK[TestConfig](func(x int) io.IO[any] {
|
||||
return func() any {
|
||||
log = append(log, "second")
|
||||
return nil
|
||||
}
|
||||
}),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of(10), outcome)
|
||||
assert.Equal(t, []string{"first", "second"}, log)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChainFirstIOK_Failure(t *testing.T) {
|
||||
t.Run("propagates error without executing IO", func(t *testing.T) {
|
||||
testErr := errors.New("test error")
|
||||
executed := false
|
||||
eff := F.Pipe1(
|
||||
Fail[TestConfig, int](testErr),
|
||||
ChainFirstIOK[TestConfig](func(x int) io.IO[any] {
|
||||
return func() any {
|
||||
executed = true
|
||||
return nil
|
||||
}
|
||||
}),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Left[int](testErr), outcome)
|
||||
assert.False(t, executed)
|
||||
})
|
||||
}
|
||||
|
||||
// TestTapIOK tests the TapIOK function
|
||||
func TestTapIOK_Success(t *testing.T) {
|
||||
t.Run("executes IO but preserves value", func(t *testing.T) {
|
||||
log := []string{}
|
||||
eff := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
TapIOK[TestConfig](func(x int) io.IO[any] {
|
||||
return func() any {
|
||||
log = append(log, fmt.Sprintf("tapped: %d", x))
|
||||
return nil
|
||||
}
|
||||
}),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of(42), outcome)
|
||||
assert.Equal(t, []string{"tapped: 42"}, log)
|
||||
})
|
||||
|
||||
t.Run("is equivalent to ChainFirstIOK", func(t *testing.T) {
|
||||
log1 := []string{}
|
||||
log2 := []string{}
|
||||
|
||||
eff1 := F.Pipe1(
|
||||
Of[TestConfig](10),
|
||||
TapIOK[TestConfig](func(x int) io.IO[any] {
|
||||
return func() any {
|
||||
log1 = append(log1, "tap")
|
||||
return nil
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
eff2 := F.Pipe1(
|
||||
Of[TestConfig](10),
|
||||
ChainFirstIOK[TestConfig](func(x int) io.IO[any] {
|
||||
return func() any {
|
||||
log2 = append(log2, "tap")
|
||||
return nil
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
outcome1 := eff1(testConfig)(context.Background())()
|
||||
outcome2 := eff2(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, outcome1, outcome2)
|
||||
assert.Equal(t, log1, log2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTapIOK_Failure(t *testing.T) {
|
||||
t.Run("propagates error without executing IO", func(t *testing.T) {
|
||||
testErr := errors.New("test error")
|
||||
executed := false
|
||||
eff := F.Pipe1(
|
||||
Fail[TestConfig, int](testErr),
|
||||
TapIOK[TestConfig](func(x int) io.IO[any] {
|
||||
return func() any {
|
||||
executed = true
|
||||
return nil
|
||||
}
|
||||
}),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Left[int](testErr), outcome)
|
||||
assert.False(t, executed)
|
||||
})
|
||||
}
|
||||
|
||||
// TestChainResultK tests the ChainResultK function
|
||||
func TestChainResultK_Success(t *testing.T) {
|
||||
t.Run("chains with Result-returning function", func(t *testing.T) {
|
||||
parseIntResult := result.Eitherize1(strconv.Atoi)
|
||||
eff := F.Pipe1(
|
||||
Of[TestConfig]("42"),
|
||||
ChainResultK[TestConfig](parseIntResult),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of(42), outcome)
|
||||
})
|
||||
|
||||
t.Run("chains multiple Result operations", func(t *testing.T) {
|
||||
parseIntResult := result.Eitherize1(strconv.Atoi)
|
||||
eff := F.Pipe2(
|
||||
Of[TestConfig]("10"),
|
||||
ChainResultK[TestConfig](parseIntResult),
|
||||
ChainResultK[TestConfig](func(x int) result.Result[string] {
|
||||
return result.Of(fmt.Sprintf("Value: %d", x*2))
|
||||
}),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of("Value: 20"), outcome)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChainResultK_Failure(t *testing.T) {
|
||||
t.Run("propagates error from previous effect", func(t *testing.T) {
|
||||
testErr := errors.New("test error")
|
||||
parseIntResult := result.Eitherize1(strconv.Atoi)
|
||||
eff := F.Pipe1(
|
||||
Fail[TestConfig, string](testErr),
|
||||
ChainResultK[TestConfig](parseIntResult),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Left[int](testErr), outcome)
|
||||
})
|
||||
|
||||
t.Run("propagates error from Result function", func(t *testing.T) {
|
||||
parseIntResult := result.Eitherize1(strconv.Atoi)
|
||||
eff := F.Pipe1(
|
||||
Of[TestConfig]("not a number"),
|
||||
ChainResultK[TestConfig](parseIntResult),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.True(t, result.IsLeft(outcome))
|
||||
})
|
||||
}
|
||||
|
||||
// TestAp tests the Ap function
|
||||
func TestAp_Success(t *testing.T) {
|
||||
t.Run("applies function effect to value effect", func(t *testing.T) {
|
||||
fnEff := Of[TestConfig](func(x int) int { return x * 2 })
|
||||
valEff := Of[TestConfig](21)
|
||||
eff := Ap[int](valEff)(fnEff)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of(42), outcome)
|
||||
})
|
||||
|
||||
t.Run("applies function with different types", func(t *testing.T) {
|
||||
fnEff := Of[TestConfig](func(x int) string { return strconv.Itoa(x) })
|
||||
valEff := Of[TestConfig](42)
|
||||
eff := Ap[string](valEff)(fnEff)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of("42"), outcome)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAp_Failure(t *testing.T) {
|
||||
t.Run("propagates error from function effect", func(t *testing.T) {
|
||||
testErr := errors.New("function error")
|
||||
fnEff := Fail[TestConfig, func(int) int](testErr)
|
||||
valEff := Of[TestConfig](42)
|
||||
eff := Ap[int](valEff)(fnEff)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Left[int](testErr), outcome)
|
||||
})
|
||||
|
||||
t.Run("propagates error from value effect", func(t *testing.T) {
|
||||
testErr := errors.New("value error")
|
||||
fnEff := Of[TestConfig](func(x int) int { return x * 2 })
|
||||
valEff := Fail[TestConfig, int](testErr)
|
||||
eff := Ap[int](valEff)(fnEff)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Left[int](testErr), outcome)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSuspend tests the Suspend function
|
||||
func TestSuspend_Success(t *testing.T) {
|
||||
t.Run("delays evaluation of effect", func(t *testing.T) {
|
||||
counter := 0
|
||||
eff := Suspend(func() Effect[TestConfig, int] {
|
||||
counter++
|
||||
return Of[TestConfig](42)
|
||||
})
|
||||
assert.Equal(t, 0, counter, "should not evaluate immediately")
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, 1, counter, "should evaluate when run")
|
||||
assert.Equal(t, result.Of(42), outcome)
|
||||
})
|
||||
|
||||
t.Run("enables recursive effects", func(t *testing.T) {
|
||||
var factorial func(int) Effect[TestConfig, int]
|
||||
factorial = func(n int) Effect[TestConfig, int] {
|
||||
if n <= 1 {
|
||||
return Of[TestConfig](1)
|
||||
}
|
||||
return Suspend(func() Effect[TestConfig, int] {
|
||||
return F.Pipe1(
|
||||
factorial(n-1),
|
||||
Map[TestConfig](func(x int) int { return x * n }),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
outcome := factorial(5)(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of(120), outcome)
|
||||
})
|
||||
}
|
||||
|
||||
// TestTap tests the Tap function
|
||||
func TestTap_Success(t *testing.T) {
|
||||
t.Run("executes side effect but preserves value", func(t *testing.T) {
|
||||
log := []string{}
|
||||
eff := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
Tap[TestConfig](func(x int) Effect[TestConfig, any] {
|
||||
log = append(log, fmt.Sprintf("tapped: %d", x))
|
||||
return Of[TestConfig, any](nil)
|
||||
}),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of(42), outcome)
|
||||
assert.Equal(t, []string{"tapped: 42"}, log)
|
||||
})
|
||||
|
||||
t.Run("chains multiple taps", func(t *testing.T) {
|
||||
log := []string{}
|
||||
eff := F.Pipe2(
|
||||
Of[TestConfig](10),
|
||||
Tap[TestConfig](func(x int) Effect[TestConfig, any] {
|
||||
log = append(log, "first")
|
||||
return Of[TestConfig, any](nil)
|
||||
}),
|
||||
Tap[TestConfig](func(x int) Effect[TestConfig, any] {
|
||||
log = append(log, "second")
|
||||
return Of[TestConfig, any](nil)
|
||||
}),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of(10), outcome)
|
||||
assert.Equal(t, []string{"first", "second"}, log)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTap_Failure(t *testing.T) {
|
||||
t.Run("propagates error without executing tap", func(t *testing.T) {
|
||||
testErr := errors.New("test error")
|
||||
executed := false
|
||||
eff := F.Pipe1(
|
||||
Fail[TestConfig, int](testErr),
|
||||
Tap[TestConfig](func(x int) Effect[TestConfig, any] {
|
||||
executed = true
|
||||
return Of[TestConfig, any](nil)
|
||||
}),
|
||||
)
|
||||
outcome := eff(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Left[int](testErr), outcome)
|
||||
assert.False(t, executed)
|
||||
})
|
||||
}
|
||||
|
||||
// TestTernary tests the Ternary function
|
||||
func TestTernary_Success(t *testing.T) {
|
||||
t.Run("executes onTrue when predicate is true", func(t *testing.T) {
|
||||
kleisli := Ternary(
|
||||
func(x int) bool { return x > 10 },
|
||||
func(x int) Effect[TestConfig, string] {
|
||||
return Of[TestConfig]("large")
|
||||
},
|
||||
func(x int) Effect[TestConfig, string] {
|
||||
return Of[TestConfig]("small")
|
||||
},
|
||||
)
|
||||
outcome := kleisli(15)(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of("large"), outcome)
|
||||
})
|
||||
|
||||
t.Run("executes onFalse when predicate is false", func(t *testing.T) {
|
||||
kleisli := Ternary(
|
||||
func(x int) bool { return x > 10 },
|
||||
func(x int) Effect[TestConfig, string] {
|
||||
return Of[TestConfig]("large")
|
||||
},
|
||||
func(x int) Effect[TestConfig, string] {
|
||||
return Of[TestConfig]("small")
|
||||
},
|
||||
)
|
||||
outcome := kleisli(5)(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of("small"), outcome)
|
||||
})
|
||||
|
||||
t.Run("works with boundary value", func(t *testing.T) {
|
||||
kleisli := Ternary(
|
||||
func(x int) bool { return x >= 10 },
|
||||
func(x int) Effect[TestConfig, string] {
|
||||
return Of[TestConfig]("gte")
|
||||
},
|
||||
func(x int) Effect[TestConfig, string] {
|
||||
return Of[TestConfig]("lt")
|
||||
},
|
||||
)
|
||||
outcome := kleisli(10)(testConfig)(context.Background())()
|
||||
assert.Equal(t, result.Of("gte"), outcome)
|
||||
})
|
||||
}
|
||||
|
||||
// TestRead tests the Read function
|
||||
func TestRead_Success(t *testing.T) {
|
||||
t.Run("provides context to effect", func(t *testing.T) {
|
||||
eff := Of[TestConfig](42)
|
||||
thunk := Read[int](testConfig)(eff)
|
||||
outcome := thunk(context.Background())()
|
||||
assert.Equal(t, result.Of(42), outcome)
|
||||
})
|
||||
|
||||
t.Run("converts effect to thunk", func(t *testing.T) {
|
||||
eff := F.Pipe1(
|
||||
Of[TestConfig](10),
|
||||
Map[TestConfig](func(x int) int { return x * testConfig.Multiplier }),
|
||||
)
|
||||
thunk := Read[int](testConfig)(eff)
|
||||
outcome := thunk(context.Background())()
|
||||
assert.Equal(t, result.Of(30), outcome)
|
||||
})
|
||||
|
||||
t.Run("works with different contexts", func(t *testing.T) {
|
||||
cfg1 := TestConfig{Multiplier: 2, Prefix: "A", DatabaseURL: ""}
|
||||
cfg2 := TestConfig{Multiplier: 5, Prefix: "B", DatabaseURL: ""}
|
||||
|
||||
// Create an effect that uses the context's Multiplier
|
||||
eff := F.Pipe1(
|
||||
Of[TestConfig](10),
|
||||
ChainReaderK[TestConfig](func(x int) reader.Reader[TestConfig, int] {
|
||||
return func(cfg TestConfig) int {
|
||||
return x * cfg.Multiplier
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
thunk1 := Read[int](cfg1)(eff)
|
||||
thunk2 := Read[int](cfg2)(eff)
|
||||
|
||||
outcome1 := thunk1(context.Background())()
|
||||
outcome2 := thunk2(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Of(20), outcome1)
|
||||
assert.Equal(t, result.Of(50), outcome2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRead_Failure(t *testing.T) {
|
||||
t.Run("propagates error from effect", func(t *testing.T) {
|
||||
testErr := errors.New("test error")
|
||||
eff := Fail[TestConfig, int](testErr)
|
||||
thunk := Read[int](testConfig)(eff)
|
||||
outcome := thunk(context.Background())()
|
||||
assert.Equal(t, result.Left[int](testErr), outcome)
|
||||
})
|
||||
}
|
||||
@@ -46,7 +46,7 @@ import (
|
||||
// eff := effect.Of[MyContext](42)
|
||||
// thunk := effect.Provide[MyContext, int](ctx)(eff)
|
||||
// // thunk is now a ReaderIOResult[int] that can be run
|
||||
func Provide[C, A any](c C) func(Effect[C, A]) ReaderIOResult[A] {
|
||||
func Provide[A, C any](c C) func(Effect[C, A]) ReaderIOResult[A] {
|
||||
return readerreaderioresult.Read[A](c)
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ func TestProvide(t *testing.T) {
|
||||
ctx := TestContext{Value: "test-value"}
|
||||
eff := Of[TestContext]("result")
|
||||
|
||||
ioResult := Provide[TestContext, string](ctx)(eff)
|
||||
ioResult := Provide[string, TestContext](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestProvide(t *testing.T) {
|
||||
cfg := Config{Host: "localhost", Port: 8080}
|
||||
eff := Of[Config]("connected")
|
||||
|
||||
ioResult := Provide[Config, string](cfg)(eff)
|
||||
ioResult := Provide[string, Config](cfg)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -58,7 +58,7 @@ func TestProvide(t *testing.T) {
|
||||
ctx := TestContext{Value: "test"}
|
||||
eff := Fail[TestContext, string](expectedErr)
|
||||
|
||||
ioResult := Provide[TestContext, string](ctx)(eff)
|
||||
ioResult := Provide[string, TestContext](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
_, err := readerResult(context.Background())
|
||||
|
||||
@@ -74,7 +74,7 @@ func TestProvide(t *testing.T) {
|
||||
ctx := SimpleContext{ID: 42}
|
||||
eff := Of[SimpleContext](100)
|
||||
|
||||
ioResult := Provide[SimpleContext, int](ctx)(eff)
|
||||
ioResult := Provide[int, SimpleContext](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -89,7 +89,7 @@ func TestProvide(t *testing.T) {
|
||||
return Of[TestContext]("result")
|
||||
})(Of[TestContext](42))
|
||||
|
||||
ioResult := Provide[TestContext, string](ctx)(eff)
|
||||
ioResult := Provide[string, TestContext](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -104,7 +104,7 @@ func TestProvide(t *testing.T) {
|
||||
return "mapped"
|
||||
})(Of[TestContext](42))
|
||||
|
||||
ioResult := Provide[TestContext, string](ctx)(eff)
|
||||
ioResult := Provide[string, TestContext](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -118,7 +118,7 @@ func TestRunSync(t *testing.T) {
|
||||
ctx := TestContext{Value: "test"}
|
||||
eff := Of[TestContext](42)
|
||||
|
||||
ioResult := Provide[TestContext, int](ctx)(eff)
|
||||
ioResult := Provide[int, TestContext](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -130,7 +130,7 @@ func TestRunSync(t *testing.T) {
|
||||
ctx := TestContext{Value: "test"}
|
||||
eff := Of[TestContext]("hello")
|
||||
|
||||
ioResult := Provide[TestContext, string](ctx)(eff)
|
||||
ioResult := Provide[string, TestContext](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
|
||||
bgCtx := context.Background()
|
||||
@@ -145,7 +145,7 @@ func TestRunSync(t *testing.T) {
|
||||
ctx := TestContext{Value: "test"}
|
||||
eff := Fail[TestContext, int](expectedErr)
|
||||
|
||||
ioResult := Provide[TestContext, int](ctx)(eff)
|
||||
ioResult := Provide[int, TestContext](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
_, err := readerResult(context.Background())
|
||||
|
||||
@@ -162,7 +162,7 @@ func TestRunSync(t *testing.T) {
|
||||
return Of[TestContext](x + 10)
|
||||
})(Of[TestContext](5)))
|
||||
|
||||
ioResult := Provide[TestContext, int](ctx)(eff)
|
||||
ioResult := Provide[int, TestContext](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -174,7 +174,7 @@ func TestRunSync(t *testing.T) {
|
||||
ctx := TestContext{Value: "test"}
|
||||
eff := Of[TestContext](42)
|
||||
|
||||
ioResult := Provide[TestContext, int](ctx)(eff)
|
||||
ioResult := Provide[int, TestContext](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
|
||||
// Run multiple times
|
||||
@@ -200,7 +200,7 @@ func TestRunSync(t *testing.T) {
|
||||
user := User{Name: "Alice", Age: 30}
|
||||
eff := Of[TestContext](user)
|
||||
|
||||
ioResult := Provide[TestContext, User](ctx)(eff)
|
||||
ioResult := Provide[User, TestContext](ctx)(eff)
|
||||
readerResult := RunSync(ioResult)
|
||||
result, err := readerResult(context.Background())
|
||||
|
||||
@@ -222,7 +222,7 @@ func TestProvideAndRunSyncIntegration(t *testing.T) {
|
||||
eff := Of[AppConfig]("API call successful")
|
||||
|
||||
// Provide config and run
|
||||
result, err := RunSync(Provide[AppConfig, string](cfg)(eff))(context.Background())
|
||||
result, err := RunSync(Provide[string, AppConfig](cfg)(eff))(context.Background())
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "API call successful", result)
|
||||
@@ -238,7 +238,7 @@ func TestProvideAndRunSyncIntegration(t *testing.T) {
|
||||
|
||||
eff := Fail[AppConfig, string](expectedErr)
|
||||
|
||||
_, err := RunSync(Provide[AppConfig, string](cfg)(eff))(context.Background())
|
||||
_, err := RunSync(Provide[string, AppConfig](cfg)(eff))(context.Background())
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, expectedErr, err)
|
||||
@@ -253,7 +253,7 @@ func TestProvideAndRunSyncIntegration(t *testing.T) {
|
||||
return Of[TestContext](x * 2)
|
||||
})(Of[TestContext](21)))
|
||||
|
||||
result, err := RunSync(Provide[TestContext, string](ctx)(eff))(context.Background())
|
||||
result, err := RunSync(Provide[string, TestContext](ctx)(eff))(context.Background())
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "final", result)
|
||||
@@ -281,7 +281,7 @@ func TestProvideAndRunSyncIntegration(t *testing.T) {
|
||||
return State{X: x}
|
||||
})(Of[TestContext](10)))
|
||||
|
||||
result, err := RunSync(Provide[TestContext, State](ctx)(eff))(context.Background())
|
||||
result, err := RunSync(Provide[State, TestContext](ctx)(eff))(context.Background())
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 10, result.X)
|
||||
@@ -300,11 +300,11 @@ func TestProvideAndRunSyncIntegration(t *testing.T) {
|
||||
innerEff := Of[InnerCtx]("inner result")
|
||||
|
||||
// Transform context
|
||||
transformedEff := Local[OuterCtx, InnerCtx, string](func(outer OuterCtx) InnerCtx {
|
||||
transformedEff := Local[string, OuterCtx, InnerCtx](func(outer OuterCtx) InnerCtx {
|
||||
return InnerCtx{Data: outer.Value + "-transformed"}
|
||||
})(innerEff)
|
||||
|
||||
result, err := RunSync(Provide[OuterCtx, string](outerCtx)(transformedEff))(context.Background())
|
||||
result, err := RunSync(Provide[string, OuterCtx](outerCtx)(transformedEff))(context.Background())
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "inner result", result)
|
||||
@@ -318,7 +318,7 @@ func TestProvideAndRunSyncIntegration(t *testing.T) {
|
||||
return Of[TestContext](x * 2)
|
||||
})(input)
|
||||
|
||||
result, err := RunSync(Provide[TestContext, []int](ctx)(eff))(context.Background())
|
||||
result, err := RunSync(Provide[[]int, TestContext](ctx)(eff))(context.Background())
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []int{2, 4, 6, 8, 10}, result)
|
||||
|
||||
@@ -37,11 +37,46 @@ import (
|
||||
"github.com/IBM/fp-go/v2/readeroption"
|
||||
)
|
||||
|
||||
// FromReaderOption converts a ReaderOption to a Kleisli arrow that handles None cases.
|
||||
// When the Option is None, the provided lazy error value is used.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - R: The context/environment type
|
||||
// - A: The value type
|
||||
// - E: The error type
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - onNone: Lazy function that provides the error value when Option is None
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Kleisli arrow that converts ReaderOption to ReaderIOEither
|
||||
//
|
||||
//go:inline
|
||||
func FromReaderOption[R, A, E any](onNone Lazy[E]) Kleisli[R, E, ReaderOption[R, A], A] {
|
||||
return function.Bind2nd(function.Flow2[ReaderOption[R, A], IOE.Kleisli[E, Option[A], A]], IOE.FromOption[A](onNone))
|
||||
}
|
||||
|
||||
// FromReaderIO lifts a ReaderIO into a ReaderIOEither, placing the result in the Right side.
|
||||
// This is an alias for RightReaderIO, converting a computation that cannot fail into one
|
||||
// that can fail but never does.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - E: The error type (will never actually contain an error)
|
||||
// - R: The context/environment type
|
||||
// - A: The value type
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - ma: The ReaderIO to lift
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - ReaderIOEither with the ReaderIO result in the Right side
|
||||
//
|
||||
//go:inline
|
||||
func FromReaderIO[E, R, A any](ma ReaderIO[R, A]) ReaderIOEither[R, E, A] {
|
||||
return RightReaderIO[E](ma)
|
||||
@@ -121,6 +156,26 @@ func MonadChainFirst[R, E, A, B any](fa ReaderIOEither[R, E, A], f Kleisli[R, E,
|
||||
f)
|
||||
}
|
||||
|
||||
// MonadTap is an alias for MonadChainFirst.
|
||||
// It sequences two computations but keeps the result of the first, emphasizing the
|
||||
// side-effect nature of the operation (like "tapping" into a pipeline).
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - R: The context/environment type
|
||||
// - E: The error type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - fa: The ReaderIOEither computation
|
||||
// - f: The side effect Kleisli arrow
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - ReaderIOEither with the original value preserved
|
||||
//
|
||||
//go:inline
|
||||
func MonadTap[R, E, A, B any](fa ReaderIOEither[R, E, A], f Kleisli[R, E, A, B]) ReaderIOEither[R, E, A] {
|
||||
return MonadChainFirst(fa, f)
|
||||
@@ -165,6 +220,26 @@ func MonadChainFirstEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f either
|
||||
)
|
||||
}
|
||||
|
||||
// MonadTapEitherK is an alias for MonadChainFirstEitherK.
|
||||
// It chains an Either-returning computation while preserving the original value,
|
||||
// emphasizing the side-effect nature of the operation.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - R: The context/environment type
|
||||
// - E: The error type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - ma: The ReaderIOEither computation
|
||||
// - f: The Either-returning side effect function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - ReaderIOEither with the original value preserved
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f either.Kleisli[E, A, B]) ReaderIOEither[R, E, A] {
|
||||
return MonadChainFirstEitherK(ma, f)
|
||||
@@ -183,6 +258,25 @@ func ChainFirstEitherK[R, E, A, B any](f either.Kleisli[E, A, B]) Operator[R, E,
|
||||
)
|
||||
}
|
||||
|
||||
// TapEitherK is an alias for ChainFirstEitherK.
|
||||
// It returns a function that chains an Either-returning side effect while preserving
|
||||
// the original value, emphasizing the "tap" pattern for observing values.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - R: The context/environment type
|
||||
// - E: The error type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: The Either-returning side effect function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Operator that executes the side effect while preserving the original value
|
||||
//
|
||||
//go:inline
|
||||
func TapEitherK[R, E, A, B any](f either.Kleisli[E, A, B]) Operator[R, E, A, A] {
|
||||
return ChainFirstEitherK[R](f)
|
||||
@@ -213,6 +307,26 @@ func ChainReaderK[E, R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, E, A, B
|
||||
)
|
||||
}
|
||||
|
||||
// MonadChainFirstReaderK chains a Reader-returning computation while preserving the original value.
|
||||
// Useful for performing Reader-based side effects (like logging with context) while keeping
|
||||
// the original value.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - E: The error type
|
||||
// - R: The context/environment type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - ma: The ReaderIOEither computation
|
||||
// - f: The Reader-returning side effect function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - ReaderIOEither with the original value preserved
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirstReaderK[E, R, A, B any](ma ReaderIOEither[R, E, A], f reader.Kleisli[R, A, B]) ReaderIOEither[R, E, A] {
|
||||
return fromreader.MonadChainFirstReaderK(
|
||||
@@ -223,6 +337,25 @@ func MonadChainFirstReaderK[E, R, A, B any](ma ReaderIOEither[R, E, A], f reader
|
||||
)
|
||||
}
|
||||
|
||||
// MonadTapReaderK is an alias for MonadChainFirstReaderK.
|
||||
// It chains a Reader-returning side effect while preserving the original value.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - E: The error type
|
||||
// - R: The context/environment type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - ma: The ReaderIOEither computation
|
||||
// - f: The Reader-returning side effect function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - ReaderIOEither with the original value preserved
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapReaderK[E, R, A, B any](ma ReaderIOEither[R, E, A], f reader.Kleisli[R, A, B]) ReaderIOEither[R, E, A] {
|
||||
return MonadChainFirstReaderK(ma, f)
|
||||
@@ -240,11 +373,49 @@ func ChainFirstReaderK[E, R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, E,
|
||||
)
|
||||
}
|
||||
|
||||
// TapReaderK is an alias for ChainFirstReaderK.
|
||||
// It returns a function that chains a Reader-returning side effect while preserving
|
||||
// the original value.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - E: The error type
|
||||
// - R: The context/environment type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: The Reader-returning side effect function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Operator that executes the side effect while preserving the original value
|
||||
//
|
||||
//go:inline
|
||||
func TapReaderK[E, R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, E, A, A] {
|
||||
return ChainFirstReaderK[E](f)
|
||||
}
|
||||
|
||||
// MonadChainReaderIOK chains a ReaderIO-returning computation into a ReaderIOEither.
|
||||
// The ReaderIO is automatically lifted into the ReaderIOEither context.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - E: The error type
|
||||
// - R: The context/environment type
|
||||
// - A: The input value type
|
||||
// - B: The output value type
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - ma: The ReaderIOEither computation
|
||||
// - f: The ReaderIO-returning function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - ReaderIOEither with the result of the ReaderIO computation
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainReaderIOK[E, R, A, B any](ma ReaderIOEither[R, E, A], f readerio.Kleisli[R, A, B]) ReaderIOEither[R, E, B] {
|
||||
return fromreader.MonadChainReaderK(
|
||||
@@ -255,6 +426,24 @@ func MonadChainReaderIOK[E, R, A, B any](ma ReaderIOEither[R, E, A], f readerio.
|
||||
)
|
||||
}
|
||||
|
||||
// ChainReaderIOK returns a function that chains a ReaderIO-returning function into ReaderIOEither.
|
||||
// This is the curried version of MonadChainReaderIOK.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - E: The error type
|
||||
// - R: The context/environment type
|
||||
// - A: The input value type
|
||||
// - B: The output value type
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: The ReaderIO-returning function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Operator that chains the ReaderIO computation
|
||||
//
|
||||
//go:inline
|
||||
func ChainReaderIOK[E, R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, E, A, B] {
|
||||
return fromreader.ChainReaderK(
|
||||
@@ -264,6 +453,25 @@ func ChainReaderIOK[E, R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, E,
|
||||
)
|
||||
}
|
||||
|
||||
// MonadChainFirstReaderIOK chains a ReaderIO-returning computation while preserving the original value.
|
||||
// Useful for performing ReaderIO-based side effects while keeping the original value.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - E: The error type
|
||||
// - R: The context/environment type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - ma: The ReaderIOEither computation
|
||||
// - f: The ReaderIO-returning side effect function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - ReaderIOEither with the original value preserved
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirstReaderIOK[E, R, A, B any](ma ReaderIOEither[R, E, A], f readerio.Kleisli[R, A, B]) ReaderIOEither[R, E, A] {
|
||||
return fromreader.MonadChainFirstReaderK(
|
||||
@@ -274,11 +482,48 @@ func MonadChainFirstReaderIOK[E, R, A, B any](ma ReaderIOEither[R, E, A], f read
|
||||
)
|
||||
}
|
||||
|
||||
// MonadTapReaderIOK is an alias for MonadChainFirstReaderIOK.
|
||||
// It chains a ReaderIO-returning side effect while preserving the original value.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - E: The error type
|
||||
// - R: The context/environment type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - ma: The ReaderIOEither computation
|
||||
// - f: The ReaderIO-returning side effect function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - ReaderIOEither with the original value preserved
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapReaderIOK[E, R, A, B any](ma ReaderIOEither[R, E, A], f readerio.Kleisli[R, A, B]) ReaderIOEither[R, E, A] {
|
||||
return MonadChainFirstReaderIOK(ma, f)
|
||||
}
|
||||
|
||||
// ChainFirstReaderIOK returns a function that chains a ReaderIO-returning function while
|
||||
// preserving the original value. This is the curried version of MonadChainFirstReaderIOK.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - E: The error type
|
||||
// - R: The context/environment type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: The ReaderIO-returning side effect function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Operator that executes the side effect while preserving the original value
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstReaderIOK[E, R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, E, A, A] {
|
||||
return fromreader.ChainFirstReaderK(
|
||||
@@ -288,11 +533,49 @@ func ChainFirstReaderIOK[E, R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R
|
||||
)
|
||||
}
|
||||
|
||||
// TapReaderIOK is an alias for ChainFirstReaderIOK.
|
||||
// It returns a function that chains a ReaderIO-returning side effect while preserving
|
||||
// the original value.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - E: The error type
|
||||
// - R: The context/environment type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: The ReaderIO-returning side effect function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Operator that executes the side effect while preserving the original value
|
||||
//
|
||||
//go:inline
|
||||
func TapReaderIOK[E, R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, E, A, A] {
|
||||
return ChainFirstReaderIOK[E](f)
|
||||
}
|
||||
|
||||
// MonadChainReaderEitherK chains a ReaderEither-returning computation into a ReaderIOEither.
|
||||
// The ReaderEither is automatically lifted into the ReaderIOEither context.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - R: The context/environment type
|
||||
// - E: The error type
|
||||
// - A: The input value type
|
||||
// - B: The output value type
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - ma: The ReaderIOEither computation
|
||||
// - f: The ReaderEither-returning function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - ReaderIOEither with the result of the ReaderEither computation
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainReaderEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f RE.Kleisli[R, E, A, B]) ReaderIOEither[R, E, B] {
|
||||
return fromreader.MonadChainReaderK(
|
||||
@@ -315,6 +598,25 @@ func ChainReaderEitherK[E, R, A, B any](f RE.Kleisli[R, E, A, B]) Operator[R, E,
|
||||
)
|
||||
}
|
||||
|
||||
// MonadChainFirstReaderEitherK chains a ReaderEither-returning computation while preserving the original value.
|
||||
// Useful for performing ReaderEither-based side effects while keeping the original value.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - R: The context/environment type
|
||||
// - E: The error type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - ma: The ReaderIOEither computation
|
||||
// - f: The ReaderEither-returning side effect function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - ReaderIOEither with the original value preserved
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirstReaderEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f RE.Kleisli[R, E, A, B]) ReaderIOEither[R, E, A] {
|
||||
return fromreader.MonadChainFirstReaderK(
|
||||
@@ -325,6 +627,25 @@ func MonadChainFirstReaderEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f
|
||||
)
|
||||
}
|
||||
|
||||
// MonadTapReaderEitherK is an alias for MonadChainFirstReaderEitherK.
|
||||
// It chains a ReaderEither-returning side effect while preserving the original value.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - R: The context/environment type
|
||||
// - E: The error type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - ma: The ReaderIOEither computation
|
||||
// - f: The ReaderEither-returning side effect function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - ReaderIOEither with the original value preserved
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapReaderEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f RE.Kleisli[R, E, A, B]) ReaderIOEither[R, E, A] {
|
||||
return MonadChainFirstReaderEitherK(ma, f)
|
||||
@@ -342,11 +663,48 @@ func ChainFirstReaderEitherK[E, R, A, B any](f RE.Kleisli[R, E, A, B]) Operator[
|
||||
)
|
||||
}
|
||||
|
||||
// TapReaderEitherK is an alias for ChainFirstReaderEitherK.
|
||||
// It returns a function that chains a ReaderEither-returning side effect while preserving
|
||||
// the original value.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - E: The error type
|
||||
// - R: The context/environment type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: The ReaderEither-returning side effect function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Operator that executes the side effect while preserving the original value
|
||||
//
|
||||
//go:inline
|
||||
func TapReaderEitherK[E, R, A, B any](f RE.Kleisli[R, E, A, B]) Operator[R, E, A, A] {
|
||||
return ChainFirstReaderEitherK(f)
|
||||
}
|
||||
|
||||
// ChainReaderOptionK returns a function that chains a ReaderOption-returning function into ReaderIOEither.
|
||||
// When the Option is None, the provided error value is used.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - R: The context/environment type
|
||||
// - A: The input value type
|
||||
// - B: The output value type
|
||||
// - E: The error type
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - onNone: Lazy function that provides the error value when Option is None
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Function that takes a ReaderOption Kleisli and returns an Operator
|
||||
//
|
||||
//go:inline
|
||||
func ChainReaderOptionK[R, A, B, E any](onNone Lazy[E]) func(readeroption.Kleisli[R, A, B]) Operator[R, E, A, B] {
|
||||
fro := FromReaderOption[R, B](onNone)
|
||||
@@ -359,6 +717,24 @@ func ChainReaderOptionK[R, A, B, E any](onNone Lazy[E]) func(readeroption.Kleisl
|
||||
}
|
||||
}
|
||||
|
||||
// ChainFirstReaderOptionK returns a function that chains a ReaderOption-returning function
|
||||
// while preserving the original value. When the Option is None, the provided error value is used.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - R: The context/environment type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
// - E: The error type
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - onNone: Lazy function that provides the error value when Option is None
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Function that takes a ReaderOption Kleisli and returns an Operator
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstReaderOptionK[R, A, B, E any](onNone Lazy[E]) func(readeroption.Kleisli[R, A, B]) Operator[R, E, A, A] {
|
||||
fro := FromReaderOption[R, B](onNone)
|
||||
@@ -371,6 +747,25 @@ func ChainFirstReaderOptionK[R, A, B, E any](onNone Lazy[E]) func(readeroption.K
|
||||
}
|
||||
}
|
||||
|
||||
// TapReaderOptionK is an alias for ChainFirstReaderOptionK.
|
||||
// It returns a function that chains a ReaderOption-returning side effect while preserving
|
||||
// the original value.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - R: The context/environment type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
// - E: The error type
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - onNone: Lazy function that provides the error value when Option is None
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Function that takes a ReaderOption Kleisli and returns an Operator
|
||||
//
|
||||
//go:inline
|
||||
func TapReaderOptionK[R, A, B, E any](onNone Lazy[E]) func(readeroption.Kleisli[R, A, B]) Operator[R, E, A, A] {
|
||||
return ChainFirstReaderOptionK[R, A, B](onNone)
|
||||
@@ -401,6 +796,110 @@ func ChainIOEitherK[R, E, A, B any](f IOE.Kleisli[E, A, B]) Operator[R, E, A, B]
|
||||
)
|
||||
}
|
||||
|
||||
// ChainFirstIOEitherK chains an IOEither computation while preserving the original value.
|
||||
// This is useful for performing side effects that may fail (like logging, validation, or
|
||||
// external API calls) while keeping the original value in the success case.
|
||||
//
|
||||
// The function executes the IOEither computation but discards its result, returning the
|
||||
// original value if both computations succeed. If either computation fails, the error
|
||||
// is propagated.
|
||||
//
|
||||
// This is the curried version that returns an Operator for use in function composition.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - R: The context/environment type
|
||||
// - E: The error type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: IOEither.Kleisli function that performs the side effect
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Operator that chains the side effect while preserving the original value
|
||||
//
|
||||
// # Example Usage
|
||||
//
|
||||
// type Config struct{ LogEnabled bool }
|
||||
//
|
||||
// logValue := func(v int) IOEither[error, string] {
|
||||
// return IOE.Of[error](fmt.Sprintf("Value: %d", v))
|
||||
// }
|
||||
//
|
||||
// pipeline := F.Pipe1(
|
||||
// Of[Config, error](42),
|
||||
// ChainFirstIOEitherK[Config](logValue),
|
||||
// )
|
||||
// result := pipeline(Config{LogEnabled: true})() // Right(42)
|
||||
//
|
||||
// # See Also
|
||||
//
|
||||
// - TapIOEitherK: Alias for ChainFirstIOEitherK
|
||||
// - ChainIOEitherK: Chains IOEither and uses its result
|
||||
// - ChainFirstEitherK: Similar but for Either computations
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstIOEitherK[R, E, A, B any](f IOE.Kleisli[E, A, B]) Operator[R, E, A, A] {
|
||||
return fromioeither.ChainFirstIOEitherK(
|
||||
Chain[R, E, A, A],
|
||||
Map[R, E, B, A],
|
||||
FromIOEither[R, E, B],
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
// TapIOEitherK is an alias for ChainFirstIOEitherK.
|
||||
// It executes an IOEither side effect while preserving the original value.
|
||||
//
|
||||
// The name "Tap" emphasizes the side-effect nature of the operation, similar to
|
||||
// tapping into a pipeline to observe or log values without modifying the flow.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - R: The context/environment type
|
||||
// - E: The error type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: IOEither.Kleisli function that performs the side effect
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Operator that executes the side effect while preserving the original value
|
||||
//
|
||||
// # Example Usage
|
||||
//
|
||||
// type Config struct{}
|
||||
//
|
||||
// validatePositive := func(v int) IOEither[error, bool] {
|
||||
// if v > 0 {
|
||||
// return IOE.Of[error](true)
|
||||
// }
|
||||
// return IOE.Left[bool](errors.New("must be positive"))
|
||||
// }
|
||||
//
|
||||
// pipeline := F.Pipe1(
|
||||
// Of[Config, error](42),
|
||||
// TapIOEitherK[Config](validatePositive),
|
||||
// )
|
||||
// result := pipeline(Config{})() // Right(42) if validation passes
|
||||
//
|
||||
// # See Also
|
||||
//
|
||||
// - ChainFirstIOEitherK: The underlying implementation
|
||||
// - TapEitherK: Similar but for Either computations
|
||||
// - TapIOK: Similar but for IO computations
|
||||
//
|
||||
//go:inline
|
||||
func TapIOEitherK[R, E, A, B any](f IOE.Kleisli[E, A, B]) Operator[R, E, A, A] {
|
||||
return ChainFirstIOEitherK[R](f)
|
||||
}
|
||||
|
||||
// MonadChainIOK chains an IO-returning computation into a ReaderIOEither.
|
||||
// The IO is automatically lifted into the ReaderIOEither context (always succeeds).
|
||||
//
|
||||
@@ -440,6 +939,25 @@ func MonadChainFirstIOK[R, E, A, B any](ma ReaderIOEither[R, E, A], f io.Kleisli
|
||||
)
|
||||
}
|
||||
|
||||
// MonadTapIOK is an alias for MonadChainFirstIOK.
|
||||
// It chains an IO-returning side effect while preserving the original value.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - R: The context/environment type
|
||||
// - E: The error type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - ma: The ReaderIOEither computation
|
||||
// - f: The IO-returning side effect function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - ReaderIOEither with the original value preserved
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapIOK[R, E, A, B any](ma ReaderIOEither[R, E, A], f io.Kleisli[A, B]) ReaderIOEither[R, E, A] {
|
||||
return MonadChainFirstIOK(ma, f)
|
||||
@@ -458,6 +976,25 @@ func ChainFirstIOK[R, E, A, B any](f io.Kleisli[A, B]) Operator[R, E, A, A] {
|
||||
)
|
||||
}
|
||||
|
||||
// TapIOK is an alias for ChainFirstIOK.
|
||||
// It returns a function that chains an IO-returning side effect while preserving
|
||||
// the original value.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - R: The context/environment type
|
||||
// - E: The error type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: The IO-returning side effect function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Operator that executes the side effect while preserving the original value
|
||||
//
|
||||
//go:inline
|
||||
func TapIOK[R, E, A, B any](f io.Kleisli[A, B]) Operator[R, E, A, A] {
|
||||
return ChainFirstIOK[R, E](f)
|
||||
@@ -540,6 +1077,25 @@ func ChainFirst[R, E, A, B any](f Kleisli[R, E, A, B]) Operator[R, E, A, A] {
|
||||
f)
|
||||
}
|
||||
|
||||
// Tap is an alias for ChainFirst.
|
||||
// It returns a function that sequences computations but keeps the first result,
|
||||
// emphasizing the side-effect nature of the operation.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - R: The context/environment type
|
||||
// - E: The error type
|
||||
// - A: The input value type (preserved in output)
|
||||
// - B: The side effect result type (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: The Kleisli arrow for the side effect
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Operator that executes the side effect while preserving the original value
|
||||
//
|
||||
//go:inline
|
||||
func Tap[R, E, A, B any](f Kleisli[R, E, A, B]) Operator[R, E, A, A] {
|
||||
return ChainFirst(f)
|
||||
|
||||
@@ -733,3 +733,390 @@ func TestChainLeftIdenticalToOrElse(t *testing.T) {
|
||||
assert.Equal(t, cfg, *chainLeftCfg)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChainFirstIOEitherK(t *testing.T) {
|
||||
type Config struct {
|
||||
logEnabled bool
|
||||
}
|
||||
|
||||
t.Run("Success - preserves original value", func(t *testing.T) {
|
||||
sideEffectRan := false
|
||||
|
||||
logValue := func(v int) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
sideEffectRan = true
|
||||
return E.Right[error](fmt.Sprintf("Logged: %d", v))
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config, error](42),
|
||||
ChainFirstIOEitherK[Config](logValue),
|
||||
)
|
||||
|
||||
result := pipeline(Config{logEnabled: true})()
|
||||
assert.Equal(t, E.Right[error](42), result)
|
||||
assert.True(t, sideEffectRan)
|
||||
})
|
||||
|
||||
t.Run("Success - side effect result is discarded", func(t *testing.T) {
|
||||
sideEffect := func(v int) IOE.IOEither[error, string] {
|
||||
return IOE.Of[error]("side effect result")
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config, error](100),
|
||||
ChainFirstIOEitherK[Config](sideEffect),
|
||||
)
|
||||
|
||||
result := pipeline(Config{})()
|
||||
assert.Equal(t, E.Right[error](100), result)
|
||||
})
|
||||
|
||||
t.Run("Failure - side effect fails", func(t *testing.T) {
|
||||
sideEffectError := errors.New("side effect failed")
|
||||
|
||||
failingSideEffect := func(v int) IOE.IOEither[error, string] {
|
||||
return IOE.Left[string](sideEffectError)
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config, error](42),
|
||||
ChainFirstIOEitherK[Config](failingSideEffect),
|
||||
)
|
||||
|
||||
result := pipeline(Config{})()
|
||||
assert.Equal(t, E.Left[int](sideEffectError), result)
|
||||
})
|
||||
|
||||
t.Run("Failure - original computation fails", func(t *testing.T) {
|
||||
originalError := errors.New("original failed")
|
||||
sideEffectRan := false
|
||||
|
||||
sideEffect := func(v int) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
sideEffectRan = true
|
||||
return E.Right[error]("logged")
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Left[Config, int](originalError),
|
||||
ChainFirstIOEitherK[Config](sideEffect),
|
||||
)
|
||||
|
||||
result := pipeline(Config{})()
|
||||
assert.Equal(t, E.Left[int](originalError), result)
|
||||
assert.False(t, sideEffectRan, "Side effect should not run when original computation fails")
|
||||
})
|
||||
|
||||
t.Run("Chaining multiple side effects", func(t *testing.T) {
|
||||
log1Ran := false
|
||||
log2Ran := false
|
||||
|
||||
log1 := func(v int) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
log1Ran = true
|
||||
return E.Right[error]("log1")
|
||||
}
|
||||
}
|
||||
|
||||
log2 := func(v int) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
log2Ran = true
|
||||
return E.Right[error]("log2")
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe2(
|
||||
Of[Config, error](42),
|
||||
ChainFirstIOEitherK[Config](log1),
|
||||
ChainFirstIOEitherK[Config](log2),
|
||||
)
|
||||
|
||||
result := pipeline(Config{})()
|
||||
assert.Equal(t, E.Right[error](42), result)
|
||||
assert.True(t, log1Ran)
|
||||
assert.True(t, log2Ran)
|
||||
})
|
||||
|
||||
t.Run("Integration with Map", func(t *testing.T) {
|
||||
validate := func(v int) IOE.IOEither[error, bool] {
|
||||
if v > 0 {
|
||||
return IOE.Of[error](true)
|
||||
}
|
||||
return IOE.Left[bool](errors.New("must be positive"))
|
||||
}
|
||||
|
||||
pipeline := F.Pipe2(
|
||||
Of[Config, error](10),
|
||||
ChainFirstIOEitherK[Config](validate),
|
||||
Map[Config, error](func(x int) int { return x * 2 }),
|
||||
)
|
||||
|
||||
result := pipeline(Config{})()
|
||||
assert.Equal(t, E.Right[error](20), result)
|
||||
})
|
||||
|
||||
t.Run("Different types for input and side effect result", func(t *testing.T) {
|
||||
convertToString := func(v int) IOE.IOEither[error, string] {
|
||||
return IOE.Of[error](fmt.Sprintf("Value: %d", v))
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config, error](123),
|
||||
ChainFirstIOEitherK[Config](convertToString),
|
||||
)
|
||||
|
||||
result := pipeline(Config{})()
|
||||
assert.Equal(t, E.Right[error](123), result)
|
||||
})
|
||||
|
||||
t.Run("With context-dependent side effect", func(t *testing.T) {
|
||||
logged := ""
|
||||
|
||||
logIfEnabled := func(v int) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
logged = fmt.Sprintf("Logged: %d", v)
|
||||
return E.Right[error](logged)
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config, error](99),
|
||||
ChainFirstIOEitherK[Config](logIfEnabled),
|
||||
)
|
||||
|
||||
result := pipeline(Config{logEnabled: true})()
|
||||
assert.Equal(t, E.Right[error](99), result)
|
||||
assert.Equal(t, "Logged: 99", logged)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTapIOEitherK(t *testing.T) {
|
||||
type Config struct {
|
||||
debugMode bool
|
||||
}
|
||||
|
||||
t.Run("Success - preserves original value", func(t *testing.T) {
|
||||
tapped := false
|
||||
|
||||
tapFunc := func(v int) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
tapped = true
|
||||
return E.Right[error](fmt.Sprintf("Tapped: %d", v))
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config, error](42),
|
||||
TapIOEitherK[Config](tapFunc),
|
||||
)
|
||||
|
||||
result := pipeline(Config{})()
|
||||
assert.Equal(t, E.Right[error](42), result)
|
||||
assert.True(t, tapped)
|
||||
})
|
||||
|
||||
t.Run("Failure - tap fails", func(t *testing.T) {
|
||||
tapError := errors.New("tap failed")
|
||||
|
||||
tapFunc := func(v int) IOE.IOEither[error, string] {
|
||||
return IOE.Left[string](tapError)
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config, error](42),
|
||||
TapIOEitherK[Config](tapFunc),
|
||||
)
|
||||
|
||||
result := pipeline(Config{})()
|
||||
assert.Equal(t, E.Left[int](tapError), result)
|
||||
})
|
||||
|
||||
t.Run("Failure - original computation fails", func(t *testing.T) {
|
||||
originalError := errors.New("original failed")
|
||||
tapped := false
|
||||
|
||||
tapFunc := func(v int) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
tapped = true
|
||||
return E.Right[error]("tapped")
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Left[Config, int](originalError),
|
||||
TapIOEitherK[Config](tapFunc),
|
||||
)
|
||||
|
||||
result := pipeline(Config{})()
|
||||
assert.Equal(t, E.Left[int](originalError), result)
|
||||
assert.False(t, tapped, "Tap should not run when original computation fails")
|
||||
})
|
||||
|
||||
t.Run("Validation use case", func(t *testing.T) {
|
||||
validatePositive := func(v int) IOE.IOEither[error, bool] {
|
||||
if v > 0 {
|
||||
return IOE.Of[error](true)
|
||||
}
|
||||
return IOE.Left[bool](errors.New("must be positive"))
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config, error](42),
|
||||
TapIOEitherK[Config](validatePositive),
|
||||
)
|
||||
|
||||
result := pipeline(Config{})()
|
||||
assert.Equal(t, E.Right[error](42), result)
|
||||
})
|
||||
|
||||
t.Run("Validation failure", func(t *testing.T) {
|
||||
validatePositive := func(v int) IOE.IOEither[error, bool] {
|
||||
if v > 0 {
|
||||
return IOE.Of[error](true)
|
||||
}
|
||||
return IOE.Left[bool](errors.New("must be positive"))
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config, error](-5),
|
||||
TapIOEitherK[Config](validatePositive),
|
||||
)
|
||||
|
||||
result := pipeline(Config{})()
|
||||
assert.True(t, E.IsLeft(result))
|
||||
})
|
||||
|
||||
t.Run("Multiple taps in sequence", func(t *testing.T) {
|
||||
tap1Ran := false
|
||||
tap2Ran := false
|
||||
tap3Ran := false
|
||||
|
||||
tap1 := func(v int) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
tap1Ran = true
|
||||
return E.Right[error]("tap1")
|
||||
}
|
||||
}
|
||||
|
||||
tap2 := func(v int) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
tap2Ran = true
|
||||
return E.Right[error]("tap2")
|
||||
}
|
||||
}
|
||||
|
||||
tap3 := func(v int) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
tap3Ran = true
|
||||
return E.Right[error]("tap3")
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe3(
|
||||
Of[Config, error](100),
|
||||
TapIOEitherK[Config](tap1),
|
||||
TapIOEitherK[Config](tap2),
|
||||
TapIOEitherK[Config](tap3),
|
||||
)
|
||||
|
||||
result := pipeline(Config{})()
|
||||
assert.Equal(t, E.Right[error](100), result)
|
||||
assert.True(t, tap1Ran)
|
||||
assert.True(t, tap2Ran)
|
||||
assert.True(t, tap3Ran)
|
||||
})
|
||||
|
||||
t.Run("Tap with transformation pipeline", func(t *testing.T) {
|
||||
tappedValue := 0
|
||||
|
||||
tapFunc := func(v int) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
tappedValue = v
|
||||
return E.Right[error]("tapped")
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe3(
|
||||
Of[Config, error](10),
|
||||
Map[Config, error](func(x int) int { return x * 2 }),
|
||||
TapIOEitherK[Config](tapFunc),
|
||||
Map[Config, error](func(x int) int { return x + 5 }),
|
||||
)
|
||||
|
||||
result := pipeline(Config{})()
|
||||
assert.Equal(t, E.Right[error](25), result)
|
||||
assert.Equal(t, 20, tappedValue)
|
||||
})
|
||||
|
||||
t.Run("Tap is alias for ChainFirstIOEitherK", func(t *testing.T) {
|
||||
// Verify that TapIOEitherK and ChainFirstIOEitherK produce identical results
|
||||
sideEffect := func(v int) IOE.IOEither[error, string] {
|
||||
return IOE.Of[error](fmt.Sprintf("Value: %d", v))
|
||||
}
|
||||
|
||||
pipelineWithTap := F.Pipe1(
|
||||
Of[Config, error](42),
|
||||
TapIOEitherK[Config](sideEffect),
|
||||
)
|
||||
|
||||
pipelineWithChainFirst := F.Pipe1(
|
||||
Of[Config, error](42),
|
||||
ChainFirstIOEitherK[Config](sideEffect),
|
||||
)
|
||||
|
||||
resultTap := pipelineWithTap(Config{})()
|
||||
resultChainFirst := pipelineWithChainFirst(Config{})()
|
||||
|
||||
assert.Equal(t, resultChainFirst, resultTap)
|
||||
})
|
||||
|
||||
t.Run("Logging use case", func(t *testing.T) {
|
||||
logs := []string{}
|
||||
|
||||
logValue := func(v int) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
logMsg := fmt.Sprintf("Processing value: %d", v)
|
||||
logs = append(logs, logMsg)
|
||||
return E.Right[error](logMsg)
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe2(
|
||||
Of[Config, error](5),
|
||||
TapIOEitherK[Config](logValue),
|
||||
Map[Config, error](func(x int) int { return x * x }),
|
||||
)
|
||||
|
||||
result := pipeline(Config{debugMode: true})()
|
||||
assert.Equal(t, E.Right[error](25), result)
|
||||
assert.Len(t, logs, 1)
|
||||
assert.Equal(t, "Processing value: 5", logs[0])
|
||||
})
|
||||
|
||||
t.Run("Error propagation in tap chain", func(t *testing.T) {
|
||||
tap1 := func(v int) IOE.IOEither[error, string] {
|
||||
return IOE.Of[error]("tap1")
|
||||
}
|
||||
|
||||
tap2 := func(v int) IOE.IOEither[error, string] {
|
||||
return IOE.Left[string](errors.New("tap2 failed"))
|
||||
}
|
||||
|
||||
tap3 := func(v int) IOE.IOEither[error, string] {
|
||||
return IOE.Of[error]("tap3")
|
||||
}
|
||||
|
||||
pipeline := F.Pipe3(
|
||||
Of[Config, error](42),
|
||||
TapIOEitherK[Config](tap1),
|
||||
TapIOEitherK[Config](tap2),
|
||||
TapIOEitherK[Config](tap3),
|
||||
)
|
||||
|
||||
result := pipeline(Config{})()
|
||||
assert.True(t, E.IsLeft(result))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
|
||||
"github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/io"
|
||||
"github.com/IBM/fp-go/v2/ioresult"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
RE "github.com/IBM/fp-go/v2/readereither"
|
||||
"github.com/IBM/fp-go/v2/readerio"
|
||||
@@ -28,6 +29,9 @@ import (
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
)
|
||||
|
||||
// FromReaderOption converts a ReaderOption to a ReaderIOResult.
|
||||
// If the ReaderOption is None, the provided function is called to produce the error.
|
||||
//
|
||||
//go:inline
|
||||
func FromReaderOption[R, A any](onNone Lazy[error]) Kleisli[R, ReaderOption[R, A], A] {
|
||||
return RIOE.FromReaderOption[R, A](onNone)
|
||||
@@ -105,6 +109,9 @@ func MonadChainFirst[R, A, B any](fa ReaderIOResult[R, A], f Kleisli[R, A, B]) R
|
||||
return RIOE.MonadChainFirst(fa, f)
|
||||
}
|
||||
|
||||
// MonadTap is an alias for MonadChainFirst, executing a side effect while preserving the original value.
|
||||
// The name "Tap" emphasizes the side-effect nature of the operation.
|
||||
//
|
||||
//go:inline
|
||||
func MonadTap[R, A, B any](fa ReaderIOResult[R, A], f Kleisli[R, A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadTap(fa, f)
|
||||
@@ -150,6 +157,8 @@ func MonadChainFirstEitherK[R, A, B any](ma ReaderIOResult[R, A], f result.Kleis
|
||||
return RIOE.MonadChainFirstEitherK(ma, f)
|
||||
}
|
||||
|
||||
// MonadTapEitherK is an alias for MonadChainFirstEitherK, executing a Result side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapEitherK[R, A, B any](ma ReaderIOResult[R, A], f result.Kleisli[A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadTapEitherK(ma, f)
|
||||
@@ -163,11 +172,43 @@ func ChainFirstEitherK[R, A, B any](f result.Kleisli[A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstEitherK[R](f)
|
||||
}
|
||||
|
||||
// TapEitherK is an alias for ChainFirstEitherK, executing a Result side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func TapEitherK[R, A, B any](f result.Kleisli[A, B]) Operator[R, A, A] {
|
||||
return RIOE.TapEitherK[R](f)
|
||||
}
|
||||
|
||||
// ChainFirstIOEitherK chains an IOResult computation while preserving the original value.
|
||||
// Useful for performing side effects that may fail while keeping the original value.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstIOEitherK[R, A, B any](f ioresult.Kleisli[A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstIOEitherK[R](f)
|
||||
}
|
||||
|
||||
// TapIOEitherK is an alias for ChainFirstIOEitherK, executing an IOResult side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func TapIOEitherK[R, A, B any](f ioresult.Kleisli[A, B]) Operator[R, A, A] {
|
||||
return RIOE.TapIOEitherK[R](f)
|
||||
}
|
||||
|
||||
// ChainFirstIOResultK chains an IOResult computation while preserving the original value.
|
||||
// This is an alias for ChainFirstIOEitherK with more explicit naming.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstIOResultK[R, A, B any](f ioresult.Kleisli[A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstIOEitherK[R](f)
|
||||
}
|
||||
|
||||
// TapIOResultK is an alias for ChainFirstIOResultK, executing an IOResult side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func TapIOResultK[R, A, B any](f ioresult.Kleisli[A, B]) Operator[R, A, A] {
|
||||
return RIOE.TapIOEitherK[R](f)
|
||||
}
|
||||
|
||||
// MonadChainFirstEitherK chains an Either-returning computation but keeps the original value.
|
||||
// Useful for validation or side effects that return Either.
|
||||
//
|
||||
@@ -176,19 +217,23 @@ func MonadChainFirstResultK[R, A, B any](ma ReaderIOResult[R, A], f result.Kleis
|
||||
return RIOE.MonadChainFirstEitherK(ma, f)
|
||||
}
|
||||
|
||||
// MonadTapResultK is an alias for MonadChainFirstResultK, executing a Result side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapResultK[R, A, B any](ma ReaderIOResult[R, A], f result.Kleisli[A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadTapEitherK(ma, f)
|
||||
}
|
||||
|
||||
// ChainFirstEitherK returns a function that chains an Either computation while preserving the original value.
|
||||
// This is the curried version of MonadChainFirstEitherK.
|
||||
// ChainFirstResultK returns a function that chains a Result computation while preserving the original value.
|
||||
// This is an alias for ChainFirstEitherK with more explicit naming.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstResultK[R, A, B any](f result.Kleisli[A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstEitherK[R](f)
|
||||
}
|
||||
|
||||
// TapResultK is an alias for ChainFirstResultK, executing a Result side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func TapResultK[R, A, B any](f result.Kleisli[A, B]) Operator[R, A, A] {
|
||||
return RIOE.TapEitherK[R](f)
|
||||
@@ -210,36 +255,54 @@ func ChainReaderK[R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, A, B] {
|
||||
return RIOE.ChainReaderK[error](f)
|
||||
}
|
||||
|
||||
// MonadChainFirstReaderK chains a Reader computation but keeps the original value.
|
||||
// Useful for performing Reader-based side effects while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirstReaderK[R, A, B any](ma ReaderIOResult[R, A], f reader.Kleisli[R, A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadChainFirstReaderK(ma, f)
|
||||
}
|
||||
|
||||
// MonadTapReaderK is an alias for MonadChainFirstReaderK, executing a Reader side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapReaderK[R, A, B any](ma ReaderIOResult[R, A], f reader.Kleisli[R, A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadTapReaderK(ma, f)
|
||||
}
|
||||
|
||||
// ChainFirstReaderK returns a function that chains a Reader computation while preserving the original value.
|
||||
// This is the curried version of MonadChainFirstReaderK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstReaderK[R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstReaderK[error](f)
|
||||
}
|
||||
|
||||
// TapReaderK is an alias for ChainFirstReaderK, executing a Reader side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func TapReaderK[R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, A, A] {
|
||||
return RIOE.TapReaderK[error](f)
|
||||
}
|
||||
|
||||
// ChainReaderOptionK returns a function that chains a ReaderOption-returning function into ReaderIOResult.
|
||||
// If the ReaderOption is None, the provided error function is called.
|
||||
//
|
||||
//go:inline
|
||||
func ChainReaderOptionK[R, A, B any](onNone Lazy[error]) func(readeroption.Kleisli[R, A, B]) Operator[R, A, B] {
|
||||
return RIOE.ChainReaderOptionK[R, A, B](onNone)
|
||||
}
|
||||
|
||||
// ChainFirstReaderOptionK chains a ReaderOption computation while preserving the original value.
|
||||
// If the ReaderOption is None, the provided error function is called.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstReaderOptionK[R, A, B any](onNone Lazy[error]) func(readeroption.Kleisli[R, A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstReaderOptionK[R, A, B](onNone)
|
||||
}
|
||||
|
||||
// TapReaderOptionK is an alias for ChainFirstReaderOptionK, executing a ReaderOption side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func TapReaderOptionK[R, A, B any](onNone Lazy[error]) func(readeroption.Kleisli[R, A, B]) Operator[R, A, A] {
|
||||
return RIOE.TapReaderOptionK[R, A, B](onNone)
|
||||
@@ -261,84 +324,123 @@ func ChainReaderEitherK[R, A, B any](f RE.Kleisli[R, error, A, B]) Operator[R, A
|
||||
return RIOE.ChainReaderEitherK(f)
|
||||
}
|
||||
|
||||
// MonadChainFirstReaderEitherK chains a ReaderEither computation but keeps the original value.
|
||||
// Useful for performing ReaderEither-based side effects while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirstReaderEitherK[R, A, B any](ma ReaderIOResult[R, A], f RE.Kleisli[R, error, A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadChainFirstReaderEitherK(ma, f)
|
||||
}
|
||||
|
||||
// MonadTapReaderEitherK is an alias for MonadChainFirstReaderEitherK, executing a ReaderEither side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapReaderEitherK[R, A, B any](ma ReaderIOResult[R, A], f RE.Kleisli[R, error, A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadTapReaderEitherK(ma, f)
|
||||
}
|
||||
|
||||
// ChainFirstReaderEitherK returns a function that chains a ReaderEither computation while preserving the original value.
|
||||
// This is the curried version of MonadChainFirstReaderEitherK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstReaderEitherK[R, A, B any](f RE.Kleisli[R, error, A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstReaderEitherK(f)
|
||||
}
|
||||
|
||||
// TapReaderEitherK is an alias for ChainFirstReaderEitherK, executing a ReaderEither side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func TapReaderEitherK[R, A, B any](f RE.Kleisli[R, error, A, B]) Operator[R, A, A] {
|
||||
return RIOE.TapReaderEitherK(f)
|
||||
}
|
||||
|
||||
// MonadChainReaderResultK chains a ReaderResult-returning computation into a ReaderIOResult.
|
||||
// This is an alias for MonadChainReaderEitherK with more explicit naming.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainReaderResultK[R, A, B any](ma ReaderIOResult[R, A], f RE.Kleisli[R, error, A, B]) ReaderIOResult[R, B] {
|
||||
return RIOE.MonadChainReaderEitherK(ma, f)
|
||||
}
|
||||
|
||||
// ChainReaderK returns a function that chains a Reader-returning function into ReaderIOResult.
|
||||
// This is the curried version of MonadChainReaderK.
|
||||
// ChainReaderResultK returns a function that chains a ReaderResult-returning function into ReaderIOResult.
|
||||
// This is an alias for ChainReaderEitherK with more explicit naming.
|
||||
//
|
||||
//go:inline
|
||||
func ChainReaderResultK[R, A, B any](f RE.Kleisli[R, error, A, B]) Operator[R, A, B] {
|
||||
return RIOE.ChainReaderEitherK(f)
|
||||
}
|
||||
|
||||
// MonadChainFirstReaderResultK chains a ReaderResult computation but keeps the original value.
|
||||
// This is an alias for MonadChainFirstReaderEitherK with more explicit naming.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirstReaderResultK[R, A, B any](ma ReaderIOResult[R, A], f RE.Kleisli[R, error, A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadChainFirstReaderEitherK(ma, f)
|
||||
}
|
||||
|
||||
// MonadTapReaderResultK is an alias for MonadChainFirstReaderResultK, executing a ReaderResult side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapReaderResultK[R, A, B any](ma ReaderIOResult[R, A], f RE.Kleisli[R, error, A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadTapReaderEitherK(ma, f)
|
||||
}
|
||||
|
||||
// ChainFirstReaderResultK returns a function that chains a ReaderResult computation while preserving the original value.
|
||||
// This is an alias for ChainFirstReaderEitherK with more explicit naming.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstReaderResultK[R, A, B any](f RE.Kleisli[R, error, A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstReaderEitherK(f)
|
||||
}
|
||||
|
||||
// TapReaderResultK is an alias for ChainFirstReaderResultK, executing a ReaderResult side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func TapReaderResultK[R, A, B any](f RE.Kleisli[R, error, A, B]) Operator[R, A, A] {
|
||||
return RIOE.TapReaderEitherK(f)
|
||||
}
|
||||
|
||||
// MonadChainReaderIOK chains a ReaderIO-returning computation into a ReaderIOResult.
|
||||
// The ReaderIO is automatically lifted into the ReaderIOResult context (always succeeds).
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainReaderIOK[R, A, B any](ma ReaderIOResult[R, A], f readerio.Kleisli[R, A, B]) ReaderIOResult[R, B] {
|
||||
return RIOE.MonadChainReaderIOK(ma, f)
|
||||
}
|
||||
|
||||
// ChainReaderIOK returns a function that chains a ReaderIO-returning function into ReaderIOResult.
|
||||
// This is the curried version of MonadChainReaderIOK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainReaderIOK[R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, A, B] {
|
||||
return RIOE.ChainReaderIOK[error](f)
|
||||
}
|
||||
|
||||
// MonadChainFirstReaderIOK chains a ReaderIO computation but keeps the original value.
|
||||
// Useful for performing ReaderIO-based side effects while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirstReaderIOK[R, A, B any](ma ReaderIOResult[R, A], f readerio.Kleisli[R, A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadChainFirstReaderIOK(ma, f)
|
||||
}
|
||||
|
||||
// MonadTapReaderIOK is an alias for MonadChainFirstReaderIOK, executing a ReaderIO side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapReaderIOK[R, A, B any](ma ReaderIOResult[R, A], f readerio.Kleisli[R, A, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadTapReaderIOK(ma, f)
|
||||
}
|
||||
|
||||
// ChainFirstReaderIOK returns a function that chains a ReaderIO computation while preserving the original value.
|
||||
// This is the curried version of MonadChainFirstReaderIOK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstReaderIOK[R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstReaderIOK[error](f)
|
||||
}
|
||||
|
||||
// TapReaderIOK is an alias for ChainFirstReaderIOK, executing a ReaderIO side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func TapReaderIOK[R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, A, A] {
|
||||
return RIOE.TapReaderIOK[error](f)
|
||||
@@ -400,6 +502,8 @@ func MonadChainFirstIOK[R, A, B any](ma ReaderIOResult[R, A], f func(A) IO[B]) R
|
||||
return RIOE.MonadChainFirstIOK(ma, f)
|
||||
}
|
||||
|
||||
// MonadTapIOK is an alias for MonadChainFirstIOK, executing an IO side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapIOK[R, A, B any](ma ReaderIOResult[R, A], f func(A) IO[B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadTapIOK(ma, f)
|
||||
@@ -413,6 +517,8 @@ func ChainFirstIOK[R, A, B any](f func(A) IO[B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstIOK[R, error](f)
|
||||
}
|
||||
|
||||
// TapIOK is an alias for ChainFirstIOK, executing an IO side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func TapIOK[R, A, B any](f func(A) IO[B]) Operator[R, A, A] {
|
||||
return RIOE.TapIOK[R, error](f)
|
||||
@@ -473,6 +579,9 @@ func ChainFirst[R, A, B any](f Kleisli[R, A, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirst(f)
|
||||
}
|
||||
|
||||
// Tap is an alias for ChainFirst, executing a side effect while preserving the original value.
|
||||
// The name "Tap" emphasizes the side-effect nature of the operation.
|
||||
//
|
||||
//go:inline
|
||||
func Tap[R, A, B any](f Kleisli[R, A, B]) Operator[R, A, A] {
|
||||
return RIOE.Tap(f)
|
||||
@@ -767,46 +876,70 @@ func Local[A, R1, R2 any](f func(R2) R1) func(ReaderIOResult[R1, A]) ReaderIORes
|
||||
return RIOE.Local[error, A](f)
|
||||
}
|
||||
|
||||
// Read executes a ReaderIOResult by providing a concrete environment value.
|
||||
// This converts a ReaderIOResult[R, A] into an IOResult[A] by supplying the R value.
|
||||
//
|
||||
//go:inline
|
||||
func Read[A, R any](r R) func(ReaderIOResult[R, A]) IOResult[A] {
|
||||
return RIOE.Read[error, A](r)
|
||||
}
|
||||
|
||||
// MonadChainLeft chains a computation on the error channel, allowing error recovery or transformation.
|
||||
// If the computation is successful (Right), it passes through unchanged.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainLeft[R, A any](fa ReaderIOResult[R, A], f Kleisli[R, error, A]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadChainLeft(fa, f)
|
||||
}
|
||||
|
||||
// ChainLeft returns a function that chains a computation on the error channel.
|
||||
// This is the curried version of MonadChainLeft.
|
||||
//
|
||||
//go:inline
|
||||
func ChainLeft[R, A any](f Kleisli[R, error, A]) func(ReaderIOResult[R, A]) ReaderIOResult[R, A] {
|
||||
return RIOE.ChainLeft(f)
|
||||
}
|
||||
|
||||
// MonadChainFirstLeft chains an error-handling computation but preserves the original error.
|
||||
// Useful for logging or side effects on errors without changing the error value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirstLeft[A, R, B any](ma ReaderIOResult[R, A], f Kleisli[R, error, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadChainFirstLeft(ma, f)
|
||||
}
|
||||
|
||||
// MonadTapLeft is an alias for MonadChainFirstLeft, executing a side effect on errors while preserving the original error.
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapLeft[A, R, B any](ma ReaderIOResult[R, A], f Kleisli[R, error, B]) ReaderIOResult[R, A] {
|
||||
return RIOE.MonadTapLeft(ma, f)
|
||||
}
|
||||
|
||||
// ChainFirstLeft returns a function that chains an error-handling computation while preserving the original error.
|
||||
// This is the curried version of MonadChainFirstLeft.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstLeft[A, R, B any](f Kleisli[R, error, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstLeft[A](f)
|
||||
}
|
||||
|
||||
// ChainFirstLeftIOK chains an IO computation on errors while preserving the original error.
|
||||
// Useful for IO-based error logging or side effects.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstLeftIOK[A, R, B any](f io.Kleisli[error, B]) Operator[R, A, A] {
|
||||
return RIOE.ChainFirstLeftIOK[A, R](f)
|
||||
}
|
||||
|
||||
// TapLeft is an alias for ChainFirstLeft, executing a side effect on errors while preserving the original error.
|
||||
//
|
||||
//go:inline
|
||||
func TapLeft[A, R, B any](f Kleisli[R, error, B]) Operator[R, A, A] {
|
||||
return RIOE.TapLeft[A](f)
|
||||
}
|
||||
|
||||
// TapLeftIOK is an alias for ChainFirstLeftIOK, executing an IO side effect on errors while preserving the original error.
|
||||
//
|
||||
//go:inline
|
||||
func TapLeftIOK[A, R, B any](f io.Kleisli[error, B]) Operator[R, A, A] {
|
||||
return RIOE.TapLeftIOK[A, R](f)
|
||||
|
||||
@@ -619,3 +619,453 @@ func TestLocalIOResultK(t *testing.T) {
|
||||
assert.True(t, result.IsLeft(resErr))
|
||||
})
|
||||
}
|
||||
|
||||
func TestChainFirstIOResultK(t *testing.T) {
|
||||
type Config struct {
|
||||
logEnabled bool
|
||||
}
|
||||
|
||||
t.Run("Success - preserves original value", func(t *testing.T) {
|
||||
sideEffectRan := false
|
||||
|
||||
logValue := func(v int) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
sideEffectRan = true
|
||||
return result.Of(fmt.Sprintf("Logged: %d", v))
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config](42),
|
||||
ChainFirstIOResultK[Config](logValue),
|
||||
)
|
||||
|
||||
res := pipeline(Config{logEnabled: true})()
|
||||
assert.Equal(t, result.Of(42), res)
|
||||
assert.True(t, sideEffectRan)
|
||||
})
|
||||
|
||||
t.Run("Failure - side effect fails", func(t *testing.T) {
|
||||
sideEffectError := errors.New("side effect failed")
|
||||
|
||||
failingSideEffect := func(v int) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
return result.Left[string](sideEffectError)
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config](42),
|
||||
ChainFirstIOResultK[Config](failingSideEffect),
|
||||
)
|
||||
|
||||
res := pipeline(Config{})()
|
||||
assert.True(t, E.IsLeft(res))
|
||||
})
|
||||
|
||||
t.Run("Failure - original computation fails", func(t *testing.T) {
|
||||
originalError := errors.New("original failed")
|
||||
sideEffectRan := false
|
||||
|
||||
sideEffect := func(v int) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
sideEffectRan = true
|
||||
return result.Of("logged")
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Left[Config, int](originalError),
|
||||
ChainFirstIOResultK[Config](sideEffect),
|
||||
)
|
||||
|
||||
res := pipeline(Config{})()
|
||||
assert.Equal(t, result.Left[int](originalError), res)
|
||||
assert.False(t, sideEffectRan)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTapIOResultK(t *testing.T) {
|
||||
type Config struct{}
|
||||
|
||||
t.Run("Success - preserves original value", func(t *testing.T) {
|
||||
tapped := false
|
||||
|
||||
tapFunc := func(v int) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
tapped = true
|
||||
return result.Of(fmt.Sprintf("Tapped: %d", v))
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config](42),
|
||||
TapIOResultK[Config](tapFunc),
|
||||
)
|
||||
|
||||
res := pipeline(Config{})()
|
||||
assert.Equal(t, result.Of(42), res)
|
||||
assert.True(t, tapped)
|
||||
})
|
||||
|
||||
t.Run("Validation use case", func(t *testing.T) {
|
||||
validatePositive := func(v int) IOResult[bool] {
|
||||
return func() Result[bool] {
|
||||
if v > 0 {
|
||||
return result.Of(true)
|
||||
}
|
||||
return result.Left[bool](errors.New("must be positive"))
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config](42),
|
||||
TapIOResultK[Config](validatePositive),
|
||||
)
|
||||
|
||||
res := pipeline(Config{})()
|
||||
assert.Equal(t, result.Of(42), res)
|
||||
})
|
||||
|
||||
t.Run("Validation failure", func(t *testing.T) {
|
||||
validatePositive := func(v int) IOResult[bool] {
|
||||
return func() Result[bool] {
|
||||
if v > 0 {
|
||||
return result.Of(true)
|
||||
}
|
||||
return result.Left[bool](errors.New("must be positive"))
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config](-5),
|
||||
TapIOResultK[Config](validatePositive),
|
||||
)
|
||||
|
||||
res := pipeline(Config{})()
|
||||
assert.True(t, E.IsLeft(res))
|
||||
})
|
||||
}
|
||||
|
||||
func TestTap(t *testing.T) {
|
||||
type Config struct{}
|
||||
|
||||
t.Run("Success - preserves original value", func(t *testing.T) {
|
||||
tapped := false
|
||||
|
||||
tapFunc := func(v int) ReaderIOResult[Config, string] {
|
||||
return func(cfg Config) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
tapped = true
|
||||
return result.Of(fmt.Sprintf("Tapped: %d", v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config](42),
|
||||
Tap[Config](tapFunc),
|
||||
)
|
||||
|
||||
res := pipeline(Config{})()
|
||||
assert.Equal(t, result.Of(42), res)
|
||||
assert.True(t, tapped)
|
||||
})
|
||||
|
||||
t.Run("Tap is alias for ChainFirst", func(t *testing.T) {
|
||||
sideEffect := func(v int) ReaderIOResult[Config, string] {
|
||||
return func(cfg Config) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
return result.Of(fmt.Sprintf("Value: %d", v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pipelineWithTap := F.Pipe1(
|
||||
Of[Config](42),
|
||||
Tap[Config](sideEffect),
|
||||
)
|
||||
|
||||
pipelineWithChainFirst := F.Pipe1(
|
||||
Of[Config](42),
|
||||
ChainFirst[Config](sideEffect),
|
||||
)
|
||||
|
||||
resultTap := pipelineWithTap(Config{})()
|
||||
resultChainFirst := pipelineWithChainFirst(Config{})()
|
||||
|
||||
assert.Equal(t, resultChainFirst, resultTap)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMonadTap(t *testing.T) {
|
||||
type Config struct{}
|
||||
|
||||
t.Run("Success - preserves original value", func(t *testing.T) {
|
||||
tapped := false
|
||||
|
||||
tapFunc := func(v int) ReaderIOResult[Config, string] {
|
||||
return func(cfg Config) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
tapped = true
|
||||
return result.Of("tapped")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res := MonadTap(Of[Config](42), tapFunc)(Config{})()
|
||||
assert.Equal(t, result.Of(42), res)
|
||||
assert.True(t, tapped)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTapEitherK(t *testing.T) {
|
||||
type Config struct{}
|
||||
|
||||
t.Run("Success - preserves original value", func(t *testing.T) {
|
||||
tapped := false
|
||||
|
||||
tapFunc := func(v int) Result[string] {
|
||||
tapped = true
|
||||
return result.Of(fmt.Sprintf("Tapped: %d", v))
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config](42),
|
||||
TapEitherK[Config](tapFunc),
|
||||
)
|
||||
|
||||
res := pipeline(Config{})()
|
||||
assert.Equal(t, result.Of(42), res)
|
||||
assert.True(t, tapped)
|
||||
})
|
||||
|
||||
t.Run("Failure - tap fails", func(t *testing.T) {
|
||||
tapError := errors.New("tap failed")
|
||||
|
||||
tapFunc := func(v int) Result[string] {
|
||||
return result.Left[string](tapError)
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config](42),
|
||||
TapEitherK[Config](tapFunc),
|
||||
)
|
||||
|
||||
res := pipeline(Config{})()
|
||||
assert.True(t, E.IsLeft(res))
|
||||
})
|
||||
}
|
||||
|
||||
func TestTapResultK(t *testing.T) {
|
||||
type Config struct{}
|
||||
|
||||
t.Run("Success - preserves original value", func(t *testing.T) {
|
||||
tapped := false
|
||||
|
||||
tapFunc := func(v int) Result[string] {
|
||||
tapped = true
|
||||
return result.Of("tapped")
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config](42),
|
||||
TapResultK[Config](tapFunc),
|
||||
)
|
||||
|
||||
res := pipeline(Config{})()
|
||||
assert.Equal(t, result.Of(42), res)
|
||||
assert.True(t, tapped)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTapReaderK(t *testing.T) {
|
||||
type Config struct {
|
||||
multiplier int
|
||||
}
|
||||
|
||||
t.Run("Success - preserves original value with context", func(t *testing.T) {
|
||||
tapped := false
|
||||
|
||||
tapFunc := func(v int) R.Reader[Config, string] {
|
||||
return func(cfg Config) string {
|
||||
tapped = true
|
||||
return fmt.Sprintf("Value: %d, Multiplier: %d", v, cfg.multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config](42),
|
||||
TapReaderK[Config](tapFunc),
|
||||
)
|
||||
|
||||
res := pipeline(Config{multiplier: 2})()
|
||||
assert.Equal(t, result.Of(42), res)
|
||||
assert.True(t, tapped)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTapIOK(t *testing.T) {
|
||||
type Config struct{}
|
||||
|
||||
t.Run("Success - preserves original value", func(t *testing.T) {
|
||||
tapped := false
|
||||
|
||||
tapFunc := func(v int) IO[string] {
|
||||
return func() string {
|
||||
tapped = true
|
||||
return fmt.Sprintf("Tapped: %d", v)
|
||||
}
|
||||
}
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Of[Config](42),
|
||||
TapIOK[Config](tapFunc),
|
||||
)
|
||||
|
||||
res := pipeline(Config{})()
|
||||
assert.Equal(t, result.Of(42), res)
|
||||
assert.True(t, tapped)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
type Config struct {
|
||||
value int
|
||||
}
|
||||
|
||||
t.Run("Success - provides environment", func(t *testing.T) {
|
||||
computation := func(cfg Config) IOResult[int] {
|
||||
return func() Result[int] {
|
||||
return result.Of(cfg.value * 2)
|
||||
}
|
||||
}
|
||||
|
||||
res := Read[int](Config{value: 21})(computation)()
|
||||
assert.Equal(t, result.Of(42), res)
|
||||
})
|
||||
|
||||
t.Run("Failure - computation fails", func(t *testing.T) {
|
||||
compError := errors.New("computation failed")
|
||||
|
||||
computation := func(cfg Config) IOResult[int] {
|
||||
return func() Result[int] {
|
||||
return result.Left[int](compError)
|
||||
}
|
||||
}
|
||||
|
||||
res := Read[int](Config{value: 21})(computation)()
|
||||
assert.Equal(t, result.Left[int](compError), res)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChainLeft(t *testing.T) {
|
||||
type Config struct{}
|
||||
|
||||
t.Run("Right passes through unchanged", func(t *testing.T) {
|
||||
pipeline := F.Pipe1(
|
||||
Right[Config](42),
|
||||
ChainLeft[Config](func(err error) ReaderIOResult[Config, int] {
|
||||
return Left[Config, int](errors.New("should not run"))
|
||||
}),
|
||||
)
|
||||
|
||||
res := pipeline(Config{})()
|
||||
assert.Equal(t, result.Of(42), res)
|
||||
})
|
||||
|
||||
t.Run("Left transforms error", func(t *testing.T) {
|
||||
originalError := errors.New("original")
|
||||
newError := errors.New("transformed")
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Left[Config, int](originalError),
|
||||
ChainLeft[Config](func(err error) ReaderIOResult[Config, int] {
|
||||
return Left[Config, int](newError)
|
||||
}),
|
||||
)
|
||||
|
||||
res := pipeline(Config{})()
|
||||
assert.Equal(t, result.Left[int](newError), res)
|
||||
})
|
||||
|
||||
t.Run("Left recovers to Right", func(t *testing.T) {
|
||||
pipeline := F.Pipe1(
|
||||
Left[Config, int](errors.New("error")),
|
||||
ChainLeft[Config](func(err error) ReaderIOResult[Config, int] {
|
||||
return Right[Config](99)
|
||||
}),
|
||||
)
|
||||
|
||||
res := pipeline(Config{})()
|
||||
assert.Equal(t, result.Of(99), res)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTapLeft(t *testing.T) {
|
||||
type Config struct{}
|
||||
|
||||
t.Run("Right does not call function", func(t *testing.T) {
|
||||
tapped := false
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Right[Config](42),
|
||||
TapLeft[int, Config](func(err error) ReaderIOResult[Config, string] {
|
||||
return func(cfg Config) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
tapped = true
|
||||
return result.Of("logged")
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
res := pipeline(Config{})()
|
||||
assert.Equal(t, result.Of(42), res)
|
||||
assert.False(t, tapped)
|
||||
})
|
||||
|
||||
t.Run("Left calls function but preserves error", func(t *testing.T) {
|
||||
tapped := false
|
||||
originalError := errors.New("original error")
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Left[Config, int](originalError),
|
||||
TapLeft[int, Config](func(err error) ReaderIOResult[Config, string] {
|
||||
return func(cfg Config) IOResult[string] {
|
||||
return func() Result[string] {
|
||||
tapped = true
|
||||
return result.Of("side effect done")
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
res := pipeline(Config{})()
|
||||
assert.Equal(t, result.Left[int](originalError), res)
|
||||
assert.True(t, tapped)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTapLeftIOK(t *testing.T) {
|
||||
type Config struct{}
|
||||
|
||||
t.Run("Left calls IO function but preserves error", func(t *testing.T) {
|
||||
tapped := false
|
||||
originalError := errors.New("original error")
|
||||
|
||||
pipeline := F.Pipe1(
|
||||
Left[Config, int](originalError),
|
||||
TapLeftIOK[int, Config](func(err error) IO[string] {
|
||||
return func() string {
|
||||
tapped = true
|
||||
return "logged error"
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
res := pipeline(Config{})()
|
||||
assert.Equal(t, result.Left[int](originalError), res)
|
||||
assert.True(t, tapped)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,51 +38,75 @@ import (
|
||||
"github.com/IBM/fp-go/v2/readeroption"
|
||||
)
|
||||
|
||||
// FromReaderOption converts a ReaderOption to a ReaderReaderIOEither.
|
||||
// If the ReaderOption is None, the provided function is called to produce the error.
|
||||
//
|
||||
//go:inline
|
||||
func FromReaderOption[R, C, A, E any](onNone Lazy[E]) Kleisli[R, C, E, ReaderOption[R, A], A] {
|
||||
return reader.Map[R](RIOE.FromOption[C, A](onNone))
|
||||
}
|
||||
|
||||
// FromReaderIOEither lifts a ReaderIOEither into a ReaderReaderIOEither context.
|
||||
//
|
||||
//go:inline
|
||||
func FromReaderIOEither[C, E, R, A any](ma ReaderIOEither[R, E, A]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return reader.MonadMap(ma, RIOE.FromIOEither[C])
|
||||
}
|
||||
|
||||
// FromReaderIO lifts a ReaderIO into a ReaderReaderIOEither, placing the result in the Right side.
|
||||
//
|
||||
//go:inline
|
||||
func FromReaderIO[C, E, R, A any](ma ReaderIO[R, A]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return RightReaderIO[C, E](ma)
|
||||
}
|
||||
|
||||
// RightReaderIO lifts a ReaderIO into a ReaderReaderIOEither, placing the result in the Right side.
|
||||
//
|
||||
//go:inline
|
||||
func RightReaderIO[C, E, R, A any](ma ReaderIO[R, A]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return reader.MonadMap(ma, RIOE.RightIO[C, E, A])
|
||||
}
|
||||
|
||||
// LeftReaderIO lifts a ReaderIO into a ReaderReaderIOEither, placing the result in the Left (error) side.
|
||||
//
|
||||
//go:inline
|
||||
func LeftReaderIO[C, A, R, E any](me ReaderIO[R, E]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return reader.MonadMap(me, RIOE.LeftIO[C, A, E])
|
||||
}
|
||||
|
||||
// MonadMap applies a function to the value inside a ReaderReaderIOEither context.
|
||||
// If the computation is successful (Right), the function is applied to the value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadMap[R, C, E, A, B any](fa ReaderReaderIOEither[R, C, E, A], f func(A) B) ReaderReaderIOEither[R, C, E, B] {
|
||||
return reader.MonadMap(fa, RIOE.Map[C, E](f))
|
||||
}
|
||||
|
||||
// Map returns a function that applies a transformation to the success value.
|
||||
// This is the curried version of MonadMap.
|
||||
//
|
||||
//go:inline
|
||||
func Map[R, C, E, A, B any](f func(A) B) Operator[R, C, E, A, B] {
|
||||
return reader.Map[R](RIOE.Map[C, E](f))
|
||||
}
|
||||
|
||||
// MonadMapTo replaces the success value with a constant value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadMapTo[R, C, E, A, B any](fa ReaderReaderIOEither[R, C, E, A], b B) ReaderReaderIOEither[R, C, E, B] {
|
||||
return reader.MonadMap(fa, RIOE.MapTo[C, E, A](b))
|
||||
}
|
||||
|
||||
// MapTo returns a function that replaces the success value with a constant.
|
||||
// This is the curried version of MonadMapTo.
|
||||
//
|
||||
//go:inline
|
||||
func MapTo[R, C, E, A, B any](b B) Operator[R, C, E, A, B] {
|
||||
return reader.Map[R](RIOE.MapTo[C, E, A](b))
|
||||
}
|
||||
|
||||
// MonadChain sequences two computations where the second depends on the result of the first.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChain[R, C, E, A, B any](fa ReaderReaderIOEither[R, C, E, A], f Kleisli[R, C, E, A, B]) ReaderReaderIOEither[R, C, E, B] {
|
||||
return readert.MonadChain(
|
||||
@@ -92,6 +116,9 @@ func MonadChain[R, C, E, A, B any](fa ReaderReaderIOEither[R, C, E, A], f Kleisl
|
||||
)
|
||||
}
|
||||
|
||||
// MonadChainFirst sequences two computations but keeps the result of the first.
|
||||
// Useful for performing side effects while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirst[R, C, E, A, B any](fa ReaderReaderIOEither[R, C, E, A], f Kleisli[R, C, E, A, B]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return chain.MonadChainFirst(
|
||||
@@ -101,11 +128,15 @@ func MonadChainFirst[R, C, E, A, B any](fa ReaderReaderIOEither[R, C, E, A], f K
|
||||
f)
|
||||
}
|
||||
|
||||
// MonadTap is an alias for MonadChainFirst, executing a side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadTap[R, C, E, A, B any](fa ReaderReaderIOEither[R, C, E, A], f Kleisli[R, C, E, A, B]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return MonadChainFirst(fa, f)
|
||||
}
|
||||
|
||||
// MonadChainEitherK chains a computation that returns an Either into a ReaderReaderIOEither.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainEitherK[R, C, E, A, B any](ma ReaderReaderIOEither[R, C, E, A], f either.Kleisli[E, A, B]) ReaderReaderIOEither[R, C, E, B] {
|
||||
return fromeither.MonadChainEitherK(
|
||||
@@ -116,6 +147,9 @@ func MonadChainEitherK[R, C, E, A, B any](ma ReaderReaderIOEither[R, C, E, A], f
|
||||
)
|
||||
}
|
||||
|
||||
// ChainEitherK returns a function that chains an Either-returning function into ReaderReaderIOEither.
|
||||
// This is the curried version of MonadChainEitherK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainEitherK[R, C, E, A, B any](f either.Kleisli[E, A, B]) Operator[R, C, E, A, B] {
|
||||
return fromeither.ChainEitherK(
|
||||
@@ -125,6 +159,8 @@ func ChainEitherK[R, C, E, A, B any](f either.Kleisli[E, A, B]) Operator[R, C, E
|
||||
)
|
||||
}
|
||||
|
||||
// MonadChainFirstEitherK chains an Either-returning computation but keeps the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirstEitherK[R, C, E, A, B any](ma ReaderReaderIOEither[R, C, E, A], f either.Kleisli[E, A, B]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return fromeither.MonadChainFirstEitherK(
|
||||
@@ -136,11 +172,16 @@ func MonadChainFirstEitherK[R, C, E, A, B any](ma ReaderReaderIOEither[R, C, E,
|
||||
)
|
||||
}
|
||||
|
||||
// MonadTapEitherK is an alias for MonadChainFirstEitherK, executing an Either side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapEitherK[R, C, E, A, B any](ma ReaderReaderIOEither[R, C, E, A], f either.Kleisli[E, A, B]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return MonadChainFirstEitherK(ma, f)
|
||||
}
|
||||
|
||||
// ChainFirstEitherK returns a function that chains an Either computation while preserving the original value.
|
||||
// This is the curried version of MonadChainFirstEitherK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstEitherK[R, C, E, A, B any](f either.Kleisli[E, A, B]) Operator[R, C, E, A, A] {
|
||||
return fromeither.ChainFirstEitherK(
|
||||
@@ -151,11 +192,15 @@ func ChainFirstEitherK[R, C, E, A, B any](f either.Kleisli[E, A, B]) Operator[R,
|
||||
)
|
||||
}
|
||||
|
||||
// TapEitherK is an alias for ChainFirstEitherK, executing an Either side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func TapEitherK[R, C, E, A, B any](f either.Kleisli[E, A, B]) Operator[R, C, E, A, A] {
|
||||
return ChainFirstEitherK[R, C](f)
|
||||
}
|
||||
|
||||
// MonadChainReaderK chains a Reader-returning computation into a ReaderReaderIOEither.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainReaderK[C, E, R, A, B any](ma ReaderReaderIOEither[R, C, E, A], f reader.Kleisli[R, A, B]) ReaderReaderIOEither[R, C, E, B] {
|
||||
return fromreader.MonadChainReaderK(
|
||||
@@ -166,6 +211,9 @@ func MonadChainReaderK[C, E, R, A, B any](ma ReaderReaderIOEither[R, C, E, A], f
|
||||
)
|
||||
}
|
||||
|
||||
// ChainReaderK returns a function that chains a Reader-returning function into ReaderReaderIOEither.
|
||||
// This is the curried version of MonadChainReaderK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainReaderK[C, E, R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, C, E, A, B] {
|
||||
return fromreader.ChainReaderK(
|
||||
@@ -175,6 +223,8 @@ func ChainReaderK[C, E, R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, C, E
|
||||
)
|
||||
}
|
||||
|
||||
// MonadChainFirstReaderK chains a Reader computation but keeps the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirstReaderK[C, E, R, A, B any](ma ReaderReaderIOEither[R, C, E, A], f reader.Kleisli[R, A, B]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return fromreader.MonadChainFirstReaderK(
|
||||
@@ -185,11 +235,16 @@ func MonadChainFirstReaderK[C, E, R, A, B any](ma ReaderReaderIOEither[R, C, E,
|
||||
)
|
||||
}
|
||||
|
||||
// MonadTapReaderK is an alias for MonadChainFirstReaderK, executing a Reader side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapReaderK[C, E, R, A, B any](ma ReaderReaderIOEither[R, C, E, A], f reader.Kleisli[R, A, B]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return MonadChainFirstReaderK(ma, f)
|
||||
}
|
||||
|
||||
// ChainFirstReaderK returns a function that chains a Reader computation while preserving the original value.
|
||||
// This is the curried version of MonadChainFirstReaderK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstReaderK[C, E, R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, C, E, A, A] {
|
||||
return fromreader.ChainFirstReaderK(
|
||||
@@ -199,11 +254,15 @@ func ChainFirstReaderK[C, E, R, A, B any](f reader.Kleisli[R, A, B]) Operator[R,
|
||||
)
|
||||
}
|
||||
|
||||
// TapReaderK is an alias for ChainFirstReaderK, executing a Reader side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func TapReaderK[C, E, R, A, B any](f reader.Kleisli[R, A, B]) Operator[R, C, E, A, A] {
|
||||
return ChainFirstReaderK[C, E](f)
|
||||
}
|
||||
|
||||
// MonadChainReaderIOK chains a ReaderIO-returning computation into a ReaderReaderIOEither.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainReaderIOK[C, E, R, A, B any](ma ReaderReaderIOEither[R, C, E, A], f readerio.Kleisli[R, A, B]) ReaderReaderIOEither[R, C, E, B] {
|
||||
return fromreader.MonadChainReaderK(
|
||||
@@ -214,6 +273,9 @@ func MonadChainReaderIOK[C, E, R, A, B any](ma ReaderReaderIOEither[R, C, E, A],
|
||||
)
|
||||
}
|
||||
|
||||
// ChainReaderIOK returns a function that chains a ReaderIO-returning function into ReaderReaderIOEither.
|
||||
// This is the curried version of MonadChainReaderIOK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainReaderIOK[C, E, R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, C, E, A, B] {
|
||||
return fromreader.ChainReaderK(
|
||||
@@ -223,6 +285,8 @@ func ChainReaderIOK[C, E, R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R,
|
||||
)
|
||||
}
|
||||
|
||||
// MonadChainFirstReaderIOK chains a ReaderIO computation but keeps the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirstReaderIOK[C, E, R, A, B any](ma ReaderReaderIOEither[R, C, E, A], f readerio.Kleisli[R, A, B]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return fromreader.MonadChainFirstReaderK(
|
||||
@@ -233,11 +297,16 @@ func MonadChainFirstReaderIOK[C, E, R, A, B any](ma ReaderReaderIOEither[R, C, E
|
||||
)
|
||||
}
|
||||
|
||||
// MonadTapReaderIOK is an alias for MonadChainFirstReaderIOK, executing a ReaderIO side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapReaderIOK[C, E, R, A, B any](ma ReaderReaderIOEither[R, C, E, A], f readerio.Kleisli[R, A, B]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return MonadChainFirstReaderIOK(ma, f)
|
||||
}
|
||||
|
||||
// ChainFirstReaderIOK returns a function that chains a ReaderIO computation while preserving the original value.
|
||||
// This is the curried version of MonadChainFirstReaderIOK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstReaderIOK[C, E, R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, C, E, A, A] {
|
||||
return fromreader.ChainFirstReaderK(
|
||||
@@ -247,11 +316,15 @@ func ChainFirstReaderIOK[C, E, R, A, B any](f readerio.Kleisli[R, A, B]) Operato
|
||||
)
|
||||
}
|
||||
|
||||
// TapReaderIOK is an alias for ChainFirstReaderIOK, executing a ReaderIO side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func TapReaderIOK[C, E, R, A, B any](f readerio.Kleisli[R, A, B]) Operator[R, C, E, A, A] {
|
||||
return ChainFirstReaderIOK[C, E](f)
|
||||
}
|
||||
|
||||
// MonadChainReaderEitherK chains a ReaderEither-returning computation into a ReaderReaderIOEither.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainReaderEitherK[R, C, E, A, B any](ma ReaderReaderIOEither[R, C, E, A], f RE.Kleisli[R, E, A, B]) ReaderReaderIOEither[R, C, E, B] {
|
||||
return fromreader.MonadChainReaderK(
|
||||
@@ -262,6 +335,9 @@ func MonadChainReaderEitherK[R, C, E, A, B any](ma ReaderReaderIOEither[R, C, E,
|
||||
)
|
||||
}
|
||||
|
||||
// ChainReaderEitherK returns a function that chains a ReaderEither-returning function into ReaderReaderIOEither.
|
||||
// This is the curried version of MonadChainReaderEitherK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainReaderEitherK[C, E, R, A, B any](f RE.Kleisli[R, E, A, B]) Operator[R, C, E, A, B] {
|
||||
return fromreader.ChainReaderK(
|
||||
@@ -271,6 +347,8 @@ func ChainReaderEitherK[C, E, R, A, B any](f RE.Kleisli[R, E, A, B]) Operator[R,
|
||||
)
|
||||
}
|
||||
|
||||
// ChainReaderIOEitherK returns a function that chains a ReaderIOEither-returning function into ReaderReaderIOEither.
|
||||
//
|
||||
//go:inline
|
||||
func ChainReaderIOEitherK[C, R, E, A, B any](f RIOE.Kleisli[R, E, A, B]) Operator[R, C, E, A, B] {
|
||||
return fromreader.ChainReaderK(
|
||||
@@ -280,6 +358,8 @@ func ChainReaderIOEitherK[C, R, E, A, B any](f RIOE.Kleisli[R, E, A, B]) Operato
|
||||
)
|
||||
}
|
||||
|
||||
// MonadChainFirstReaderEitherK chains a ReaderEither computation but keeps the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirstReaderEitherK[R, C, E, A, B any](ma ReaderReaderIOEither[R, C, E, A], f RE.Kleisli[R, E, A, B]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return fromreader.MonadChainFirstReaderK(
|
||||
@@ -290,11 +370,16 @@ func MonadChainFirstReaderEitherK[R, C, E, A, B any](ma ReaderReaderIOEither[R,
|
||||
)
|
||||
}
|
||||
|
||||
// MonadTapReaderEitherK is an alias for MonadChainFirstReaderEitherK, executing a ReaderEither side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapReaderEitherK[R, C, E, A, B any](ma ReaderReaderIOEither[R, C, E, A], f RE.Kleisli[R, E, A, B]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return MonadChainFirstReaderEitherK(ma, f)
|
||||
}
|
||||
|
||||
// ChainFirstReaderEitherK returns a function that chains a ReaderEither computation while preserving the original value.
|
||||
// This is the curried version of MonadChainFirstReaderEitherK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstReaderEitherK[C, E, R, A, B any](f RE.Kleisli[R, E, A, B]) Operator[R, C, E, A, A] {
|
||||
return fromreader.ChainFirstReaderK(
|
||||
@@ -304,11 +389,15 @@ func ChainFirstReaderEitherK[C, E, R, A, B any](f RE.Kleisli[R, E, A, B]) Operat
|
||||
)
|
||||
}
|
||||
|
||||
// TapReaderEitherK is an alias for ChainFirstReaderEitherK, executing a ReaderEither side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func TapReaderEitherK[C, E, R, A, B any](f RE.Kleisli[R, E, A, B]) Operator[R, C, E, A, A] {
|
||||
return ChainFirstReaderEitherK[C](f)
|
||||
}
|
||||
|
||||
// ChainReaderOptionK returns a function that chains a ReaderOption-returning function into ReaderReaderIOEither.
|
||||
// If the ReaderOption is None, the provided error function is called.
|
||||
func ChainReaderOptionK[R, C, A, B, E any](onNone Lazy[E]) func(readeroption.Kleisli[R, A, B]) Operator[R, C, E, A, B] {
|
||||
|
||||
fro := FromReaderOption[R, C, B](onNone)
|
||||
@@ -323,6 +412,8 @@ func ChainReaderOptionK[R, C, A, B, E any](onNone Lazy[E]) func(readeroption.Kle
|
||||
}
|
||||
}
|
||||
|
||||
// ChainFirstReaderOptionK chains a ReaderOption computation while preserving the original value.
|
||||
// If the ReaderOption is None, the provided error function is called.
|
||||
func ChainFirstReaderOptionK[R, C, A, B, E any](onNone Lazy[E]) func(readeroption.Kleisli[R, A, B]) Operator[R, C, E, A, A] {
|
||||
fro := FromReaderOption[R, C, B](onNone)
|
||||
return func(f readeroption.Kleisli[R, A, B]) Operator[R, C, E, A, A] {
|
||||
@@ -334,11 +425,15 @@ func ChainFirstReaderOptionK[R, C, A, B, E any](onNone Lazy[E]) func(readeroptio
|
||||
}
|
||||
}
|
||||
|
||||
// TapReaderOptionK is an alias for ChainFirstReaderOptionK, executing a ReaderOption side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func TapReaderOptionK[R, C, A, B, E any](onNone Lazy[E]) func(readeroption.Kleisli[R, A, B]) Operator[R, C, E, A, A] {
|
||||
return ChainFirstReaderOptionK[R, C, A, B](onNone)
|
||||
}
|
||||
|
||||
// MonadChainIOEitherK chains an IOEither-returning computation into a ReaderReaderIOEither.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainIOEitherK[R, C, E, A, B any](ma ReaderReaderIOEither[R, C, E, A], f IOE.Kleisli[E, A, B]) ReaderReaderIOEither[R, C, E, B] {
|
||||
return fromioeither.MonadChainIOEitherK(
|
||||
@@ -349,6 +444,9 @@ func MonadChainIOEitherK[R, C, E, A, B any](ma ReaderReaderIOEither[R, C, E, A],
|
||||
)
|
||||
}
|
||||
|
||||
// ChainIOEitherK returns a function that chains an IOEither-returning function into ReaderReaderIOEither.
|
||||
// This is the curried version of MonadChainIOEitherK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainIOEitherK[R, C, E, A, B any](f IOE.Kleisli[E, A, B]) Operator[R, C, E, A, B] {
|
||||
return fromioeither.ChainIOEitherK(
|
||||
@@ -358,6 +456,8 @@ func ChainIOEitherK[R, C, E, A, B any](f IOE.Kleisli[E, A, B]) Operator[R, C, E,
|
||||
)
|
||||
}
|
||||
|
||||
// MonadChainIOK chains an IO-returning computation into a ReaderReaderIOEither.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainIOK[R, C, E, A, B any](ma ReaderReaderIOEither[R, C, E, A], f io.Kleisli[A, B]) ReaderReaderIOEither[R, C, E, B] {
|
||||
return fromio.MonadChainIOK(
|
||||
@@ -368,6 +468,9 @@ func MonadChainIOK[R, C, E, A, B any](ma ReaderReaderIOEither[R, C, E, A], f io.
|
||||
)
|
||||
}
|
||||
|
||||
// ChainIOK returns a function that chains an IO-returning function into ReaderReaderIOEither.
|
||||
// This is the curried version of MonadChainIOK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainIOK[R, C, E, A, B any](f io.Kleisli[A, B]) Operator[R, C, E, A, B] {
|
||||
return fromio.ChainIOK(
|
||||
@@ -377,6 +480,8 @@ func ChainIOK[R, C, E, A, B any](f io.Kleisli[A, B]) Operator[R, C, E, A, B] {
|
||||
)
|
||||
}
|
||||
|
||||
// MonadChainFirstIOK chains an IO computation but keeps the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainFirstIOK[R, C, E, A, B any](ma ReaderReaderIOEither[R, C, E, A], f io.Kleisli[A, B]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return fromio.MonadChainFirstIOK(
|
||||
@@ -388,11 +493,16 @@ func MonadChainFirstIOK[R, C, E, A, B any](ma ReaderReaderIOEither[R, C, E, A],
|
||||
)
|
||||
}
|
||||
|
||||
// MonadTapIOK is an alias for MonadChainFirstIOK, executing an IO side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func MonadTapIOK[R, C, E, A, B any](ma ReaderReaderIOEither[R, C, E, A], f io.Kleisli[A, B]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return MonadChainFirstIOK(ma, f)
|
||||
}
|
||||
|
||||
// ChainFirstIOK returns a function that chains an IO computation while preserving the original value.
|
||||
// This is the curried version of MonadChainFirstIOK.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstIOK[R, C, E, A, B any](f io.Kleisli[A, B]) Operator[R, C, E, A, A] {
|
||||
return fromio.ChainFirstIOK(
|
||||
@@ -403,11 +513,16 @@ func ChainFirstIOK[R, C, E, A, B any](f io.Kleisli[A, B]) Operator[R, C, E, A, A
|
||||
)
|
||||
}
|
||||
|
||||
// TapIOK is an alias for ChainFirstIOK, executing an IO side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func TapIOK[R, C, E, A, B any](f io.Kleisli[A, B]) Operator[R, C, E, A, A] {
|
||||
return ChainFirstIOK[R, C, E](f)
|
||||
}
|
||||
|
||||
// ChainOptionK returns a function that chains an Option-returning function into ReaderReaderIOEither.
|
||||
// If the Option is None, the provided error function is called.
|
||||
//
|
||||
//go:inline
|
||||
func ChainOptionK[R, C, A, B, E any](onNone Lazy[E]) func(option.Kleisli[A, B]) Operator[R, C, E, A, B] {
|
||||
return fromeither.ChainOptionK(
|
||||
@@ -417,6 +532,8 @@ func ChainOptionK[R, C, A, B, E any](onNone Lazy[E]) func(option.Kleisli[A, B])
|
||||
)
|
||||
}
|
||||
|
||||
// MonadAp applies a function wrapped in a context to a value wrapped in a context.
|
||||
//
|
||||
//go:inline
|
||||
func MonadAp[R, C, E, A, B any](fab ReaderReaderIOEither[R, C, E, func(A) B], fa ReaderReaderIOEither[R, C, E, A]) ReaderReaderIOEither[R, C, E, B] {
|
||||
return readert.MonadAp[
|
||||
@@ -429,6 +546,8 @@ func MonadAp[R, C, E, A, B any](fab ReaderReaderIOEither[R, C, E, func(A) B], fa
|
||||
)
|
||||
}
|
||||
|
||||
// MonadApSeq applies a function in a context to a value in a context, executing them sequentially.
|
||||
//
|
||||
//go:inline
|
||||
func MonadApSeq[R, C, E, A, B any](fab ReaderReaderIOEither[R, C, E, func(A) B], fa ReaderReaderIOEither[R, C, E, A]) ReaderReaderIOEither[R, C, E, B] {
|
||||
return readert.MonadAp[
|
||||
@@ -441,6 +560,8 @@ func MonadApSeq[R, C, E, A, B any](fab ReaderReaderIOEither[R, C, E, func(A) B],
|
||||
)
|
||||
}
|
||||
|
||||
// MonadApPar applies a function in a context to a value in a context, executing them in parallel.
|
||||
//
|
||||
//go:inline
|
||||
func MonadApPar[R, C, E, A, B any](fab ReaderReaderIOEither[R, C, E, func(A) B], fa ReaderReaderIOEither[R, C, E, A]) ReaderReaderIOEither[R, C, E, B] {
|
||||
return readert.MonadAp[
|
||||
@@ -453,6 +574,9 @@ func MonadApPar[R, C, E, A, B any](fab ReaderReaderIOEither[R, C, E, func(A) B],
|
||||
)
|
||||
}
|
||||
|
||||
// Ap returns a function that applies a function in a context to a value in a context.
|
||||
// This is the curried version of MonadAp.
|
||||
//
|
||||
//go:inline
|
||||
func Ap[B, R, C, E, A any](fa ReaderReaderIOEither[R, C, E, A]) Operator[R, C, E, func(A) B, B] {
|
||||
return readert.Ap[
|
||||
@@ -464,6 +588,9 @@ func Ap[B, R, C, E, A any](fa ReaderReaderIOEither[R, C, E, A]) Operator[R, C, E
|
||||
)
|
||||
}
|
||||
|
||||
// Chain returns a function that sequences computations where the second depends on the first.
|
||||
// This is the curried version of MonadChain.
|
||||
//
|
||||
//go:inline
|
||||
func Chain[R, C, E, A, B any](f Kleisli[R, C, E, A, B]) Operator[R, C, E, A, B] {
|
||||
return readert.Chain[ReaderReaderIOEither[R, C, E, A]](
|
||||
@@ -472,6 +599,9 @@ func Chain[R, C, E, A, B any](f Kleisli[R, C, E, A, B]) Operator[R, C, E, A, B]
|
||||
)
|
||||
}
|
||||
|
||||
// ChainFirst returns a function that sequences computations but keeps the first result.
|
||||
// This is the curried version of MonadChainFirst.
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirst[R, C, E, A, B any](f Kleisli[R, C, E, A, B]) Operator[R, C, E, A, A] {
|
||||
return chain.ChainFirst(
|
||||
@@ -480,96 +610,137 @@ func ChainFirst[R, C, E, A, B any](f Kleisli[R, C, E, A, B]) Operator[R, C, E, A
|
||||
f)
|
||||
}
|
||||
|
||||
// Tap is an alias for ChainFirst, executing a side effect while preserving the original value.
|
||||
//
|
||||
//go:inline
|
||||
func Tap[R, C, E, A, B any](f Kleisli[R, C, E, A, B]) Operator[R, C, E, A, A] {
|
||||
return ChainFirst(f)
|
||||
}
|
||||
|
||||
// Right creates a successful ReaderReaderIOEither with the given value.
|
||||
//
|
||||
//go:inline
|
||||
func Right[R, C, E, A any](a A) ReaderReaderIOEither[R, C, E, A] {
|
||||
return reader.Of[R](RIOE.Right[C, E](a))
|
||||
}
|
||||
|
||||
// Left creates a failed ReaderReaderIOEither with the given error.
|
||||
//
|
||||
//go:inline
|
||||
func Left[R, C, A, E any](e E) ReaderReaderIOEither[R, C, E, A] {
|
||||
return reader.Of[R](RIOE.Left[C, A](e))
|
||||
}
|
||||
|
||||
// Of creates a successful ReaderReaderIOEither with the given value.
|
||||
// This is the pointed functor operation.
|
||||
//
|
||||
//go:inline
|
||||
func Of[R, C, E, A any](a A) ReaderReaderIOEither[R, C, E, A] {
|
||||
return Right[R, C, E](a)
|
||||
}
|
||||
|
||||
// Flatten removes one level of nesting from a nested ReaderReaderIOEither.
|
||||
//
|
||||
//go:inline
|
||||
func Flatten[R, C, E, A any](mma ReaderReaderIOEither[R, C, E, ReaderReaderIOEither[R, C, E, A]]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return MonadChain(mma, function.Identity[ReaderReaderIOEither[R, C, E, A]])
|
||||
}
|
||||
|
||||
// FromEither lifts an Either into a ReaderReaderIOEither context.
|
||||
//
|
||||
//go:inline
|
||||
func FromEither[R, C, E, A any](t Either[E, A]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return reader.Of[R](RIOE.FromEither[C](t))
|
||||
}
|
||||
|
||||
// RightReader lifts a Reader into a ReaderReaderIOEither, placing the result in the Right side.
|
||||
//
|
||||
//go:inline
|
||||
func RightReader[C, E, R, A any](ma Reader[R, A]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return reader.MonadMap(ma, RIOE.Right[C, E])
|
||||
}
|
||||
|
||||
// LeftReader lifts a Reader into a ReaderReaderIOEither, placing the result in the Left (error) side.
|
||||
//
|
||||
//go:inline
|
||||
func LeftReader[C, A, R, E any](ma Reader[R, E]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return reader.MonadMap(ma, RIOE.Left[C, A])
|
||||
}
|
||||
|
||||
// FromReader lifts a Reader into a ReaderReaderIOEither context.
|
||||
//
|
||||
//go:inline
|
||||
func FromReader[C, E, R, A any](ma Reader[R, A]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return RightReader[C, E](ma)
|
||||
}
|
||||
|
||||
// RightIO lifts an IO into a ReaderReaderIOEither, placing the result in the Right side.
|
||||
//
|
||||
//go:inline
|
||||
func RightIO[R, C, E, A any](ma IO[A]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return reader.Of[R](RIOE.RightIO[C, E](ma))
|
||||
}
|
||||
|
||||
// LeftIO lifts an IO into a ReaderReaderIOEither, placing the result in the Left (error) side.
|
||||
//
|
||||
//go:inline
|
||||
func LeftIO[R, C, A, E any](ma IO[E]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return reader.Of[R](RIOE.LeftIO[C, A](ma))
|
||||
}
|
||||
|
||||
// FromIO lifts an IO into a ReaderReaderIOEither context.
|
||||
//
|
||||
//go:inline
|
||||
func FromIO[R, C, E, A any](ma IO[A]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return RightIO[R, C, E](ma)
|
||||
}
|
||||
|
||||
// FromIOEither lifts an IOEither into a ReaderReaderIOEither context.
|
||||
//
|
||||
//go:inline
|
||||
func FromIOEither[R, C, E, A any](ma IOEither[E, A]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return reader.Of[R](RIOE.FromIOEither[C](ma))
|
||||
}
|
||||
|
||||
// FromReaderEither lifts a ReaderEither into a ReaderReaderIOEither context.
|
||||
//
|
||||
//go:inline
|
||||
func FromReaderEither[R, C, E, A any](ma RE.ReaderEither[R, E, A]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return reader.MonadMap(ma, RIOE.FromEither[C])
|
||||
}
|
||||
|
||||
// Ask returns a ReaderReaderIOEither that retrieves the outer context.
|
||||
//
|
||||
//go:inline
|
||||
func Ask[R, C, E any]() ReaderReaderIOEither[R, C, E, R] {
|
||||
return fromreader.Ask(FromReader[C, E, R, R])()
|
||||
}
|
||||
|
||||
// Asks returns a ReaderReaderIOEither that retrieves a value derived from the outer context.
|
||||
//
|
||||
//go:inline
|
||||
func Asks[C, E, R, A any](r Reader[R, A]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return fromreader.Asks(FromReader[C, E, R, A])(r)
|
||||
}
|
||||
|
||||
// FromOption converts an Option to a ReaderReaderIOEither.
|
||||
// If the Option is None, the provided function is called to produce the error.
|
||||
//
|
||||
//go:inline
|
||||
func FromOption[R, C, A, E any](onNone Lazy[E]) func(Option[A]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return fromeither.FromOption(FromEither[R, C, E, A], onNone)
|
||||
}
|
||||
|
||||
// FromPredicate creates a ReaderReaderIOEither from a predicate.
|
||||
// If the predicate returns false, the onFalse function is called to produce the error.
|
||||
//
|
||||
//go:inline
|
||||
func FromPredicate[R, C, E, A any](pred func(A) bool, onFalse func(A) E) func(A) ReaderReaderIOEither[R, C, E, A] {
|
||||
return fromeither.FromPredicate(FromEither[R, C, E, A], pred, onFalse)
|
||||
}
|
||||
|
||||
// MonadAlt tries the first computation, and if it fails, tries the second.
|
||||
//
|
||||
//go:inline
|
||||
func MonadAlt[R, C, E, A any](first ReaderReaderIOEither[R, C, E, A], second Lazy[ReaderReaderIOEither[R, C, E, A]]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return func(r R) ReaderIOEither[C, E, A] {
|
||||
@@ -579,36 +750,53 @@ func MonadAlt[R, C, E, A any](first ReaderReaderIOEither[R, C, E, A], second Laz
|
||||
}
|
||||
}
|
||||
|
||||
// Alt returns a function that tries an alternative computation if the first fails.
|
||||
// This is the curried version of MonadAlt.
|
||||
//
|
||||
//go:inline
|
||||
func Alt[R, C, E, A any](second Lazy[ReaderReaderIOEither[R, C, E, A]]) Operator[R, C, E, A, A] {
|
||||
return function.Bind2nd(MonadAlt, second)
|
||||
}
|
||||
|
||||
// MonadFlap applies a value to a function wrapped in a context.
|
||||
//
|
||||
//go:inline
|
||||
func MonadFlap[R, C, E, B, A any](fab ReaderReaderIOEither[R, C, E, func(A) B], a A) ReaderReaderIOEither[R, C, E, B] {
|
||||
return functor.MonadFlap(MonadMap[R, C, E, func(A) B, B], fab, a)
|
||||
}
|
||||
|
||||
// Flap returns a function that applies a fixed value to a function in a context.
|
||||
// This is the curried version of MonadFlap.
|
||||
//
|
||||
//go:inline
|
||||
func Flap[R, C, E, B, A any](a A) Operator[R, C, E, func(A) B, B] {
|
||||
return functor.Flap(Map[R, C, E, func(A) B, B], a)
|
||||
}
|
||||
|
||||
// MonadMapLeft applies a function to the error value, leaving success unchanged.
|
||||
//
|
||||
//go:inline
|
||||
func MonadMapLeft[R, C, E1, E2, A any](fa ReaderReaderIOEither[R, C, E1, A], f func(E1) E2) ReaderReaderIOEither[R, C, E2, A] {
|
||||
return reader.MonadMap(fa, RIOE.MapLeft[C, A](f))
|
||||
}
|
||||
|
||||
// MapLeft returns a function that transforms the error channel.
|
||||
// This is the curried version of MonadMapLeft.
|
||||
//
|
||||
//go:inline
|
||||
func MapLeft[R, C, A, E1, E2 any](f func(E1) E2) func(ReaderReaderIOEither[R, C, E1, A]) ReaderReaderIOEither[R, C, E2, A] {
|
||||
return reader.Map[R](RIOE.MapLeft[C, A](f))
|
||||
}
|
||||
|
||||
// Read executes a ReaderReaderIOEither by providing a concrete outer environment value.
|
||||
//
|
||||
//go:inline
|
||||
func Read[C, E, A, R any](r R) func(ReaderReaderIOEither[R, C, E, A]) ReaderIOEither[C, E, A] {
|
||||
return reader.Read[ReaderIOEither[C, E, A]](r)
|
||||
}
|
||||
|
||||
// ReadIOEither executes a ReaderReaderIOEither by providing an outer environment obtained from an IOEither.
|
||||
//
|
||||
//go:inline
|
||||
func ReadIOEither[A, R, C, E any](rio IOEither[E, R]) func(ReaderReaderIOEither[R, C, E, A]) ReaderIOEither[C, E, A] {
|
||||
return func(rri ReaderReaderIOEither[R, C, E, A]) ReaderIOEither[C, E, A] {
|
||||
@@ -623,6 +811,8 @@ func ReadIOEither[A, R, C, E any](rio IOEither[E, R]) func(ReaderReaderIOEither[
|
||||
}
|
||||
}
|
||||
|
||||
// ReadIO executes a ReaderReaderIOEither by providing an outer environment obtained from an IO.
|
||||
//
|
||||
//go:inline
|
||||
func ReadIO[C, E, A, R any](rio IO[R]) func(ReaderReaderIOEither[R, C, E, A]) ReaderIOEither[C, E, A] {
|
||||
return func(rri ReaderReaderIOEither[R, C, E, A]) ReaderIOEither[C, E, A] {
|
||||
@@ -637,6 +827,8 @@ func ReadIO[C, E, A, R any](rio IO[R]) func(ReaderReaderIOEither[R, C, E, A]) Re
|
||||
}
|
||||
}
|
||||
|
||||
// MonadChainLeft chains a computation on the error channel, allowing error recovery or transformation.
|
||||
//
|
||||
//go:inline
|
||||
func MonadChainLeft[R, C, EA, EB, A any](fa ReaderReaderIOEither[R, C, EA, A], f Kleisli[R, C, EB, EA, A]) ReaderReaderIOEither[R, C, EB, A] {
|
||||
return readert.MonadChain(
|
||||
@@ -646,6 +838,9 @@ func MonadChainLeft[R, C, EA, EB, A any](fa ReaderReaderIOEither[R, C, EA, A], f
|
||||
)
|
||||
}
|
||||
|
||||
// ChainLeft returns a function that chains a computation on the error channel.
|
||||
// This is the curried version of MonadChainLeft.
|
||||
//
|
||||
//go:inline
|
||||
func ChainLeft[R, C, EA, EB, A any](f Kleisli[R, C, EB, EA, A]) func(ReaderReaderIOEither[R, C, EA, A]) ReaderReaderIOEither[R, C, EB, A] {
|
||||
return readert.Chain[ReaderReaderIOEither[R, C, EA, A]](
|
||||
@@ -654,16 +849,22 @@ func ChainLeft[R, C, EA, EB, A any](f Kleisli[R, C, EB, EA, A]) func(ReaderReade
|
||||
)
|
||||
}
|
||||
|
||||
// Delay creates an operation that passes in the value after some delay.
|
||||
//
|
||||
//go:inline
|
||||
func Delay[R, C, E, A any](delay time.Duration) Operator[R, C, E, A, A] {
|
||||
return reader.Map[R](RIOE.Delay[C, E, A](delay))
|
||||
}
|
||||
|
||||
// After creates an operation that passes after the given time.Time.
|
||||
//
|
||||
//go:inline
|
||||
func After[R, C, E, A any](timestamp time.Time) Operator[R, C, E, A, A] {
|
||||
return reader.Map[R](RIOE.After[C, E, A](timestamp))
|
||||
}
|
||||
|
||||
// Defer creates a ReaderReaderIOEither lazily via a generator function.
|
||||
// The generator is called each time the ReaderReaderIOEither is executed.
|
||||
func Defer[R, C, E, A any](fa Lazy[ReaderReaderIOEither[R, C, E, A]]) ReaderReaderIOEither[R, C, E, A] {
|
||||
return func(r R) ReaderIOEither[C, E, A] {
|
||||
return func(c C) RIOE.IOEither[E, A] {
|
||||
|
||||
Reference in New Issue
Block a user