mirror of
https://github.com/IBM/fp-go.git
synced 2025-12-15 23:33:46 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acb601fc01 | ||
|
|
d17663f016 | ||
|
|
829365fc24 | ||
|
|
64b5660b4e |
@@ -61,6 +61,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/IBM/fp-go/v2/option"
|
||||
N "github.com/IBM/fp-go/v2/number"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -145,6 +146,8 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
## ⚠️ Breaking Changes
|
||||
|
||||
### From V1 to V2
|
||||
|
||||
#### 1. Generic Type Aliases
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
E "github.com/IBM/fp-go/v2/eq"
|
||||
)
|
||||
|
||||
func equals[T any](left []T, right []T, eq func(T, T) bool) bool {
|
||||
func equals[T any](left, right []T, eq func(T, T) bool) bool {
|
||||
if len(left) != len(right) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ func MatchLeft[AS ~[]A, A, B any](onEmpty func() B, onNonEmpty func(A, AS) B) fu
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func Slice[AS ~[]A, A any](start int, end int) func(AS) AS {
|
||||
func Slice[AS ~[]A, A any](start, end int) func(AS) AS {
|
||||
return array.Slice[AS](start, end)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/IBM/fp-go/v2/optics/prism"
|
||||
"github.com/IBM/fp-go/v2/option"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
)
|
||||
|
||||
func TestEqual(t *testing.T) {
|
||||
@@ -334,7 +335,7 @@ func TestThat(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should work with string predicates", func(t *testing.T) {
|
||||
startsWithH := func(s string) bool { return len(s) > 0 && s[0] == 'h' }
|
||||
startsWithH := func(s string) bool { return S.IsNonEmpty(s) && s[0] == 'h' }
|
||||
result := That(startsWithH)("hello")(t)
|
||||
if !result {
|
||||
t.Error("Expected That to pass for string predicate")
|
||||
@@ -484,7 +485,7 @@ func TestLocal(t *testing.T) {
|
||||
t.Run("should compose with other assertions", func(t *testing.T) {
|
||||
// Create multiple focused assertions
|
||||
nameNotEmpty := Local(func(u User) string { return u.Name })(
|
||||
That(func(name string) bool { return len(name) > 0 }),
|
||||
That(S.IsNonEmpty),
|
||||
)
|
||||
ageInRange := Local(func(u User) int { return u.Age })(
|
||||
That(func(age int) bool { return age >= 18 && age <= 100 }),
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
C "github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
@@ -439,7 +440,7 @@ func extractEmbeddedFields(embedType ast.Expr, fileImports map[string]string, fi
|
||||
return results
|
||||
}
|
||||
|
||||
if typeName == "" || typeIdent == nil {
|
||||
if S.IsEmpty(typeName) || typeIdent == nil {
|
||||
return results
|
||||
}
|
||||
|
||||
@@ -746,7 +747,7 @@ func generateLensHelpers(dir, filename string, verbose bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
if packageName == "" {
|
||||
if S.IsEmpty(packageName) {
|
||||
packageName = pkg
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -60,7 +61,7 @@ func TestHasLensAnnotation(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var doc *ast.CommentGroup
|
||||
if tt.comment != "" {
|
||||
if S.IsNonEmpty(tt.comment) {
|
||||
doc = &ast.CommentGroup{
|
||||
List: []*ast.Comment{
|
||||
{Text: tt.comment},
|
||||
@@ -289,7 +290,7 @@ func TestHasOmitEmpty(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var tag *ast.BasicLit
|
||||
if tt.tag != "" {
|
||||
if S.IsNonEmpty(tt.tag) {
|
||||
tag = &ast.BasicLit{
|
||||
Value: tt.tag,
|
||||
}
|
||||
@@ -326,7 +327,7 @@ type Other struct {
|
||||
}
|
||||
`
|
||||
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0644)
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Parse the file
|
||||
@@ -380,7 +381,7 @@ type Config struct {
|
||||
}
|
||||
`
|
||||
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0644)
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Parse the file
|
||||
@@ -440,7 +441,7 @@ type TypeTest struct {
|
||||
}
|
||||
`
|
||||
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0644)
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Parse the file
|
||||
@@ -542,7 +543,7 @@ type TestStruct struct {
|
||||
`
|
||||
|
||||
testFile := filepath.Join(tmpDir, "test.go")
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0644)
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Generate lens code
|
||||
@@ -597,7 +598,7 @@ type TestStruct struct {
|
||||
`
|
||||
|
||||
testFile := filepath.Join(tmpDir, "test.go")
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0644)
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Generate lens code
|
||||
@@ -639,7 +640,7 @@ type TestStruct struct {
|
||||
`
|
||||
|
||||
testFile := filepath.Join(tmpDir, "test.go")
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0644)
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Generate lens code (should not create file)
|
||||
@@ -776,7 +777,7 @@ type Extended struct {
|
||||
}
|
||||
`
|
||||
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0644)
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Parse the file
|
||||
@@ -824,7 +825,7 @@ type Person struct {
|
||||
`
|
||||
|
||||
testFile := filepath.Join(tmpDir, "test.go")
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0644)
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Generate lens code
|
||||
@@ -880,7 +881,7 @@ type Document struct {
|
||||
}
|
||||
`
|
||||
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0644)
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Parse the file
|
||||
@@ -922,7 +923,7 @@ type Container[T any] struct {
|
||||
}
|
||||
`
|
||||
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0644)
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Parse the file
|
||||
@@ -960,7 +961,7 @@ type Pair[K comparable, V any] struct {
|
||||
}
|
||||
`
|
||||
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0644)
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Parse the file
|
||||
@@ -998,7 +999,7 @@ type Box[T any] struct {
|
||||
`
|
||||
|
||||
testFile := filepath.Join(tmpDir, "test.go")
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0644)
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Generate lens code
|
||||
@@ -1049,7 +1050,7 @@ type ComparableBox[T comparable] struct {
|
||||
`
|
||||
|
||||
testFile := filepath.Join(tmpDir, "test.go")
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0644)
|
||||
err := os.WriteFile(testFile, []byte(testCode), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Generate lens code
|
||||
|
||||
@@ -19,6 +19,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
)
|
||||
|
||||
// Deprecated:
|
||||
@@ -176,7 +178,7 @@ func generateTraverseTuple1(
|
||||
}
|
||||
fmt.Fprintf(f, "F%d ~func(A%d) %s", j+1, j+1, hkt(fmt.Sprintf("T%d", j+1)))
|
||||
}
|
||||
if infix != "" {
|
||||
if S.IsNonEmpty(infix) {
|
||||
fmt.Fprintf(f, ", %s", infix)
|
||||
}
|
||||
// types
|
||||
@@ -209,7 +211,7 @@ func generateTraverseTuple1(
|
||||
fmt.Fprintf(f, " return A.TraverseTuple%d(\n", i)
|
||||
// map
|
||||
fmt.Fprintf(f, " Map[")
|
||||
if infix != "" {
|
||||
if S.IsNonEmpty(infix) {
|
||||
fmt.Fprintf(f, "%s, T1,", infix)
|
||||
} else {
|
||||
fmt.Fprintf(f, "T1,")
|
||||
@@ -231,7 +233,7 @@ func generateTraverseTuple1(
|
||||
fmt.Fprintf(f, " ")
|
||||
}
|
||||
fmt.Fprintf(f, "%s", tuple)
|
||||
if infix != "" {
|
||||
if S.IsNonEmpty(infix) {
|
||||
fmt.Fprintf(f, ", %s", infix)
|
||||
}
|
||||
fmt.Fprintf(f, ", T%d],\n", j+1)
|
||||
@@ -256,11 +258,11 @@ func generateSequenceTuple1(
|
||||
|
||||
fmt.Fprintf(f, "\n// SequenceTuple%d converts a [Tuple%d] of [%s] into an [%s].\n", i, i, hkt("T"), hkt(fmt.Sprintf("Tuple%d", i)))
|
||||
fmt.Fprintf(f, "func SequenceTuple%d[", i)
|
||||
if infix != "" {
|
||||
if S.IsNonEmpty(infix) {
|
||||
fmt.Fprintf(f, "%s", infix)
|
||||
}
|
||||
for j := 0; j < i; j++ {
|
||||
if infix != "" || j > 0 {
|
||||
if S.IsNonEmpty(infix) || j > 0 {
|
||||
fmt.Fprintf(f, ", ")
|
||||
}
|
||||
fmt.Fprintf(f, "T%d", j+1)
|
||||
@@ -276,7 +278,7 @@ func generateSequenceTuple1(
|
||||
fmt.Fprintf(f, " return A.SequenceTuple%d(\n", i)
|
||||
// map
|
||||
fmt.Fprintf(f, " Map[")
|
||||
if infix != "" {
|
||||
if S.IsNonEmpty(infix) {
|
||||
fmt.Fprintf(f, "%s, T1,", infix)
|
||||
} else {
|
||||
fmt.Fprintf(f, "T1,")
|
||||
@@ -298,7 +300,7 @@ func generateSequenceTuple1(
|
||||
fmt.Fprintf(f, " ")
|
||||
}
|
||||
fmt.Fprintf(f, "%s", tuple)
|
||||
if infix != "" {
|
||||
if S.IsNonEmpty(infix) {
|
||||
fmt.Fprintf(f, ", %s", infix)
|
||||
}
|
||||
fmt.Fprintf(f, ", T%d],\n", j+1)
|
||||
@@ -319,11 +321,11 @@ func generateSequenceT1(
|
||||
|
||||
fmt.Fprintf(f, "\n// SequenceT%d converts %d parameters of [%s] into a [%s].\n", i, i, hkt("T"), hkt(fmt.Sprintf("Tuple%d", i)))
|
||||
fmt.Fprintf(f, "func SequenceT%d[", i)
|
||||
if infix != "" {
|
||||
if S.IsNonEmpty(infix) {
|
||||
fmt.Fprintf(f, "%s", infix)
|
||||
}
|
||||
for j := 0; j < i; j++ {
|
||||
if infix != "" || j > 0 {
|
||||
if S.IsNonEmpty(infix) || j > 0 {
|
||||
fmt.Fprintf(f, ", ")
|
||||
}
|
||||
fmt.Fprintf(f, "T%d", j+1)
|
||||
@@ -339,7 +341,7 @@ func generateSequenceT1(
|
||||
fmt.Fprintf(f, " return A.SequenceT%d(\n", i)
|
||||
// map
|
||||
fmt.Fprintf(f, " Map[")
|
||||
if infix != "" {
|
||||
if S.IsNonEmpty(infix) {
|
||||
fmt.Fprintf(f, "%s, T1,", infix)
|
||||
} else {
|
||||
fmt.Fprintf(f, "T1,")
|
||||
@@ -361,7 +363,7 @@ func generateSequenceT1(
|
||||
fmt.Fprintf(f, " ")
|
||||
}
|
||||
fmt.Fprintf(f, "%s", tuple)
|
||||
if infix != "" {
|
||||
if S.IsNonEmpty(infix) {
|
||||
fmt.Fprintf(f, ", %s", infix)
|
||||
}
|
||||
fmt.Fprintf(f, ", T%d],\n", j+1)
|
||||
|
||||
@@ -8,9 +8,10 @@ This document explains how the `Sequence*` functions in the `context/readeriores
|
||||
2. [The Problem: Nested Function Application](#the-problem-nested-function-application)
|
||||
3. [The Solution: Sequence Functions](#the-solution-sequence-functions)
|
||||
4. [How Sequence Enables Point-Free Style](#how-sequence-enables-point-free-style)
|
||||
5. [Practical Benefits](#practical-benefits)
|
||||
6. [Examples](#examples)
|
||||
7. [Comparison: With and Without Sequence](#comparison-with-and-without-sequence)
|
||||
5. [TraverseReader: Introducing Dependencies](#traversereader-introducing-dependencies)
|
||||
6. [Practical Benefits](#practical-benefits)
|
||||
7. [Examples](#examples)
|
||||
8. [Comparison: With and Without Sequence](#comparison-with-and-without-sequence)
|
||||
|
||||
## What is Point-Free Style?
|
||||
|
||||
@@ -25,10 +26,7 @@ func double(x int) int {
|
||||
|
||||
**Point-free style (without points):**
|
||||
```go
|
||||
var double = F.Flow2(
|
||||
N.Mul(2),
|
||||
identity,
|
||||
)
|
||||
var double = N.Mul(2)
|
||||
```
|
||||
|
||||
The key benefit is that point-free style emphasizes **what** the function does (its transformation) rather than **how** it manipulates data.
|
||||
@@ -99,7 +97,7 @@ The `Sequence*` functions solve this by "flipping" or "sequencing" the nested st
|
||||
```go
|
||||
func SequenceReader[R, A any](
|
||||
ma ReaderIOResult[Reader[R, A]]
|
||||
) reader.Kleisli[context.Context, R, IOResult[A]]
|
||||
) Kleisli[R, A]
|
||||
```
|
||||
|
||||
**Type transformation:**
|
||||
@@ -115,7 +113,7 @@ Now `R` (the Reader's environment) comes **first**, before `context.Context`!
|
||||
```go
|
||||
func SequenceReaderIO[R, A any](
|
||||
ma ReaderIOResult[ReaderIO[R, A]]
|
||||
) reader.Kleisli[context.Context, R, IOResult[A]]
|
||||
) Kleisli[R, A]
|
||||
```
|
||||
|
||||
**Type transformation:**
|
||||
@@ -129,7 +127,7 @@ To: func(R) func(context.Context) func() Either[error, A]
|
||||
```go
|
||||
func SequenceReaderResult[R, A any](
|
||||
ma ReaderIOResult[ReaderResult[R, A]]
|
||||
) reader.Kleisli[context.Context, R, IOResult[A]]
|
||||
) Kleisli[R, A]
|
||||
```
|
||||
|
||||
**Type transformation:**
|
||||
@@ -222,6 +220,186 @@ authInfo := authService(ctx)()
|
||||
userInfo := userService(ctx)()
|
||||
```
|
||||
|
||||
## TraverseReader: Introducing Dependencies
|
||||
|
||||
While `SequenceReader` flips the parameter order of an existing nested structure, `TraverseReader` allows you to **introduce** a new Reader dependency into an existing computation.
|
||||
|
||||
### Function Signature
|
||||
|
||||
```go
|
||||
func TraverseReader[R, A, B any](
|
||||
f reader.Kleisli[R, A, B],
|
||||
) func(ReaderIOResult[A]) Kleisli[R, B]
|
||||
```
|
||||
|
||||
**Type transformation:**
|
||||
```
|
||||
Input: ReaderIOResult[A] = func(context.Context) func() Either[error, A]
|
||||
With: reader.Kleisli[R, A, B] = func(A) func(R) B
|
||||
Output: Kleisli[R, B] = func(R) func(context.Context) func() Either[error, B]
|
||||
```
|
||||
|
||||
### What It Does
|
||||
|
||||
`TraverseReader` takes:
|
||||
1. A Reader-based transformation `f: func(A) func(R) B` that depends on environment `R`
|
||||
2. Returns a function that transforms `ReaderIOResult[A]` into `Kleisli[R, B]`
|
||||
|
||||
This allows you to:
|
||||
- Add environment dependencies to computations that don't have them yet
|
||||
- Transform values within a ReaderIOResult using environment-dependent logic
|
||||
- Build composable pipelines where transformations depend on configuration
|
||||
|
||||
### Key Difference from SequenceReader
|
||||
|
||||
- **SequenceReader**: Works with computations that **already contain** a Reader (`ReaderIOResult[Reader[R, A]]`)
|
||||
- Flips the order so `R` comes first
|
||||
- No transformation of the value itself
|
||||
|
||||
- **TraverseReader**: Works with computations that **don't have** a Reader yet (`ReaderIOResult[A]`)
|
||||
- Introduces a new Reader dependency via a transformation function
|
||||
- Transforms `A` to `B` using environment `R`
|
||||
|
||||
### Example: Adding Configuration to a Computation
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Multiplier int
|
||||
Prefix string
|
||||
}
|
||||
|
||||
// Original computation that just produces an int
|
||||
getValue := func(ctx context.Context) func() Either[error, int] {
|
||||
return func() Either[error, int] {
|
||||
return Right[error](10)
|
||||
}
|
||||
}
|
||||
|
||||
// A Reader-based transformation that depends on Config
|
||||
formatWithConfig := func(n int) func(Config) string {
|
||||
return func(cfg Config) string {
|
||||
result := n * cfg.Multiplier
|
||||
return fmt.Sprintf("%s: %d", cfg.Prefix, result)
|
||||
}
|
||||
}
|
||||
|
||||
// Use TraverseReader to introduce Config dependency
|
||||
traversed := TraverseReader[Config, int, string](formatWithConfig)
|
||||
withConfig := traversed(getValue)
|
||||
|
||||
// Now we can provide Config to get the final result
|
||||
cfg := Config{Multiplier: 5, Prefix: "Result"}
|
||||
ctx := context.Background()
|
||||
result := withConfig(cfg)(ctx)() // Returns Right("Result: 50")
|
||||
```
|
||||
|
||||
### Point-Free Composition with TraverseReader
|
||||
|
||||
```go
|
||||
// Build a pipeline that introduces dependencies at each stage
|
||||
var pipeline = F.Flow4(
|
||||
loadValue, // ReaderIOResult[int]
|
||||
TraverseReader(multiplyByConfig), // Kleisli[Config, int]
|
||||
applyConfig(cfg), // ReaderIOResult[int]
|
||||
Chain(TraverseReader(formatWithStyle)), // Introduce another dependency
|
||||
)
|
||||
```
|
||||
|
||||
### When to Use TraverseReader vs SequenceReader
|
||||
|
||||
**Use SequenceReader when:**
|
||||
- Your computation already returns a Reader: `ReaderIOResult[Reader[R, A]]`
|
||||
- You just want to flip the parameter order
|
||||
- No transformation of the value is needed
|
||||
|
||||
```go
|
||||
// Already have Reader[Config, int]
|
||||
computation := getComputation() // ReaderIOResult[Reader[Config, int]]
|
||||
sequenced := SequenceReader[Config, int](computation)
|
||||
result := sequenced(cfg)(ctx)()
|
||||
```
|
||||
|
||||
**Use TraverseReader when:**
|
||||
- Your computation doesn't have a Reader yet: `ReaderIOResult[A]`
|
||||
- You want to transform the value using environment-dependent logic
|
||||
- You're introducing a new dependency into the pipeline
|
||||
|
||||
```go
|
||||
// Have ReaderIOResult[int], want to add Config dependency
|
||||
computation := getValue() // ReaderIOResult[int]
|
||||
traversed := TraverseReader[Config, int, string](formatWithConfig)
|
||||
withDep := traversed(computation)
|
||||
result := withDep(cfg)(ctx)()
|
||||
```
|
||||
|
||||
### Practical Example: Multi-Stage Processing
|
||||
|
||||
```go
|
||||
type DatabaseConfig struct {
|
||||
ConnectionString string
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
type FormattingConfig struct {
|
||||
DateFormat string
|
||||
Timezone string
|
||||
}
|
||||
|
||||
// Stage 1: Load raw data (no dependencies yet)
|
||||
loadData := func(ctx context.Context) func() Either[error, RawData] {
|
||||
// ... implementation
|
||||
}
|
||||
|
||||
// Stage 2: Process with database config
|
||||
processWithDB := func(raw RawData) func(DatabaseConfig) ProcessedData {
|
||||
return func(cfg DatabaseConfig) ProcessedData {
|
||||
// Use cfg.ConnectionString, cfg.Timeout
|
||||
return ProcessedData{/* ... */}
|
||||
}
|
||||
}
|
||||
|
||||
// Stage 3: Format with formatting config
|
||||
formatData := func(processed ProcessedData) func(FormattingConfig) string {
|
||||
return func(cfg FormattingConfig) string {
|
||||
// Use cfg.DateFormat, cfg.Timezone
|
||||
return "formatted result"
|
||||
}
|
||||
}
|
||||
|
||||
// Build pipeline introducing dependencies at each stage
|
||||
var pipeline = F.Flow3(
|
||||
loadData,
|
||||
TraverseReader[DatabaseConfig, RawData, ProcessedData](processWithDB),
|
||||
// Now we have Kleisli[DatabaseConfig, ProcessedData]
|
||||
applyConfig(dbConfig),
|
||||
// Now we have ReaderIOResult[ProcessedData]
|
||||
TraverseReader[FormattingConfig, ProcessedData, string](formatData),
|
||||
// Now we have Kleisli[FormattingConfig, string]
|
||||
)
|
||||
|
||||
// Execute with both configs
|
||||
result := pipeline(fmtConfig)(ctx)()
|
||||
```
|
||||
|
||||
### Combining TraverseReader and SequenceReader
|
||||
|
||||
You can combine both functions in complex pipelines:
|
||||
|
||||
```go
|
||||
// Start with nested Reader
|
||||
computation := getComputation() // ReaderIOResult[Reader[Config, User]]
|
||||
|
||||
var pipeline = F.Flow4(
|
||||
computation,
|
||||
SequenceReader[Config, User], // Flip to get Kleisli[Config, User]
|
||||
applyConfig(cfg), // Apply config, get ReaderIOResult[User]
|
||||
TraverseReader(enrichWithDatabase), // Add database dependency
|
||||
// Now have Kleisli[Database, EnrichedUser]
|
||||
)
|
||||
|
||||
result := pipeline(db)(ctx)()
|
||||
```
|
||||
|
||||
## Practical Benefits
|
||||
|
||||
### 1. **Improved Testability**
|
||||
|
||||
@@ -83,7 +83,7 @@ import (
|
||||
// )
|
||||
//
|
||||
//go:inline
|
||||
func SequenceReader[R, A any](ma ReaderIOResult[Reader[R, A]]) reader.Kleisli[context.Context, R, IOResult[A]] {
|
||||
func SequenceReader[R, A any](ma ReaderIOResult[Reader[R, A]]) Kleisli[R, A] {
|
||||
return RIOR.SequenceReader(ma)
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ func SequenceReader[R, A any](ma ReaderIOResult[Reader[R, A]]) reader.Kleisli[co
|
||||
// )
|
||||
//
|
||||
//go:inline
|
||||
func SequenceReaderIO[R, A any](ma ReaderIOResult[RIO.ReaderIO[R, A]]) reader.Kleisli[context.Context, R, IOResult[A]] {
|
||||
func SequenceReaderIO[R, A any](ma ReaderIOResult[RIO.ReaderIO[R, A]]) Kleisli[R, A] {
|
||||
return RIOR.SequenceReaderIO(ma)
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ func SequenceReaderIO[R, A any](ma ReaderIOResult[RIO.ReaderIO[R, A]]) reader.Kl
|
||||
// )
|
||||
//
|
||||
//go:inline
|
||||
func SequenceReaderResult[R, A any](ma ReaderIOResult[RR.ReaderResult[R, A]]) reader.Kleisli[context.Context, R, IOResult[A]] {
|
||||
func SequenceReaderResult[R, A any](ma ReaderIOResult[RR.ReaderResult[R, A]]) Kleisli[R, A] {
|
||||
return RIOR.SequenceReaderEither(ma)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
A "github.com/IBM/fp-go/v2/array"
|
||||
E "github.com/IBM/fp-go/v2/either"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -242,7 +243,7 @@ func TestTailRec_ComplexState(t *testing.T) {
|
||||
processStep := func(state ProcessState) ReaderIOResult[E.Either[ProcessState, []string]] {
|
||||
return func(ctx context.Context) IOEither[E.Either[ProcessState, []string]] {
|
||||
return func() Either[E.Either[ProcessState, []string]] {
|
||||
if len(state.items) == 0 {
|
||||
if A.IsEmpty(state.items) {
|
||||
return E.Right[error](E.Right[ProcessState](state.processed))
|
||||
}
|
||||
|
||||
@@ -304,7 +305,7 @@ func TestTailRec_CancellationDuringProcessing(t *testing.T) {
|
||||
processFileStep := func(state FileProcessState) ReaderIOResult[E.Either[FileProcessState, int]] {
|
||||
return func(ctx context.Context) IOEither[E.Either[FileProcessState, int]] {
|
||||
return func() Either[E.Either[FileProcessState, int]] {
|
||||
if len(state.files) == 0 {
|
||||
if A.IsEmpty(state.files) {
|
||||
return E.Right[error](E.Right[FileProcessState](state.processed))
|
||||
}
|
||||
|
||||
@@ -430,5 +431,3 @@ func TestTailRec_ContextWithValue(t *testing.T) {
|
||||
|
||||
assert.Equal(t, E.Of[error]("Done!"), result)
|
||||
}
|
||||
|
||||
// Made with Bob
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
A "github.com/IBM/fp-go/v2/array"
|
||||
E "github.com/IBM/fp-go/v2/either"
|
||||
R "github.com/IBM/fp-go/v2/result"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -129,7 +130,7 @@ func TestTailRecSumList(t *testing.T) {
|
||||
|
||||
sumStep := func(state State) ReaderResult[E.Either[State, int]] {
|
||||
return func(ctx context.Context) Result[E.Either[State, int]] {
|
||||
if len(state.list) == 0 {
|
||||
if A.IsEmpty(state.list) {
|
||||
return R.Of(E.Right[State](state.sum))
|
||||
}
|
||||
return R.Of(E.Left[int](State{state.list[1:], state.sum + state.list[0]}))
|
||||
@@ -495,5 +496,3 @@ func TestTailRecComplexState(t *testing.T) {
|
||||
|
||||
assert.Equal(t, R.Of("sum=15, product=120"), result)
|
||||
}
|
||||
|
||||
// Made with Bob
|
||||
|
||||
@@ -103,11 +103,11 @@ func (t *token[T]) Unerase(val any) Result[T] {
|
||||
func (t *token[T]) ProviderFactory() Option[DIE.ProviderFactory] {
|
||||
return t.base.providerFactory
|
||||
}
|
||||
func makeTokenBase(name string, id string, typ int, providerFactory Option[DIE.ProviderFactory]) *tokenBase {
|
||||
func makeTokenBase(name, id string, typ int, providerFactory Option[DIE.ProviderFactory]) *tokenBase {
|
||||
return &tokenBase{name, id, typ, providerFactory}
|
||||
}
|
||||
|
||||
func makeToken[T any](name string, id string, typ int, unerase func(val any) Result[T], providerFactory Option[DIE.ProviderFactory]) Dependency[T] {
|
||||
func makeToken[T any](name, id string, typ int, unerase func(val any) Result[T], providerFactory Option[DIE.ProviderFactory]) Dependency[T] {
|
||||
return &token[T]{makeTokenBase(name, id, typ, providerFactory), unerase}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ func TraverseArray[E, A, B any](f Kleisli[E, A, B]) Kleisli[E, []A, []B] {
|
||||
// Example:
|
||||
//
|
||||
// validate := func(i int, s string) either.Either[error, string] {
|
||||
// if len(s) > 0 {
|
||||
// if S.IsNonEmpty(s) {
|
||||
// return either.Right[error](fmt.Sprintf("%d:%s", i, s))
|
||||
// }
|
||||
// return either.Left[string](fmt.Errorf("empty at index %d", i))
|
||||
@@ -105,7 +105,7 @@ func TraverseArrayWithIndexG[GA ~[]A, GB ~[]B, E, A, B any](f func(int, A) Eithe
|
||||
// Example:
|
||||
//
|
||||
// validate := func(i int, s string) either.Either[error, string] {
|
||||
// if len(s) > 0 {
|
||||
// if S.IsNonEmpty(s) {
|
||||
// return either.Right[error](fmt.Sprintf("%d:%s", i, s))
|
||||
// }
|
||||
// return either.Left[string](fmt.Errorf("empty at index %d", i))
|
||||
|
||||
@@ -34,7 +34,7 @@ func Curry0[R any](f func() (R, error)) func() Either[error, R] {
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// parse := func(s string) (int, error) { return strconv.Atoi(s) }
|
||||
// parse := strconv.Atoi
|
||||
// curried := either.Curry1(parse)
|
||||
// result := curried("42") // Right(42)
|
||||
func Curry1[T1, R any](f func(T1) (R, error)) func(T1) Either[error, R] {
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
M "github.com/IBM/fp-go/v2/monoid"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -305,7 +306,7 @@ func TestTraverseArray(t *testing.T) {
|
||||
// Test TraverseArrayWithIndex
|
||||
func TestTraverseArrayWithIndex(t *testing.T) {
|
||||
validate := func(i int, s string) Either[error, string] {
|
||||
if len(s) > 0 {
|
||||
if S.IsNonEmpty(s) {
|
||||
return Right[error](fmt.Sprintf("%d:%s", i, s))
|
||||
}
|
||||
return Left[string](fmt.Errorf("empty at index %d", i))
|
||||
@@ -334,7 +335,7 @@ func TestTraverseRecord(t *testing.T) {
|
||||
// Test TraverseRecordWithIndex
|
||||
func TestTraverseRecordWithIndex(t *testing.T) {
|
||||
validate := func(k string, v string) Either[error, string] {
|
||||
if len(v) > 0 {
|
||||
if S.IsNonEmpty(v) {
|
||||
return Right[error](k + ":" + v)
|
||||
}
|
||||
return Left[string](fmt.Errorf("empty value for key %s", k))
|
||||
@@ -373,7 +374,7 @@ func TestCurry0(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCurry1(t *testing.T) {
|
||||
parse := func(s string) (int, error) { return strconv.Atoi(s) }
|
||||
parse := strconv.Atoi
|
||||
curried := Curry1(parse)
|
||||
result := curried("42")
|
||||
assert.Equal(t, Right[error](42), result)
|
||||
|
||||
@@ -41,7 +41,7 @@ import (
|
||||
// increment := N.Add(1)
|
||||
// result := endomorphism.MonadAp(double, increment) // Composes: double ∘ increment
|
||||
// // result(5) = double(increment(5)) = double(6) = 12
|
||||
func MonadAp[A any](fab Endomorphism[A], fa Endomorphism[A]) Endomorphism[A] {
|
||||
func MonadAp[A any](fab, fa Endomorphism[A]) Endomorphism[A] {
|
||||
return MonadCompose(fab, fa)
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ func Map[A any](f Endomorphism[A]) Operator[A] {
|
||||
// // Compare with MonadCompose which executes RIGHT-TO-LEFT:
|
||||
// composed := endomorphism.MonadCompose(increment, double)
|
||||
// result2 := composed(5) // (5 * 2) + 1 = 11 (same result, different parameter order)
|
||||
func MonadChain[A any](ma Endomorphism[A], f Endomorphism[A]) Endomorphism[A] {
|
||||
func MonadChain[A any](ma, f Endomorphism[A]) Endomorphism[A] {
|
||||
return function.Flow2(ma, f)
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ func MonadChain[A any](ma Endomorphism[A], f Endomorphism[A]) Endomorphism[A] {
|
||||
// log := func(x int) int { fmt.Println(x); return x }
|
||||
// chained := endomorphism.MonadChainFirst(double, log)
|
||||
// result := chained(5) // Prints 10, returns 10
|
||||
func MonadChainFirst[A any](ma Endomorphism[A], f Endomorphism[A]) Endomorphism[A] {
|
||||
func MonadChainFirst[A any](ma, f Endomorphism[A]) Endomorphism[A] {
|
||||
return func(a A) A {
|
||||
result := ma(a)
|
||||
f(result) // Apply f for its effect
|
||||
|
||||
@@ -72,9 +72,7 @@ func TestFromStrictEquals(t *testing.T) {
|
||||
|
||||
func TestFromEquals(t *testing.T) {
|
||||
t.Run("case-insensitive string equality", func(t *testing.T) {
|
||||
caseInsensitiveEq := FromEquals(func(a, b string) bool {
|
||||
return strings.EqualFold(a, b)
|
||||
})
|
||||
caseInsensitiveEq := FromEquals(strings.EqualFold)
|
||||
|
||||
assert.True(t, caseInsensitiveEq.Equals("hello", "HELLO"))
|
||||
assert.True(t, caseInsensitiveEq.Equals("Hello", "hello"))
|
||||
@@ -243,9 +241,7 @@ func TestContramap(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("case-insensitive name comparison", func(t *testing.T) {
|
||||
caseInsensitiveEq := FromEquals(func(a, b string) bool {
|
||||
return strings.EqualFold(a, b)
|
||||
})
|
||||
caseInsensitiveEq := FromEquals(strings.EqualFold)
|
||||
|
||||
personEqByNameCI := Contramap(func(p Person) string {
|
||||
return p.Name
|
||||
|
||||
@@ -51,7 +51,7 @@ package function
|
||||
// )
|
||||
// result := classify(5) // "positive"
|
||||
// result2 := classify(-3) // "non-positive"
|
||||
func Ternary[A, B any](pred func(A) bool, onTrue func(A) B, onFalse func(A) B) func(A) B {
|
||||
func Ternary[A, B any](pred func(A) bool, onTrue, onFalse func(A) B) func(A) B {
|
||||
return func(a A) B {
|
||||
if pred(a) {
|
||||
return onTrue(a)
|
||||
|
||||
@@ -246,7 +246,7 @@ func (builder *Builder) GetTargetURL() Result[string] {
|
||||
parseQuery,
|
||||
result.Map(F.Flow2(
|
||||
F.Curry2(FM.ValuesMonoid.Concat)(builder.GetQuery()),
|
||||
(url.Values).Encode,
|
||||
url.Values.Encode,
|
||||
)),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -28,7 +28,7 @@ func TestMkdir(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
newDir := filepath.Join(tmpDir, "testdir")
|
||||
|
||||
result := Mkdir(newDir, 0755)
|
||||
result := Mkdir(newDir, 0o755)
|
||||
path, err := result()
|
||||
|
||||
assert.NoError(t, err)
|
||||
@@ -43,14 +43,14 @@ func TestMkdir(t *testing.T) {
|
||||
t.Run("mkdir with existing directory", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
result := Mkdir(tmpDir, 0755)
|
||||
result := Mkdir(tmpDir, 0o755)
|
||||
_, err := result()
|
||||
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("mkdir with parent directory not existing", func(t *testing.T) {
|
||||
result := Mkdir("/non/existent/parent/child", 0755)
|
||||
result := Mkdir("/non/existent/parent/child", 0o755)
|
||||
_, err := result()
|
||||
|
||||
assert.Error(t, err)
|
||||
@@ -62,7 +62,7 @@ func TestMkdirAll(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
nestedDir := filepath.Join(tmpDir, "level1", "level2", "level3")
|
||||
|
||||
result := MkdirAll(nestedDir, 0755)
|
||||
result := MkdirAll(nestedDir, 0o755)
|
||||
path, err := result()
|
||||
|
||||
assert.NoError(t, err)
|
||||
@@ -88,7 +88,7 @@ func TestMkdirAll(t *testing.T) {
|
||||
t.Run("mkdirall with existing directory", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
result := MkdirAll(tmpDir, 0755)
|
||||
result := MkdirAll(tmpDir, 0o755)
|
||||
path, err := result()
|
||||
|
||||
// MkdirAll should succeed even if directory exists
|
||||
@@ -100,7 +100,7 @@ func TestMkdirAll(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
newDir := filepath.Join(tmpDir, "single")
|
||||
|
||||
result := MkdirAll(newDir, 0755)
|
||||
result := MkdirAll(newDir, 0o755)
|
||||
path, err := result()
|
||||
|
||||
assert.NoError(t, err)
|
||||
@@ -116,11 +116,11 @@ func TestMkdirAll(t *testing.T) {
|
||||
filePath := filepath.Join(tmpDir, "file.txt")
|
||||
|
||||
// Create a file
|
||||
err := os.WriteFile(filePath, []byte("content"), 0644)
|
||||
err := os.WriteFile(filePath, []byte("content"), 0o644)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Try to create a directory where file exists
|
||||
result := MkdirAll(filepath.Join(filePath, "subdir"), 0755)
|
||||
result := MkdirAll(filepath.Join(filePath, "subdir"), 0o755)
|
||||
_, err = result()
|
||||
|
||||
assert.Error(t, err)
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestOpen(t *testing.T) {
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
// Write some content
|
||||
err = os.WriteFile(tmpPath, []byte("test content"), 0644)
|
||||
err = os.WriteFile(tmpPath, []byte("test content"), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test Open
|
||||
@@ -127,7 +127,7 @@ func TestWriteFile(t *testing.T) {
|
||||
testPath := filepath.Join(tmpDir, "write-test.txt")
|
||||
testData := []byte("test data")
|
||||
|
||||
result := WriteFile(testPath, 0644)(testData)
|
||||
result := WriteFile(testPath, 0o644)(testData)
|
||||
returnedData, err := result()
|
||||
|
||||
assert.NoError(t, err)
|
||||
@@ -141,7 +141,7 @@ func TestWriteFile(t *testing.T) {
|
||||
|
||||
t.Run("write to invalid path", func(t *testing.T) {
|
||||
testData := []byte("test data")
|
||||
result := WriteFile("/non/existent/dir/file.txt", 0644)(testData)
|
||||
result := WriteFile("/non/existent/dir/file.txt", 0o644)(testData)
|
||||
_, err := result()
|
||||
|
||||
assert.Error(t, err)
|
||||
@@ -155,12 +155,12 @@ func TestWriteFile(t *testing.T) {
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
// Write initial content
|
||||
err = os.WriteFile(tmpPath, []byte("initial"), 0644)
|
||||
err = os.WriteFile(tmpPath, []byte("initial"), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Overwrite with new content
|
||||
newData := []byte("overwritten")
|
||||
result := WriteFile(tmpPath, 0644)(newData)
|
||||
result := WriteFile(tmpPath, 0o644)(newData)
|
||||
returnedData, err := result()
|
||||
|
||||
assert.NoError(t, err)
|
||||
@@ -212,7 +212,7 @@ func TestClose(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify file is closed by attempting to write
|
||||
_, writeErr := tmpFile.Write([]byte("test"))
|
||||
_, writeErr := tmpFile.WriteString("test")
|
||||
assert.Error(t, writeErr)
|
||||
})
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ func TestReadAll(t *testing.T) {
|
||||
largeContent[i] = byte('A' + (i % 26))
|
||||
}
|
||||
|
||||
err := os.WriteFile(testPath, largeContent, 0644)
|
||||
err := os.WriteFile(testPath, largeContent, 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
result := ReadAll(Open(testPath))
|
||||
|
||||
@@ -70,7 +70,7 @@ func TestWriteAll(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify file is closed by trying to write to it
|
||||
_, writeErr := capturedFile.Write([]byte("more"))
|
||||
_, writeErr := capturedFile.WriteString("more")
|
||||
assert.Error(t, writeErr)
|
||||
})
|
||||
|
||||
@@ -147,7 +147,7 @@ func TestWrite(t *testing.T) {
|
||||
|
||||
useFile := func(f *os.File) IOResult[string] {
|
||||
return func() (string, error) {
|
||||
_, err := f.Write([]byte("data"))
|
||||
_, err := f.WriteString("data")
|
||||
return "success", err
|
||||
}
|
||||
}
|
||||
@@ -158,7 +158,7 @@ func TestWrite(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify file is closed
|
||||
_, writeErr := capturedFile.Write([]byte("more"))
|
||||
_, writeErr := capturedFile.WriteString("more")
|
||||
assert.Error(t, writeErr)
|
||||
})
|
||||
|
||||
@@ -183,7 +183,7 @@ func TestWrite(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
|
||||
// Verify file is still closed even on error
|
||||
_, writeErr := capturedFile.Write([]byte("more"))
|
||||
_, writeErr := capturedFile.WriteString("more")
|
||||
assert.Error(t, writeErr)
|
||||
})
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ func Requester(builder *R.Builder) IOEH.Requester {
|
||||
|
||||
withoutBody := F.Curry2(func(url string, method string) IOResult[*http.Request] {
|
||||
return func() (*http.Request, error) {
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
req, err := http.NewRequest(method, url, http.NoBody)
|
||||
if err == nil {
|
||||
H.Monoid.Concat(req.Header, builder.GetHeaders())
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/internal/utils"
|
||||
"github.com/IBM/fp-go/v2/io"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -111,7 +112,7 @@ func TestChainWithIO(t *testing.T) {
|
||||
Of("test"),
|
||||
ChainIOK(func(s string) IO[bool] {
|
||||
return func() bool {
|
||||
return len(s) > 0
|
||||
return S.IsNonEmpty(s)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"testing"
|
||||
|
||||
N "github.com/IBM/fp-go/v2/number"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
)
|
||||
|
||||
// Benchmark shallow chain (1 step)
|
||||
@@ -100,7 +101,7 @@ func BenchmarkChain_RealWorld_Validation(b *testing.B) {
|
||||
|
||||
// Step 1: Validate not empty
|
||||
v1, ok1 := Chain(func(s string) (string, bool) {
|
||||
if len(s) > 0 {
|
||||
if S.IsNonEmpty(s) {
|
||||
return s, true
|
||||
}
|
||||
return "", false
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
A "github.com/IBM/fp-go/v2/array"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
I "github.com/IBM/fp-go/v2/iterator/iter"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -225,7 +226,7 @@ func TestTraverseIter_ComplexTransformation(t *testing.T) {
|
||||
}
|
||||
|
||||
validatePerson := func(name string) (Person, bool) {
|
||||
if name == "" {
|
||||
if S.IsEmpty(name) {
|
||||
return None[Person]()
|
||||
}
|
||||
return Some(Person{Name: name, Age: len(name)})
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
L "github.com/IBM/fp-go/v2/logging"
|
||||
)
|
||||
|
||||
func _log[A any](left func(string, ...any), right func(string, ...any), prefix string) Operator[A, A] {
|
||||
func _log[A any](left, right func(string, ...any), prefix string) Operator[A, A] {
|
||||
return func(a A, aok bool) (A, bool) {
|
||||
if aok {
|
||||
right("%s: %v", prefix, a)
|
||||
|
||||
@@ -42,6 +42,8 @@ import (
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import S "github.com/IBM/fp-go/v2/string"
|
||||
//
|
||||
// type Database struct {
|
||||
// ConnectionString string
|
||||
// }
|
||||
@@ -57,7 +59,7 @@ import (
|
||||
// }
|
||||
// return func(db Database) func() (string, error) {
|
||||
// return func() (string, error) {
|
||||
// if db.ConnectionString == "" {
|
||||
// if S.IsEmpty(db.ConnectionString) {
|
||||
// return "", errors.New("empty connection")
|
||||
// }
|
||||
// return fmt.Sprintf("Query on %s with timeout %d",
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
N "github.com/IBM/fp-go/v2/number"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -89,7 +90,7 @@ func TestSequence(t *testing.T) {
|
||||
return func() (ReaderIOResult[string, int], error) {
|
||||
return func(s string) IOResult[int] {
|
||||
return func() (int, error) {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return 0, expectedError
|
||||
}
|
||||
return x + len(s), nil
|
||||
@@ -140,7 +141,7 @@ func TestSequence(t *testing.T) {
|
||||
}
|
||||
return func(db Database) IOResult[string] {
|
||||
return func() (string, error) {
|
||||
if db.ConnectionString == "" {
|
||||
if S.IsEmpty(db.ConnectionString) {
|
||||
return "", errors.New("empty connection string")
|
||||
}
|
||||
return fmt.Sprintf("Query on %s with timeout %d",
|
||||
@@ -400,7 +401,7 @@ func TestTraverse(t *testing.T) {
|
||||
kleisli := func(a int) ReaderIOResult[string, int] {
|
||||
return func(s string) IOResult[int] {
|
||||
return func() (int, error) {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return 0, expectedError
|
||||
}
|
||||
return a + len(s), nil
|
||||
|
||||
@@ -41,6 +41,8 @@ import (
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import S "github.com/IBM/fp-go/v2/string"
|
||||
//
|
||||
// type Database struct {
|
||||
// ConnectionString string
|
||||
// }
|
||||
@@ -54,7 +56,7 @@ import (
|
||||
// return nil, errors.New("invalid timeout")
|
||||
// }
|
||||
// return func(db Database) (string, error) {
|
||||
// if db.ConnectionString == "" {
|
||||
// if S.IsEmpty(db.ConnectionString) {
|
||||
// return "", errors.New("empty connection")
|
||||
// }
|
||||
// return fmt.Sprintf("Query on %s with timeout %d",
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
N "github.com/IBM/fp-go/v2/number"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -79,7 +80,7 @@ func TestSequence(t *testing.T) {
|
||||
|
||||
original := func(x int) (ReaderResult[string, int], error) {
|
||||
return func(s string) (int, error) {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return 0, expectedError
|
||||
}
|
||||
return x + len(s), nil
|
||||
@@ -122,7 +123,7 @@ func TestSequence(t *testing.T) {
|
||||
return nil, errors.New("invalid timeout")
|
||||
}
|
||||
return func(db Database) (string, error) {
|
||||
if db.ConnectionString == "" {
|
||||
if S.IsEmpty(db.ConnectionString) {
|
||||
return "", errors.New("empty connection string")
|
||||
}
|
||||
return fmt.Sprintf("Query on %s with timeout %d",
|
||||
@@ -314,7 +315,7 @@ func TestTraverse(t *testing.T) {
|
||||
|
||||
kleisli := func(a int) ReaderResult[string, int] {
|
||||
return func(s string) (int, error) {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return 0, expectedError
|
||||
}
|
||||
return a + len(s), nil
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
N "github.com/IBM/fp-go/v2/number"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
RES "github.com/IBM/fp-go/v2/result"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -292,11 +291,11 @@ func TestChainReaderK(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestChainEitherK(t *testing.T) {
|
||||
parseInt := func(s string) RES.Result[int] {
|
||||
parseInt := func(s string) Result[int] {
|
||||
if s == "42" {
|
||||
return RES.Of(42)
|
||||
return result.Of(42)
|
||||
}
|
||||
return RES.Left[int](errors.New("parse error"))
|
||||
return result.Left[int](errors.New("parse error"))
|
||||
}
|
||||
|
||||
chain := ChainEitherK[MyContext](parseInt)
|
||||
|
||||
@@ -129,7 +129,7 @@ func TraverseArray[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B] {
|
||||
// Example - Annotate with index:
|
||||
//
|
||||
// annotate := func(i int, s string) (string, error) {
|
||||
// if len(s) == 0 {
|
||||
// if S.IsEmpty(s) {
|
||||
// return "", fmt.Errorf("empty string at index %d", i)
|
||||
// }
|
||||
// return fmt.Sprintf("[%d]=%s", i, s), nil
|
||||
@@ -170,7 +170,7 @@ func TraverseArrayWithIndexG[GA ~[]A, GB ~[]B, A, B any](f func(int, A) (B, erro
|
||||
// Example - Validate with position info:
|
||||
//
|
||||
// check := func(i int, s string) (string, error) {
|
||||
// if len(s) == 0 {
|
||||
// if S.IsEmpty(s) {
|
||||
// return "", fmt.Errorf("empty value at position %d", i)
|
||||
// }
|
||||
// return strings.ToUpper(s), nil
|
||||
|
||||
@@ -21,15 +21,14 @@ import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestTraverseArrayG_Success tests successful traversal of an array with all valid elements
|
||||
func TestTraverseArrayG_Success(t *testing.T) {
|
||||
parse := func(s string) (int, error) {
|
||||
return strconv.Atoi(s)
|
||||
}
|
||||
parse := strconv.Atoi
|
||||
|
||||
result, err := TraverseArrayG[[]string, []int](parse)([]string{"1", "2", "3"})
|
||||
|
||||
@@ -39,9 +38,7 @@ func TestTraverseArrayG_Success(t *testing.T) {
|
||||
|
||||
// TestTraverseArrayG_Error tests that traversal short-circuits on first error
|
||||
func TestTraverseArrayG_Error(t *testing.T) {
|
||||
parse := func(s string) (int, error) {
|
||||
return strconv.Atoi(s)
|
||||
}
|
||||
parse := strconv.Atoi
|
||||
|
||||
result, err := TraverseArrayG[[]string, []int](parse)([]string{"1", "bad", "3"})
|
||||
|
||||
@@ -51,9 +48,7 @@ func TestTraverseArrayG_Error(t *testing.T) {
|
||||
|
||||
// TestTraverseArrayG_EmptyArray tests traversal of an empty array
|
||||
func TestTraverseArrayG_EmptyArray(t *testing.T) {
|
||||
parse := func(s string) (int, error) {
|
||||
return strconv.Atoi(s)
|
||||
}
|
||||
parse := strconv.Atoi
|
||||
|
||||
result, err := TraverseArrayG[[]string, []int](parse)([]string{})
|
||||
|
||||
@@ -64,9 +59,7 @@ func TestTraverseArrayG_EmptyArray(t *testing.T) {
|
||||
|
||||
// TestTraverseArrayG_SingleElement tests traversal with a single element
|
||||
func TestTraverseArrayG_SingleElement(t *testing.T) {
|
||||
parse := func(s string) (int, error) {
|
||||
return strconv.Atoi(s)
|
||||
}
|
||||
parse := strconv.Atoi
|
||||
|
||||
result, err := TraverseArrayG[[]string, []int](parse)([]string{"42"})
|
||||
|
||||
@@ -96,9 +89,7 @@ func TestTraverseArrayG_CustomSliceType(t *testing.T) {
|
||||
type StringSlice []string
|
||||
type IntSlice []int
|
||||
|
||||
parse := func(s string) (int, error) {
|
||||
return strconv.Atoi(s)
|
||||
}
|
||||
parse := strconv.Atoi
|
||||
|
||||
input := StringSlice{"1", "2", "3"}
|
||||
result, err := TraverseArrayG[StringSlice, IntSlice](parse)(input)
|
||||
@@ -178,7 +169,7 @@ func TestTraverseArray_EmptyArray(t *testing.T) {
|
||||
// TestTraverseArray_DifferentTypes tests transformation between different types
|
||||
func TestTraverseArray_DifferentTypes(t *testing.T) {
|
||||
toLength := func(s string) (int, error) {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return 0, errors.New("empty string")
|
||||
}
|
||||
return len(s), nil
|
||||
@@ -193,7 +184,7 @@ func TestTraverseArray_DifferentTypes(t *testing.T) {
|
||||
// TestTraverseArrayWithIndexG_Success tests successful indexed traversal
|
||||
func TestTraverseArrayWithIndexG_Success(t *testing.T) {
|
||||
annotate := func(i int, s string) (string, error) {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return "", fmt.Errorf("empty string at index %d", i)
|
||||
}
|
||||
return fmt.Sprintf("[%d]=%s", i, s), nil
|
||||
@@ -208,7 +199,7 @@ func TestTraverseArrayWithIndexG_Success(t *testing.T) {
|
||||
// TestTraverseArrayWithIndexG_Error tests error handling with index
|
||||
func TestTraverseArrayWithIndexG_Error(t *testing.T) {
|
||||
annotate := func(i int, s string) (string, error) {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return "", fmt.Errorf("empty string at index %d", i)
|
||||
}
|
||||
return fmt.Sprintf("[%d]=%s", i, s), nil
|
||||
@@ -284,7 +275,7 @@ func TestTraverseArrayWithIndexG_CustomSliceType(t *testing.T) {
|
||||
// TestTraverseArrayWithIndex_Success tests successful indexed traversal
|
||||
func TestTraverseArrayWithIndex_Success(t *testing.T) {
|
||||
check := func(i int, s string) (string, error) {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return "", fmt.Errorf("empty value at position %d", i)
|
||||
}
|
||||
return fmt.Sprintf("%d_%s", i, s), nil
|
||||
@@ -299,7 +290,7 @@ func TestTraverseArrayWithIndex_Success(t *testing.T) {
|
||||
// TestTraverseArrayWithIndex_Error tests error with position info
|
||||
func TestTraverseArrayWithIndex_Error(t *testing.T) {
|
||||
check := func(i int, s string) (string, error) {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return "", fmt.Errorf("empty value at position %d", i)
|
||||
}
|
||||
return s, nil
|
||||
|
||||
@@ -30,7 +30,7 @@ func Curry0[R any](f func() (R, error)) func() (R, error) {
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// parse := func(s string) (int, error) { return strconv.Atoi(s) }
|
||||
// parse := strconv.Atoi
|
||||
// curried := either.Curry1(parse)
|
||||
// result := curried("42") // Right(42)
|
||||
func Curry1[T1, R any](f func(T1) (R, error)) func(T1) (R, error) {
|
||||
|
||||
@@ -65,6 +65,6 @@ func bodyRequest(method string) func(string) func([]byte) (*http.Request, error)
|
||||
|
||||
func noBodyRequest(method string) func(string) (*http.Request, error) {
|
||||
return func(url string) (*http.Request, error) {
|
||||
return http.NewRequest(method, url, nil)
|
||||
return http.NewRequest(method, url, http.NoBody)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
L "github.com/IBM/fp-go/v2/logging"
|
||||
)
|
||||
|
||||
func _log[A any](left func(string, ...any), right func(string, ...any), prefix string) Operator[A, A] {
|
||||
func _log[A any](left, right func(string, ...any), prefix string) Operator[A, A] {
|
||||
return func(a A, err error) (A, error) {
|
||||
if err != nil {
|
||||
left("%s: %v", prefix, err)
|
||||
|
||||
@@ -21,15 +21,14 @@ import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestTraverseRecordG_Success tests successful traversal of a map
|
||||
func TestTraverseRecordG_Success(t *testing.T) {
|
||||
parse := func(s string) (int, error) {
|
||||
return strconv.Atoi(s)
|
||||
}
|
||||
parse := strconv.Atoi
|
||||
|
||||
input := map[string]string{"a": "1", "b": "2", "c": "3"}
|
||||
result, err := TraverseRecordG[map[string]string, map[string]int](parse)(input)
|
||||
@@ -42,9 +41,7 @@ func TestTraverseRecordG_Success(t *testing.T) {
|
||||
|
||||
// TestTraverseRecordG_Error tests that traversal short-circuits on error
|
||||
func TestTraverseRecordG_Error(t *testing.T) {
|
||||
parse := func(s string) (int, error) {
|
||||
return strconv.Atoi(s)
|
||||
}
|
||||
parse := strconv.Atoi
|
||||
|
||||
input := map[string]string{"a": "1", "b": "bad", "c": "3"}
|
||||
result, err := TraverseRecordG[map[string]string, map[string]int](parse)(input)
|
||||
@@ -55,9 +52,7 @@ func TestTraverseRecordG_Error(t *testing.T) {
|
||||
|
||||
// TestTraverseRecordG_EmptyMap tests traversal of an empty map
|
||||
func TestTraverseRecordG_EmptyMap(t *testing.T) {
|
||||
parse := func(s string) (int, error) {
|
||||
return strconv.Atoi(s)
|
||||
}
|
||||
parse := strconv.Atoi
|
||||
|
||||
input := map[string]string{}
|
||||
result, err := TraverseRecordG[map[string]string, map[string]int](parse)(input)
|
||||
@@ -72,9 +67,7 @@ func TestTraverseRecordG_CustomMapType(t *testing.T) {
|
||||
type StringMap map[string]string
|
||||
type IntMap map[string]int
|
||||
|
||||
parse := func(s string) (int, error) {
|
||||
return strconv.Atoi(s)
|
||||
}
|
||||
parse := strconv.Atoi
|
||||
|
||||
input := StringMap{"x": "10", "y": "20"}
|
||||
result, err := TraverseRecordG[StringMap, IntMap](parse)(input)
|
||||
@@ -128,7 +121,7 @@ func TestTraverseRecord_ValidationError(t *testing.T) {
|
||||
// TestTraverseRecordWithIndexG_Success tests successful indexed traversal
|
||||
func TestTraverseRecordWithIndexG_Success(t *testing.T) {
|
||||
annotate := func(k string, v string) (string, error) {
|
||||
if len(v) == 0 {
|
||||
if S.IsEmpty(v) {
|
||||
return "", fmt.Errorf("empty value for key %s", k)
|
||||
}
|
||||
return fmt.Sprintf("%s=%s", k, v), nil
|
||||
@@ -145,7 +138,7 @@ func TestTraverseRecordWithIndexG_Success(t *testing.T) {
|
||||
// TestTraverseRecordWithIndexG_Error tests error handling with key
|
||||
func TestTraverseRecordWithIndexG_Error(t *testing.T) {
|
||||
annotate := func(k string, v string) (string, error) {
|
||||
if len(v) == 0 {
|
||||
if S.IsEmpty(v) {
|
||||
return "", fmt.Errorf("empty value for key %s", k)
|
||||
}
|
||||
return v, nil
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
N "github.com/IBM/fp-go/v2/number"
|
||||
S "github.com/IBM/fp-go/v2/semigroup"
|
||||
STR "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -43,7 +44,7 @@ func makeErrorListSemigroup() S.Semigroup[error] {
|
||||
var msgs []string
|
||||
if strings.HasPrefix(msg1, "[") && strings.HasSuffix(msg1, "]") {
|
||||
trimmed := strings.Trim(msg1, "[]")
|
||||
if trimmed != "" {
|
||||
if STR.IsNonEmpty(trimmed) {
|
||||
msgs = strings.Split(trimmed, ", ")
|
||||
}
|
||||
} else {
|
||||
@@ -52,7 +53,7 @@ func makeErrorListSemigroup() S.Semigroup[error] {
|
||||
|
||||
if strings.HasPrefix(msg2, "[") && strings.HasSuffix(msg2, "]") {
|
||||
trimmed := strings.Trim(msg2, "[]")
|
||||
if trimmed != "" {
|
||||
if STR.IsNonEmpty(trimmed) {
|
||||
msgs = append(msgs, strings.Split(trimmed, ", ")...)
|
||||
}
|
||||
} else {
|
||||
@@ -160,7 +161,7 @@ func TestApV_StringTransformation(t *testing.T) {
|
||||
sg := makeErrorConcatSemigroup()
|
||||
apv := ApV[string, string](sg)
|
||||
|
||||
toUpper := func(s string) string { return strings.ToUpper(s) }
|
||||
toUpper := strings.ToUpper
|
||||
|
||||
value, verr := Right("hello")
|
||||
fn, ferr := Right(toUpper)
|
||||
|
||||
@@ -166,7 +166,7 @@ func MonadMapWithIndex[GA ~[]A, GB ~[]B, A, B any](as GA, f func(idx int, a A) B
|
||||
}
|
||||
|
||||
func ConstNil[GA ~[]A, A any]() GA {
|
||||
return (GA)(nil)
|
||||
return GA(nil)
|
||||
}
|
||||
|
||||
func Concat[GT ~[]T, T any](left, right GT) GT {
|
||||
|
||||
@@ -45,7 +45,7 @@ func Requester(builder *R.Builder) IOEH.Requester {
|
||||
|
||||
withoutBody := F.Curry2(func(url string, method string) IOEither[*http.Request] {
|
||||
return ioeither.TryCatchError(func() (*http.Request, error) {
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
req, err := http.NewRequest(method, url, http.NoBody)
|
||||
if err == nil {
|
||||
H.Monoid.Concat(req.Header, builder.GetHeaders())
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
A "github.com/IBM/fp-go/v2/array"
|
||||
E "github.com/IBM/fp-go/v2/either"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -107,7 +108,7 @@ func TestTailRecSumList(t *testing.T) {
|
||||
}
|
||||
|
||||
sumList := TailRec(func(state SumState) IOEither[error, E.Either[SumState, int]] {
|
||||
if len(state.items) == 0 {
|
||||
if A.IsEmpty(state.items) {
|
||||
return Of[error](E.Right[SumState](state.sum))
|
||||
}
|
||||
return Of[error](E.Left[int](SumState{
|
||||
@@ -231,7 +232,7 @@ func TestTailRecFindInList(t *testing.T) {
|
||||
}
|
||||
|
||||
findInList := TailRec(func(state FindState) IOEither[error, E.Either[FindState, int]] {
|
||||
if len(state.items) == 0 {
|
||||
if A.IsEmpty(state.items) {
|
||||
return Left[E.Either[FindState, int]](errors.New("not found"))
|
||||
}
|
||||
if state.items[0] == state.target {
|
||||
@@ -283,5 +284,3 @@ func TestTailRecFindInList(t *testing.T) {
|
||||
assert.True(t, E.IsLeft(result))
|
||||
})
|
||||
}
|
||||
|
||||
// Made with Bob
|
||||
|
||||
@@ -192,7 +192,7 @@ func FromEither[E, A any](e Either[E, A]) IOOption[A] {
|
||||
}
|
||||
|
||||
// MonadAlt identifies an associative operation on a type constructor
|
||||
func MonadAlt[A any](first IOOption[A], second IOOption[A]) IOOption[A] {
|
||||
func MonadAlt[A any](first, second IOOption[A]) IOOption[A] {
|
||||
return optiont.MonadAlt(
|
||||
io.MonadOf[Option[A]],
|
||||
io.MonadChain[Option[A], Option[A]],
|
||||
|
||||
@@ -332,5 +332,3 @@ func TestTailRecCollatzConjecture(t *testing.T) {
|
||||
assert.Equal(t, O.None[int](), result)
|
||||
})
|
||||
}
|
||||
|
||||
// Made with Bob
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestMkdir(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
newDir := filepath.Join(tmpDir, "testdir")
|
||||
|
||||
result := Mkdir(newDir, 0755)()
|
||||
result := Mkdir(newDir, 0o755)()
|
||||
path, err := E.UnwrapError(result)
|
||||
|
||||
assert.NoError(t, err)
|
||||
@@ -43,14 +43,14 @@ func TestMkdir(t *testing.T) {
|
||||
t.Run("mkdir with existing directory", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
result := Mkdir(tmpDir, 0755)()
|
||||
result := Mkdir(tmpDir, 0o755)()
|
||||
_, err := E.UnwrapError(result)
|
||||
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("mkdir with parent directory not existing", func(t *testing.T) {
|
||||
result := Mkdir("/non/existent/parent/child", 0755)()
|
||||
result := Mkdir("/non/existent/parent/child", 0o755)()
|
||||
_, err := E.UnwrapError(result)
|
||||
|
||||
assert.Error(t, err)
|
||||
@@ -62,7 +62,7 @@ func TestMkdirAll(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
nestedDir := filepath.Join(tmpDir, "level1", "level2", "level3")
|
||||
|
||||
result := MkdirAll(nestedDir, 0755)()
|
||||
result := MkdirAll(nestedDir, 0o755)()
|
||||
path, err := E.UnwrapError(result)
|
||||
|
||||
assert.NoError(t, err)
|
||||
@@ -86,7 +86,7 @@ func TestMkdirAll(t *testing.T) {
|
||||
t.Run("mkdirall with existing directory", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
result := MkdirAll(tmpDir, 0755)()
|
||||
result := MkdirAll(tmpDir, 0o755)()
|
||||
path, err := E.UnwrapError(result)
|
||||
|
||||
assert.NoError(t, err)
|
||||
@@ -97,7 +97,7 @@ func TestMkdirAll(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
newDir := filepath.Join(tmpDir, "single")
|
||||
|
||||
result := MkdirAll(newDir, 0755)()
|
||||
result := MkdirAll(newDir, 0o755)()
|
||||
path, err := E.UnwrapError(result)
|
||||
|
||||
assert.NoError(t, err)
|
||||
@@ -112,10 +112,10 @@ func TestMkdirAll(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
filePath := filepath.Join(tmpDir, "file.txt")
|
||||
|
||||
err := os.WriteFile(filePath, []byte("content"), 0644)
|
||||
err := os.WriteFile(filePath, []byte("content"), 0o644)
|
||||
assert.NoError(t, err)
|
||||
|
||||
result := MkdirAll(filepath.Join(filePath, "subdir"), 0755)()
|
||||
result := MkdirAll(filepath.Join(filePath, "subdir"), 0o755)()
|
||||
_, err = E.UnwrapError(result)
|
||||
|
||||
assert.Error(t, err)
|
||||
|
||||
@@ -33,7 +33,7 @@ func TestOpen(t *testing.T) {
|
||||
tmpFile.Close()
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
err = os.WriteFile(tmpPath, []byte("test content"), 0644)
|
||||
err = os.WriteFile(tmpPath, []byte("test content"), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
result := Open(tmpPath)()
|
||||
@@ -124,7 +124,7 @@ func TestWriteFile(t *testing.T) {
|
||||
testPath := filepath.Join(tmpDir, "write-test.txt")
|
||||
testData := []byte("test data")
|
||||
|
||||
result := WriteFile(testPath, 0644)(testData)()
|
||||
result := WriteFile(testPath, 0o644)(testData)()
|
||||
returnedData, err := E.UnwrapError(result)
|
||||
|
||||
assert.NoError(t, err)
|
||||
@@ -137,7 +137,7 @@ func TestWriteFile(t *testing.T) {
|
||||
|
||||
t.Run("write to invalid path", func(t *testing.T) {
|
||||
testData := []byte("test data")
|
||||
result := WriteFile("/non/existent/dir/file.txt", 0644)(testData)()
|
||||
result := WriteFile("/non/existent/dir/file.txt", 0o644)(testData)()
|
||||
_, err := E.UnwrapError(result)
|
||||
|
||||
assert.Error(t, err)
|
||||
@@ -150,11 +150,11 @@ func TestWriteFile(t *testing.T) {
|
||||
tmpFile.Close()
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
err = os.WriteFile(tmpPath, []byte("initial"), 0644)
|
||||
err = os.WriteFile(tmpPath, []byte("initial"), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
newData := []byte("overwritten")
|
||||
result := WriteFile(tmpPath, 0644)(newData)()
|
||||
result := WriteFile(tmpPath, 0o644)(newData)()
|
||||
returnedData, err := E.UnwrapError(result)
|
||||
|
||||
assert.NoError(t, err)
|
||||
@@ -203,7 +203,7 @@ func TestClose(t *testing.T) {
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, writeErr := tmpFile.Write([]byte("test"))
|
||||
_, writeErr := tmpFile.WriteString("test")
|
||||
assert.Error(t, writeErr)
|
||||
})
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ func TestReadAll(t *testing.T) {
|
||||
largeContent[i] = byte('A' + (i % 26))
|
||||
}
|
||||
|
||||
err := os.WriteFile(testPath, largeContent, 0644)
|
||||
err := os.WriteFile(testPath, largeContent, 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
result := ReadAll(Open(testPath))()
|
||||
|
||||
@@ -101,7 +101,7 @@ func TestWithTempFile(t *testing.T) {
|
||||
useFile := func(f *os.File) IOResult[string] {
|
||||
return ioeither.TryCatchError(func() (string, error) {
|
||||
tmpPath = f.Name()
|
||||
_, err := f.Write([]byte("test"))
|
||||
_, err := f.WriteString("test")
|
||||
return tmpPath, err
|
||||
})
|
||||
}
|
||||
@@ -199,7 +199,7 @@ func TestWithTempFile(t *testing.T) {
|
||||
useFile := func(f *os.File) IOResult[string] {
|
||||
capturedFile = f
|
||||
return ioeither.TryCatchError(func() (string, error) {
|
||||
_, err := f.Write([]byte("test"))
|
||||
_, err := f.WriteString("test")
|
||||
return f.Name(), err
|
||||
})
|
||||
}
|
||||
@@ -210,7 +210,7 @@ func TestWithTempFile(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
// File should be closed
|
||||
_, writeErr := capturedFile.Write([]byte("more"))
|
||||
_, writeErr := capturedFile.WriteString("more")
|
||||
assert.Error(t, writeErr)
|
||||
})
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ func TestWriteAll(t *testing.T) {
|
||||
_, err := E.UnwrapError(result)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, writeErr := capturedFile.Write([]byte("more"))
|
||||
_, writeErr := capturedFile.WriteString("more")
|
||||
assert.Error(t, writeErr)
|
||||
})
|
||||
|
||||
@@ -150,7 +150,7 @@ func TestWrite(t *testing.T) {
|
||||
|
||||
useFile := func(f *os.File) IOResult[string] {
|
||||
return ioeither.TryCatchError(func() (string, error) {
|
||||
_, err := f.Write([]byte("data"))
|
||||
_, err := f.WriteString("data")
|
||||
return "success", err
|
||||
})
|
||||
}
|
||||
@@ -160,7 +160,7 @@ func TestWrite(t *testing.T) {
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, writeErr := capturedFile.Write([]byte("more"))
|
||||
_, writeErr := capturedFile.WriteString("more")
|
||||
assert.Error(t, writeErr)
|
||||
})
|
||||
|
||||
@@ -187,7 +187,7 @@ func TestWrite(t *testing.T) {
|
||||
|
||||
assert.Error(t, err)
|
||||
|
||||
_, writeErr := capturedFile.Write([]byte("more"))
|
||||
_, writeErr := capturedFile.WriteString("more")
|
||||
assert.Error(t, writeErr)
|
||||
})
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ func single() int64 {
|
||||
if n%10 == 4 {
|
||||
return false
|
||||
}
|
||||
n = n / 10
|
||||
n /= 10
|
||||
}
|
||||
return true
|
||||
}),
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
)
|
||||
|
||||
// TestLoggingCallbacks_NoLoggers tests the case when no loggers are provided.
|
||||
@@ -190,7 +192,7 @@ func TestLoggingCallbacks_EmptyMessage(t *testing.T) {
|
||||
infoLog("")
|
||||
output := buf.String()
|
||||
// Should still produce output (newline at minimum)
|
||||
if len(output) == 0 {
|
||||
if S.IsEmpty(output) {
|
||||
t.Error("Expected some output even with empty message")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ type magma[A any] struct {
|
||||
c func(A, A) A
|
||||
}
|
||||
|
||||
func (m magma[A]) Concat(x A, y A) A {
|
||||
func (m magma[A]) Concat(x, y A) A {
|
||||
return m.c(x, y)
|
||||
}
|
||||
|
||||
@@ -37,14 +37,14 @@ func Reverse[A any](m Magma[A]) Magma[A] {
|
||||
})
|
||||
}
|
||||
|
||||
func filterFirst[A any](p func(A) bool, c func(A, A) A, x A, y A) A {
|
||||
func filterFirst[A any](p func(A) bool, c func(A, A) A, x, y A) A {
|
||||
if p(x) {
|
||||
return c(x, y)
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
func filterSecond[A any](p func(A) bool, c func(A, A) A, x A, y A) A {
|
||||
func filterSecond[A any](p func(A) bool, c func(A, A) A, x, y A) A {
|
||||
if p(y) {
|
||||
return c(x, y)
|
||||
}
|
||||
|
||||
@@ -127,6 +127,8 @@ Work with APIs that use zero values to indicate absence:
|
||||
|
||||
Simplify required vs optional field validation:
|
||||
|
||||
import S "github.com/IBM/fp-go/v2/string"
|
||||
|
||||
type FormData struct {
|
||||
Name string // Required
|
||||
Email string // Required
|
||||
@@ -149,7 +151,7 @@ Simplify required vs optional field validation:
|
||||
comments := commentsIso.Get(form.Comments) // None[string]
|
||||
|
||||
// Validate: required fields must be non-empty
|
||||
if form.Name == "" || form.Email == "" {
|
||||
if S.IsEmpty(form.Name) || S.IsEmpty(form.Email) {
|
||||
// Validation error
|
||||
}
|
||||
|
||||
|
||||
@@ -66,8 +66,8 @@ func TestIso(t *testing.T) {
|
||||
// iso
|
||||
intIso := FromNillable[int]()
|
||||
|
||||
innerFromOuter := L.MakeLens((Outer).GetInner, (Outer).SetInner)
|
||||
valueFromInner := L.MakeLens((Inner).GetValue, (Inner).SetValue)
|
||||
innerFromOuter := L.MakeLens(Outer.GetInner, Outer.SetInner)
|
||||
valueFromInner := L.MakeLens(Inner.GetValue, Inner.SetValue)
|
||||
|
||||
optValueFromInner := F.Pipe1(
|
||||
valueFromInner,
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
L "github.com/IBM/fp-go/v2/optics/lens"
|
||||
LT "github.com/IBM/fp-go/v2/optics/lens/testing"
|
||||
O "github.com/IBM/fp-go/v2/option"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -306,7 +307,7 @@ func TestFromIsoWithStringEmpty(t *testing.T) {
|
||||
// Isomorphism that treats empty string as None
|
||||
emptyAsNone := ISO.MakeIso(
|
||||
func(s string) O.Option[string] {
|
||||
if s == "" {
|
||||
if S.IsEmpty(s) {
|
||||
return O.None[string]()
|
||||
}
|
||||
return O.Some(s)
|
||||
|
||||
@@ -181,6 +181,7 @@ func prismModify[S, A any](f Endomorphism[A], sa Prism[S, A], s S) S {
|
||||
}
|
||||
|
||||
// prismSet is an internal helper that creates a setter function.
|
||||
//
|
||||
// Deprecated: Use Set instead.
|
||||
func prismSet[S, A any](a A) func(Prism[S, A]) Endomorphism[S] {
|
||||
return F.Curry3(prismModify[S, A])(F.Constant1[A](a))
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/IBM/fp-go/v2/either"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/option"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
)
|
||||
|
||||
// FromEncoding creates a prism for base64 encoding/decoding operations.
|
||||
@@ -697,7 +698,7 @@ func RegexNamedMatcher(re *regexp.Regexp) Prism[string, NamedMatch] {
|
||||
|
||||
groups := make(map[string]string)
|
||||
for i := 1; i < len(loc)/2; i++ {
|
||||
if names[i] != "" && loc[2*i] >= 0 {
|
||||
if S.IsNonEmpty(names[i]) && loc[2*i] >= 0 {
|
||||
groups[names[i]] = s[loc[2*i]:loc[2*i+1]]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"testing"
|
||||
|
||||
N "github.com/IBM/fp-go/v2/number"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
)
|
||||
|
||||
// Benchmark shallow chain (1 step)
|
||||
@@ -147,7 +148,7 @@ func BenchmarkChain_RealWorld_Validation(b *testing.B) {
|
||||
input,
|
||||
// Step 1: Validate not empty
|
||||
func(s string) Option[string] {
|
||||
if len(s) > 0 {
|
||||
if S.IsNonEmpty(s) {
|
||||
return Some(s)
|
||||
}
|
||||
return None[string]()
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"testing"
|
||||
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -223,7 +224,7 @@ func TestTraverseIter_ComplexTransformation(t *testing.T) {
|
||||
}
|
||||
|
||||
validatePerson := func(name string) Option[Person] {
|
||||
if name == "" {
|
||||
if S.IsEmpty(name) {
|
||||
return None[Person]()
|
||||
}
|
||||
return Some(Person{Name: name, Age: len(name)})
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
L "github.com/IBM/fp-go/v2/logging"
|
||||
)
|
||||
|
||||
func _log[A any](left func(string, ...any), right func(string, ...any), prefix string) Kleisli[Option[A], A] {
|
||||
func _log[A any](left, right func(string, ...any), prefix string) Kleisli[Option[A], A] {
|
||||
return Fold(
|
||||
func() Option[A] {
|
||||
left("%s", prefix)
|
||||
|
||||
@@ -16,12 +16,52 @@
|
||||
package ord
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
|
||||
C "github.com/IBM/fp-go/v2/constraints"
|
||||
E "github.com/IBM/fp-go/v2/eq"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
P "github.com/IBM/fp-go/v2/predicate"
|
||||
)
|
||||
|
||||
// Ord represents a total ordering type class for type T.
|
||||
// It extends Eq with a comparison function that establishes a total order.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - T: The type for which ordering is defined
|
||||
//
|
||||
// Methods:
|
||||
// - Equals(x, y T) bool: Inherited from Eq, returns true if x and y are equal
|
||||
// - Compare(x, y T) int: Returns -1 if x < y, 0 if x == y, 1 if x > y
|
||||
//
|
||||
// Laws:
|
||||
// An Ord instance must satisfy the total order laws:
|
||||
// 1. Reflexivity: Compare(x, x) = 0 for all x
|
||||
// 2. Antisymmetry: if Compare(x, y) <= 0 and Compare(y, x) <= 0 then x = y
|
||||
// 3. Transitivity: if Compare(x, y) <= 0 and Compare(y, z) <= 0 then Compare(x, z) <= 0
|
||||
// 4. Totality: Compare(x, y) <= 0 or Compare(y, x) <= 0 for all x, y
|
||||
//
|
||||
// The Compare function must be consistent with Equals:
|
||||
// - Compare(x, y) = 0 if and only if Equals(x, y) = true
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // Using built-in ordering for integers
|
||||
// intOrd := ord.FromStrictCompare[int]()
|
||||
// result := intOrd.Compare(5, 3) // 1 (5 > 3)
|
||||
// result := intOrd.Compare(3, 5) // -1 (3 < 5)
|
||||
// result := intOrd.Compare(5, 5) // 0 (5 == 5)
|
||||
//
|
||||
// // Creating custom ordering
|
||||
// type Person struct { Name string; Age int }
|
||||
// personOrd := ord.MakeOrd(
|
||||
// func(p1, p2 Person) int {
|
||||
// if p1.Age < p2.Age { return -1 }
|
||||
// if p1.Age > p2.Age { return 1 }
|
||||
// return 0
|
||||
// },
|
||||
// func(p1, p2 Person) bool { return p1.Age == p2.Age },
|
||||
// )
|
||||
type Ord[T any] interface {
|
||||
E.Eq[T]
|
||||
Compare(x, y T) int
|
||||
@@ -41,11 +81,15 @@ func (self ord[T]) Compare(x, y T) int {
|
||||
}
|
||||
|
||||
// ToEq converts an [Ord] to [E.Eq]
|
||||
|
||||
//go:inline
|
||||
func ToEq[T any](o Ord[T]) E.Eq[T] {
|
||||
return o
|
||||
}
|
||||
|
||||
// MakeOrd creates an instance of an Ord
|
||||
//
|
||||
//go:inline
|
||||
func MakeOrd[T any](c func(x, y T) int, e func(x, y T) bool) Ord[T] {
|
||||
return ord[T]{c: c, e: e}
|
||||
}
|
||||
@@ -112,21 +156,19 @@ func Clamp[A any](o Ord[A]) func(A, A) func(A) A {
|
||||
}
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func strictCompare[A C.Ordered](a, b A) int {
|
||||
if a < b {
|
||||
return -1
|
||||
} else if a > b {
|
||||
return +1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
return cmp.Compare(a, b)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func strictEq[A comparable](a, b A) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
// FromStrictCompare implements the ordering based on the built in native order
|
||||
//
|
||||
//go:inline
|
||||
func FromStrictCompare[A C.Ordered]() Ord[A] {
|
||||
return MakeOrd(strictCompare[A], strictEq[A])
|
||||
}
|
||||
@@ -141,10 +183,10 @@ func Lt[A any](o Ord[A]) func(A) func(A) bool {
|
||||
}
|
||||
|
||||
// Leq Tests whether one value is less or equal than another
|
||||
func Leq[A any](O Ord[A]) func(A) func(A) bool {
|
||||
func Leq[A any](o Ord[A]) func(A) func(A) bool {
|
||||
return func(second A) func(A) bool {
|
||||
return func(first A) bool {
|
||||
return O.Compare(first, second) <= 0
|
||||
return o.Compare(first, second) <= 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,27 +194,27 @@ func Leq[A any](O Ord[A]) func(A) func(A) bool {
|
||||
/**
|
||||
* Test whether one value is strictly greater than another
|
||||
*/
|
||||
func Gt[A any](O Ord[A]) func(A) func(A) bool {
|
||||
func Gt[A any](o Ord[A]) func(A) func(A) bool {
|
||||
return func(second A) func(A) bool {
|
||||
return func(first A) bool {
|
||||
return O.Compare(first, second) > 0
|
||||
return o.Compare(first, second) > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Geq tests whether one value is greater or equal than another
|
||||
func Geq[A any](O Ord[A]) func(A) func(A) bool {
|
||||
func Geq[A any](o Ord[A]) func(A) func(A) bool {
|
||||
return func(second A) func(A) bool {
|
||||
return func(first A) bool {
|
||||
return O.Compare(first, second) >= 0
|
||||
return o.Compare(first, second) >= 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Between tests whether a value is between a minimum (inclusive) and a maximum (exclusive)
|
||||
func Between[A any](O Ord[A]) func(A, A) func(A) bool {
|
||||
lt := Lt(O)
|
||||
geq := Geq(O)
|
||||
func Between[A any](o Ord[A]) func(A, A) func(A) bool {
|
||||
lt := Lt(o)
|
||||
geq := Geq(o)
|
||||
return func(lo, hi A) func(A) bool {
|
||||
// returns the predicate
|
||||
return P.And(lt(hi))(geq(lo))
|
||||
|
||||
@@ -44,6 +44,8 @@ import (
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import S "github.com/IBM/fp-go/v2/string"
|
||||
//
|
||||
// type Database struct {
|
||||
// ConnectionString string
|
||||
// }
|
||||
@@ -57,7 +59,7 @@ import (
|
||||
// return either.Left[ReaderEither[Database, error, string]](errors.New("invalid timeout"))
|
||||
// }
|
||||
// return either.Right[error](func(db Database) either.Either[error, string] {
|
||||
// if db.ConnectionString == "" {
|
||||
// if S.IsEmpty(db.ConnectionString) {
|
||||
// return either.Left[string](errors.New("empty connection string"))
|
||||
// }
|
||||
// return either.Right[error](fmt.Sprintf("Query on %s with timeout %d",
|
||||
@@ -186,7 +188,7 @@ func SequenceReader[R1, R2, E, A any](ma ReaderEither[R2, E, Reader[R1, A]]) Kle
|
||||
// // Kleisli function: transforms int to ReaderEither[Database, error, string]
|
||||
// kleisli := func(value int) ReaderEither[Database, error, string] {
|
||||
// return func(db Database) either.Either[error, string] {
|
||||
// if db.ConnectionString == "" {
|
||||
// if S.IsEmpty(db.ConnectionString) {
|
||||
// return either.Left[string](errors.New("empty connection string"))
|
||||
// }
|
||||
// return either.Right[error](fmt.Sprintf("%s:%d", db.ConnectionString, value))
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/IBM/fp-go/v2/either"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -67,7 +68,7 @@ func TestSequence(t *testing.T) {
|
||||
return either.Left[ReaderEither[Database, error, string]](errors.New("invalid timeout"))
|
||||
}
|
||||
return either.Right[error](func(db Database) either.Either[error, string] {
|
||||
if db.ConnectionString == "" {
|
||||
if S.IsEmpty(db.ConnectionString) {
|
||||
return either.Left[string](errors.New("empty connection string"))
|
||||
}
|
||||
return either.Right[error](fmt.Sprintf("Query on %s with timeout %d",
|
||||
@@ -135,7 +136,7 @@ func TestSequence(t *testing.T) {
|
||||
// Original that fails at inner level
|
||||
original := func(x int) either.Either[error, ReaderEither[string, error, int]] {
|
||||
return either.Right[error](func(s string) either.Either[error, int] {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return either.Left[int](expectedError)
|
||||
}
|
||||
return either.Right[error](x + len(s))
|
||||
@@ -223,7 +224,7 @@ func TestSequence(t *testing.T) {
|
||||
return either.Left[ReaderEither[Session, error, string]](errors.New("invalid user ID"))
|
||||
}
|
||||
return either.Right[error](func(session Session) either.Either[error, string] {
|
||||
if session.Token == "" {
|
||||
if S.IsEmpty(session.Token) {
|
||||
return either.Left[string](errors.New("empty token"))
|
||||
}
|
||||
return either.Right[error](fmt.Sprintf("User %s (ID: %d) authenticated with token %s",
|
||||
@@ -332,7 +333,7 @@ func TestSequenceEdgeCases(t *testing.T) {
|
||||
return either.Left[ReaderEither[string, error, int]](errors.New("outer: negative value"))
|
||||
}
|
||||
return either.Right[error](func(s string) either.Either[error, int] {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return either.Left[int](errors.New("inner: empty string"))
|
||||
}
|
||||
return either.Right[error](x + len(s))
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
A "github.com/IBM/fp-go/v2/array"
|
||||
E "github.com/IBM/fp-go/v2/either"
|
||||
G "github.com/IBM/fp-go/v2/io"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -156,7 +157,7 @@ func TestTailRecSumList(t *testing.T) {
|
||||
sumStep := func(state State) ReaderIO[TestEnv, E.Either[State, int]] {
|
||||
return func(env TestEnv) G.IO[E.Either[State, int]] {
|
||||
return func() E.Either[State, int] {
|
||||
if len(state.list) == 0 {
|
||||
if A.IsEmpty(state.list) {
|
||||
return E.Right[State](state.sum * env.Multiplier)
|
||||
}
|
||||
return E.Left[int](State{state.list[1:], state.sum + state.list[0]})
|
||||
@@ -453,5 +454,3 @@ func TestTailRecDifferentEnvironments(t *testing.T) {
|
||||
assert.Equal(t, 0, result1)
|
||||
assert.Equal(t, 0, result2)
|
||||
}
|
||||
|
||||
// Made with Bob
|
||||
|
||||
@@ -24,12 +24,13 @@ import (
|
||||
"github.com/IBM/fp-go/v2/either"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
TST "github.com/IBM/fp-go/v2/internal/testing"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTraverseArrayEx(t *testing.T) {
|
||||
f := TraverseArray(func(a string) ReaderIOEither[context.Context, string, string] {
|
||||
if len(a) > 0 {
|
||||
if S.IsNonEmpty(a) {
|
||||
return Right[context.Context, string](a + a)
|
||||
}
|
||||
return Left[context.Context, string]("e")
|
||||
|
||||
@@ -39,10 +39,10 @@ import (
|
||||
// - ma: A ReaderIOEither that takes R2 and may produce a ReaderIOEither[R1, E, A]
|
||||
//
|
||||
// Returns:
|
||||
// - A reader.Kleisli[R2, R1, IOEither[E, A]], which is func(R2) func(R1) IOEither[E, A]
|
||||
// - A Kleisli[R2, E, R1, A], which is func(R2) func(R1) IOEither[E, A]
|
||||
//
|
||||
// The function preserves error handling and IO effects at both levels.
|
||||
func Sequence[R1, R2, E, A any](ma ReaderIOEither[R2, E, ReaderIOEither[R1, E, A]]) reader.Kleisli[R2, R1, IOEither[E, A]] {
|
||||
func Sequence[R1, R2, E, A any](ma ReaderIOEither[R2, E, ReaderIOEither[R1, E, A]]) Kleisli[R2, E, R1, A] {
|
||||
return readert.Sequence(
|
||||
ioeither.Chain,
|
||||
ma,
|
||||
@@ -64,8 +64,8 @@ func Sequence[R1, R2, E, A any](ma ReaderIOEither[R2, E, ReaderIOEither[R1, E, A
|
||||
// - ma: A ReaderIOEither that takes R2 and may produce a Reader[R1, A]
|
||||
//
|
||||
// Returns:
|
||||
// - A reader.Kleisli[R2, R1, IOEither[E, A]], which is func(R2) func(R1) IOEither[E, A]
|
||||
func SequenceReader[R1, R2, E, A any](ma ReaderIOEither[R2, E, Reader[R1, A]]) reader.Kleisli[R2, R1, IOEither[E, A]] {
|
||||
// - A Kleisli[R2, E, R1, A], which is func(R2) func(R1) IOEither[E, A]
|
||||
func SequenceReader[R1, R2, E, A any](ma ReaderIOEither[R2, E, Reader[R1, A]]) Kleisli[R2, E, R1, A] {
|
||||
return readert.SequenceReader(
|
||||
ioeither.Map,
|
||||
ma,
|
||||
@@ -87,8 +87,8 @@ func SequenceReader[R1, R2, E, A any](ma ReaderIOEither[R2, E, Reader[R1, A]]) r
|
||||
// - ma: A ReaderIOEither that takes R2 and may produce a ReaderIO[R1, A]
|
||||
//
|
||||
// Returns:
|
||||
// - A reader.Kleisli[R2, R1, IOEither[E, A]], which is func(R2) func(R1) IOEither[E, A]
|
||||
func SequenceReaderIO[R1, R2, E, A any](ma ReaderIOEither[R2, E, ReaderIO[R1, A]]) reader.Kleisli[R2, R1, IOEither[E, A]] {
|
||||
// - A Kleisli[R2, E, R1, A], which is func(R2) func(R1) IOEither[E, A]
|
||||
func SequenceReaderIO[R1, R2, E, A any](ma ReaderIOEither[R2, E, ReaderIO[R1, A]]) Kleisli[R2, E, R1, A] {
|
||||
return func(r1 R1) ReaderIOEither[R2, E, A] {
|
||||
return func(r2 R2) IOEither[E, A] {
|
||||
return func() Either[E, A] {
|
||||
@@ -118,8 +118,8 @@ func SequenceReaderIO[R1, R2, E, A any](ma ReaderIOEither[R2, E, ReaderIO[R1, A]
|
||||
// - ma: A ReaderIOEither that takes R2 and may produce a ReaderEither[R1, E, A]
|
||||
//
|
||||
// Returns:
|
||||
// - A reader.Kleisli[R2, R1, IOEither[E, A]], which is func(R2) func(R1) IOEither[E, A]
|
||||
func SequenceReaderEither[R1, R2, E, A any](ma ReaderIOEither[R2, E, ReaderEither[R1, E, A]]) reader.Kleisli[R2, R1, IOEither[E, A]] {
|
||||
// - A Kleisli[R2, E, R1, A], which is func(R2) func(R1) IOEither[E, A]
|
||||
func SequenceReaderEither[R1, R2, E, A any](ma ReaderIOEither[R2, E, ReaderEither[R1, E, A]]) Kleisli[R2, E, R1, A] {
|
||||
return func(r1 R1) ReaderIOEither[R2, E, A] {
|
||||
return func(r2 R2) IOEither[E, A] {
|
||||
return func() Either[E, A] {
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
N "github.com/IBM/fp-go/v2/number"
|
||||
"github.com/IBM/fp-go/v2/reader"
|
||||
"github.com/IBM/fp-go/v2/readerio"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -85,7 +86,7 @@ func TestSequence(t *testing.T) {
|
||||
|
||||
original := func(x int) IOEither[error, ReaderIOEither[string, error, int]] {
|
||||
return ioeither.Right[error](func(s string) IOEither[error, int] {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return ioeither.Left[int](expectedError)
|
||||
}
|
||||
return ioeither.Right[error](x + len(s))
|
||||
@@ -284,7 +285,7 @@ func TestSequenceReaderEither(t *testing.T) {
|
||||
|
||||
original := func(x int) IOEither[error, ReaderEither[string, error, int]] {
|
||||
return ioeither.Right[error](func(s string) either.Either[error, int] {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return either.Left[int](expectedError)
|
||||
}
|
||||
return either.Right[error](x + len(s))
|
||||
@@ -316,7 +317,7 @@ func TestSequenceEdgeCases(t *testing.T) {
|
||||
return ioeither.Left[ReaderIOEither[Database, error, string]](errors.New("invalid timeout"))
|
||||
}
|
||||
return ioeither.Right[error](func(db Database) IOEither[error, string] {
|
||||
if db.ConnectionString == "" {
|
||||
if S.IsEmpty(db.ConnectionString) {
|
||||
return ioeither.Left[string](errors.New("empty connection string"))
|
||||
}
|
||||
return ioeither.Right[error](fmt.Sprintf("Query on %s with timeout %d",
|
||||
@@ -414,7 +415,7 @@ func TestTraverse(t *testing.T) {
|
||||
|
||||
kleisli := func(a int) ReaderIOEither[string, error, int] {
|
||||
return func(s string) IOEither[error, int] {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return ioeither.Left[int](expectedError)
|
||||
}
|
||||
return ioeither.Right[error](a + len(s))
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
A "github.com/IBM/fp-go/v2/array"
|
||||
E "github.com/IBM/fp-go/v2/either"
|
||||
IOE "github.com/IBM/fp-go/v2/ioeither"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -217,7 +218,7 @@ func TestTailRecSumList(t *testing.T) {
|
||||
if state.sum > env.MaxValue {
|
||||
return E.Left[E.Either[State, int]](fmt.Sprintf("sum exceeds max: %d > %d", state.sum, env.MaxValue))
|
||||
}
|
||||
if len(state.list) == 0 {
|
||||
if A.IsEmpty(state.list) {
|
||||
return E.Right[string](E.Right[State](state.sum * env.Multiplier))
|
||||
}
|
||||
return E.Right[string](E.Left[int](State{state.list[1:], state.sum + state.list[0]}))
|
||||
@@ -246,7 +247,7 @@ func TestTailRecSumListError(t *testing.T) {
|
||||
if state.sum > env.MaxValue {
|
||||
return E.Left[E.Either[State, int]](fmt.Sprintf("sum exceeds max: %d > %d", state.sum, env.MaxValue))
|
||||
}
|
||||
if len(state.list) == 0 {
|
||||
if A.IsEmpty(state.list) {
|
||||
return E.Right[string](E.Right[State](state.sum))
|
||||
}
|
||||
return E.Right[string](E.Left[int](State{state.list[1:], state.sum + state.list[0]}))
|
||||
@@ -610,5 +611,3 @@ func TestTailRecDifferentEnvironments(t *testing.T) {
|
||||
assert.Equal(t, E.Of[string](0), result1) // 0 * 2 = 0
|
||||
assert.Equal(t, E.Of[string](0), result2) // 0 * 5 = 0
|
||||
}
|
||||
|
||||
// Made with Bob
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
E "github.com/IBM/fp-go/v2/either"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
RIE "github.com/IBM/fp-go/v2/readerioeither"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@@ -37,7 +38,7 @@ type User struct {
|
||||
func fetchUser(id int) RIE.ReaderIOEither[Config, error, User] {
|
||||
return func(cfg Config) func() E.Either[error, User] {
|
||||
return func() E.Either[error, User] {
|
||||
if cfg.APIKey == "" {
|
||||
if S.IsEmpty(cfg.APIKey) {
|
||||
return E.Left[User](fmt.Errorf("missing API key"))
|
||||
}
|
||||
if id <= 0 {
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
TST "github.com/IBM/fp-go/v2/internal/testing"
|
||||
M "github.com/IBM/fp-go/v2/monoid"
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -34,7 +35,7 @@ func TestTraverseArray(t *testing.T) {
|
||||
e := errors.New("e")
|
||||
|
||||
f := TraverseArray(func(a string) ReaderIOResult[context.Context, string] {
|
||||
if len(a) > 0 {
|
||||
if S.IsNonEmpty(a) {
|
||||
return Right[context.Context](a + a)
|
||||
}
|
||||
return Left[context.Context, string](e)
|
||||
|
||||
@@ -6,27 +6,27 @@ import (
|
||||
)
|
||||
|
||||
//go:inline
|
||||
func Sequence[R1, R2, A any](ma ReaderIOResult[R2, ReaderIOResult[R1, A]]) reader.Kleisli[R2, R1, IOResult[A]] {
|
||||
func Sequence[R1, R2, A any](ma ReaderIOResult[R2, ReaderIOResult[R1, A]]) Kleisli[R2, R1, A] {
|
||||
return RIOE.Sequence(ma)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func SequenceReader[R1, R2, A any](ma ReaderIOResult[R2, Reader[R1, A]]) reader.Kleisli[R2, R1, IOResult[A]] {
|
||||
func SequenceReader[R1, R2, A any](ma ReaderIOResult[R2, Reader[R1, A]]) Kleisli[R2, R1, A] {
|
||||
return RIOE.SequenceReader(ma)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func SequenceReaderIO[R1, R2, A any](ma ReaderIOResult[R2, ReaderIO[R1, A]]) reader.Kleisli[R2, R1, IOResult[A]] {
|
||||
func SequenceReaderIO[R1, R2, A any](ma ReaderIOResult[R2, ReaderIO[R1, A]]) Kleisli[R2, R1, A] {
|
||||
return RIOE.SequenceReaderIO(ma)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func SequenceReaderEither[R1, R2, A any](ma ReaderIOResult[R2, ReaderResult[R1, A]]) reader.Kleisli[R2, R1, IOResult[A]] {
|
||||
func SequenceReaderEither[R1, R2, A any](ma ReaderIOResult[R2, ReaderResult[R1, A]]) Kleisli[R2, R1, A] {
|
||||
return RIOE.SequenceReaderEither(ma)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func SequenceReaderResult[R1, R2, A any](ma ReaderIOResult[R2, ReaderResult[R1, A]]) reader.Kleisli[R2, R1, IOResult[A]] {
|
||||
func SequenceReaderResult[R1, R2, A any](ma ReaderIOResult[R2, ReaderResult[R1, A]]) Kleisli[R2, R1, A] {
|
||||
return RIOE.SequenceReaderEither(ma)
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ import (
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import S "github.com/IBM/fp-go/v2/string"
|
||||
//
|
||||
// type Database struct {
|
||||
// ConnectionString string
|
||||
// }
|
||||
@@ -56,7 +58,7 @@ import (
|
||||
// return option.None[ReaderOption[Database, string]]()
|
||||
// }
|
||||
// return option.Some(func(db Database) option.Option[string] {
|
||||
// if db.ConnectionString == "" {
|
||||
// if S.IsEmpty(db.ConnectionString) {
|
||||
// return option.None[string]()
|
||||
// }
|
||||
// return option.Some(fmt.Sprintf("Query on %s with timeout %d",
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/IBM/fp-go/v2/option"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -66,7 +67,7 @@ func TestSequence(t *testing.T) {
|
||||
return option.None[ReaderOption[Database, string]]()
|
||||
}
|
||||
return option.Some(func(db Database) option.Option[string] {
|
||||
if db.ConnectionString == "" {
|
||||
if S.IsEmpty(db.ConnectionString) {
|
||||
return option.None[string]()
|
||||
}
|
||||
return option.Some(fmt.Sprintf("Query on %s with timeout %d",
|
||||
@@ -126,7 +127,7 @@ func TestSequence(t *testing.T) {
|
||||
// Original that returns None at inner level
|
||||
original := func(x int) option.Option[ReaderOption[string, int]] {
|
||||
return option.Some(func(s string) option.Option[int] {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return option.None[int]()
|
||||
}
|
||||
return option.Some(x + len(s))
|
||||
@@ -210,7 +211,7 @@ func TestSequence(t *testing.T) {
|
||||
return option.None[ReaderOption[Session, string]]()
|
||||
}
|
||||
return option.Some(func(session Session) option.Option[string] {
|
||||
if session.Token == "" {
|
||||
if S.IsEmpty(session.Token) {
|
||||
return option.None[string]()
|
||||
}
|
||||
return option.Some(fmt.Sprintf("User %s (ID: %d) authenticated with token %s",
|
||||
@@ -319,7 +320,7 @@ func TestSequenceEdgeCases(t *testing.T) {
|
||||
return option.None[ReaderOption[string, int]]()
|
||||
}
|
||||
return option.Some(func(s string) option.Option[int] {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return option.None[int]()
|
||||
}
|
||||
return option.Some(x + len(s))
|
||||
|
||||
@@ -364,7 +364,7 @@ func Flap[E, B, A any](a A) Operator[E, func(A) B, B] {
|
||||
// result := readeroption.MonadAlt(primary, fallback)
|
||||
//
|
||||
//go:inline
|
||||
func MonadAlt[E, A any](fa ReaderOption[E, A], that ReaderOption[E, A]) ReaderOption[E, A] {
|
||||
func MonadAlt[E, A any](fa, that ReaderOption[E, A]) ReaderOption[E, A] {
|
||||
return MonadFold(fa, that, Of[E, A])
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ import (
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// import S "github.com/IBM/fp-go/v2/string"
|
||||
//
|
||||
// type Database struct {
|
||||
// ConnectionString string
|
||||
// }
|
||||
@@ -58,7 +60,7 @@ import (
|
||||
// return result.Error[ReaderResult[Database, string]](errors.New("invalid timeout"))
|
||||
// }
|
||||
// return result.Ok[error](func(db Database) result.Result[string] {
|
||||
// if db.ConnectionString == "" {
|
||||
// if S.IsEmpty(db.ConnectionString) {
|
||||
// return result.Error[string](errors.New("empty connection string"))
|
||||
// }
|
||||
// return result.Ok[error](fmt.Sprintf("Query on %s with timeout %d",
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/IBM/fp-go/v2/result"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -64,7 +65,7 @@ func TestSequence(t *testing.T) {
|
||||
return result.Left[ReaderResult[Database, string]](errors.New("invalid timeout"))
|
||||
}
|
||||
return result.Right(func(db Database) result.Result[string] {
|
||||
if db.ConnectionString == "" {
|
||||
if S.IsEmpty(db.ConnectionString) {
|
||||
return result.Left[string](errors.New("empty connection string"))
|
||||
}
|
||||
return result.Right(fmt.Sprintf("Query on %s with timeout %d",
|
||||
@@ -128,7 +129,7 @@ func TestSequence(t *testing.T) {
|
||||
// Original that fails at inner level
|
||||
original := func(x int) result.Result[ReaderResult[string, int]] {
|
||||
return result.Right(func(s string) result.Result[int] {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return result.Left[int](expectedError)
|
||||
}
|
||||
return result.Right(x + len(s))
|
||||
@@ -215,7 +216,7 @@ func TestSequence(t *testing.T) {
|
||||
return result.Left[ReaderResult[Session, string]](errors.New("invalid user ID"))
|
||||
}
|
||||
return result.Right(func(session Session) result.Result[string] {
|
||||
if session.Token == "" {
|
||||
if S.IsEmpty(session.Token) {
|
||||
return result.Left[string](errors.New("empty token"))
|
||||
}
|
||||
return result.Right(fmt.Sprintf("User %s (ID: %d) authenticated with token %s",
|
||||
@@ -312,7 +313,7 @@ func TestSequenceEdgeCases(t *testing.T) {
|
||||
return result.Left[ReaderResult[string, int]](errors.New("outer: negative value"))
|
||||
}
|
||||
return result.Right(func(s string) result.Result[int] {
|
||||
if len(s) == 0 {
|
||||
if S.IsEmpty(s) {
|
||||
return result.Left[int](errors.New("inner: empty string"))
|
||||
}
|
||||
return result.Right(x + len(s))
|
||||
|
||||
@@ -230,7 +230,7 @@ func Has[M ~map[K]V, K comparable, V any](k K, r M) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
func union[M ~map[K]V, K comparable, V any](m Mg.Magma[V], left M, right M) M {
|
||||
func union[M ~map[K]V, K comparable, V any](m Mg.Magma[V], left, right M) M {
|
||||
lenLeft := len(left)
|
||||
|
||||
if lenLeft == 0 {
|
||||
@@ -261,7 +261,7 @@ func union[M ~map[K]V, K comparable, V any](m Mg.Magma[V], left M, right M) M {
|
||||
return result
|
||||
}
|
||||
|
||||
func unionLast[M ~map[K]V, K comparable, V any](left M, right M) M {
|
||||
func unionLast[M ~map[K]V, K comparable, V any](left, right M) M {
|
||||
lenLeft := len(left)
|
||||
|
||||
if lenLeft == 0 {
|
||||
@@ -526,7 +526,7 @@ func IsNonNil[M ~map[K]V, K comparable, V any](m M) bool {
|
||||
|
||||
// ConstNil return a nil map
|
||||
func ConstNil[M ~map[K]V, K comparable, V any]() M {
|
||||
return (M)(nil)
|
||||
return M(nil)
|
||||
}
|
||||
|
||||
func FoldMap[AS ~map[K]A, K comparable, A, B any](m Mo.Monoid[B]) func(func(A) B) func(AS) B {
|
||||
|
||||
@@ -26,9 +26,7 @@ func UnionSemigroup[N ~map[K]V, K comparable, V any](s S.Semigroup[V]) S.Semigro
|
||||
}
|
||||
|
||||
func UnionLastSemigroup[N ~map[K]V, K comparable, V any]() S.Semigroup[N] {
|
||||
return S.MakeSemigroup(func(first N, second N) N {
|
||||
return unionLast(first, second)
|
||||
})
|
||||
return S.MakeSemigroup(unionLast[N])
|
||||
}
|
||||
|
||||
func UnionFirstSemigroup[N ~map[K]V, K comparable, V any]() S.Semigroup[N] {
|
||||
|
||||
@@ -194,7 +194,7 @@ func IsNonNil[K comparable, V any](m map[K]V) bool {
|
||||
|
||||
// ConstNil return a nil map
|
||||
func ConstNil[K comparable, V any]() map[K]V {
|
||||
return (map[K]V)(nil)
|
||||
return map[K]V(nil)
|
||||
}
|
||||
|
||||
func MonadChainWithIndex[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2], r map[K]V1, f func(K, V1) map[K]V2) map[K]V2 {
|
||||
|
||||
@@ -64,7 +64,7 @@ func TraverseArray[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B] {
|
||||
// Example:
|
||||
//
|
||||
// validate := func(i int, s string) either.Result[string] {
|
||||
// if len(s) > 0 {
|
||||
// if S.IsNonEmpty(s) {
|
||||
// return either.Right[error](fmt.Sprintf("%d:%s", i, s))
|
||||
// }
|
||||
// return either.Left[string](fmt.Errorf("empty at index %d", i))
|
||||
@@ -84,7 +84,7 @@ func TraverseArrayWithIndexG[GA ~[]A, GB ~[]B, A, B any](f func(int, A) Result[B
|
||||
// Example:
|
||||
//
|
||||
// validate := func(i int, s string) either.Result[string] {
|
||||
// if len(s) > 0 {
|
||||
// if S.IsNonEmpty(s) {
|
||||
// return either.Right[error](fmt.Sprintf("%d:%s", i, s))
|
||||
// }
|
||||
// return either.Left[string](fmt.Errorf("empty at index %d", i))
|
||||
|
||||
@@ -34,7 +34,7 @@ func Curry0[R any](f func() (R, error)) func() Result[R] {
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// parse := func(s string) (int, error) { return strconv.Atoi(s) }
|
||||
// parse := strconv.Atoi
|
||||
// curried := either.Curry1(parse)
|
||||
// result := curried("42") // Right(42)
|
||||
func Curry1[T1, R any](f func(T1) (R, error)) func(T1) Result[R] {
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// package result provides the Either monad, a data structure representing a value of one of two possible types.
|
||||
// Package result provides the Result monad, a specialized Either monad with error as the left type.
|
||||
//
|
||||
// Either is commonly used for error handling, where by convention:
|
||||
// - Left represents an error or failure case (type E)
|
||||
// - Right represents a success case (type A)
|
||||
// Result is commonly used for error handling, where:
|
||||
// - Error represents a failure case (type error)
|
||||
// - Ok represents a success case (type A)
|
||||
//
|
||||
// # Fantasy Land Specification
|
||||
//
|
||||
@@ -36,45 +36,43 @@
|
||||
//
|
||||
// # Core Concepts
|
||||
//
|
||||
// The Either type is a discriminated union that can hold either a Left value (typically an error)
|
||||
// or a Right value (typically a successful result). This makes it ideal for computations that may fail.
|
||||
// The Result type is a discriminated union that can hold either an Error value
|
||||
// or an Ok value (successful result). This makes it ideal for computations that may fail.
|
||||
//
|
||||
// # Basic Usage
|
||||
//
|
||||
// // Creating Either values
|
||||
// success := either.Right[error](42) // Right value
|
||||
// failure := either.Left[int](errors.New("oops")) // Left value
|
||||
// // Creating Result values
|
||||
// success := result.Ok(42) // Ok value
|
||||
// failure := result.Error[int](errors.New("oops")) // Error value
|
||||
//
|
||||
// // Pattern matching with Fold
|
||||
// result := either.Fold(
|
||||
// output := result.Fold(
|
||||
// func(err error) string { return "Error: " + err.Error() },
|
||||
// func(n int) string { return fmt.Sprintf("Success: %d", n) },
|
||||
// )(success)
|
||||
//
|
||||
// // Chaining operations (short-circuits on Left)
|
||||
// result := either.Chain(func(n int) either.Result[int] {
|
||||
// return either.Right[error](n * 2)
|
||||
// // Chaining operations (short-circuits on Error)
|
||||
// doubled := result.Chain(func(n int) result.Result[int] {
|
||||
// return result.Ok(n * 2)
|
||||
// })(success)
|
||||
//
|
||||
// # Monadic Operations
|
||||
//
|
||||
// Either implements the Monad interface, providing:
|
||||
// - Map: Transform the Right value
|
||||
// Result implements the Monad interface, providing:
|
||||
// - Map: Transform the Ok value
|
||||
// - Chain (FlatMap): Chain computations that may fail
|
||||
// - Ap: Apply a function wrapped in Either
|
||||
// - Ap: Apply a function wrapped in Result
|
||||
//
|
||||
// # Error Handling
|
||||
//
|
||||
// Either provides utilities for working with Go's error type:
|
||||
// - TryCatchError: Convert (value, error) tuples to Either
|
||||
// - UnwrapError: Convert Either back to (value, error) tuple
|
||||
// - FromError: Create Either from error-returning functions
|
||||
// Result provides utilities for working with Go's error type:
|
||||
// - TryCatchError: Convert (value, error) tuples to Result
|
||||
// - UnwrapError: Convert Result back to (value, error) tuple
|
||||
// - FromError: Create Result from error-returning functions
|
||||
//
|
||||
// # Subpackages
|
||||
//
|
||||
// - either/exec: Execute system commands returning Either
|
||||
// - either/http: HTTP request builders returning Either
|
||||
// - either/testing: Testing utilities for Either laws
|
||||
// - result/http: HTTP request builders returning Result
|
||||
package result
|
||||
|
||||
//go:generate go run .. either --count 15 --filename gen.go
|
||||
|
||||
@@ -352,9 +352,7 @@ func TestRetryingWithOption(t *testing.T) {
|
||||
return result
|
||||
}
|
||||
|
||||
check := func(value O.Option[string]) bool {
|
||||
return O.IsNone(value) // Retry if None
|
||||
}
|
||||
check := O.IsNone[string] // Retry if None
|
||||
|
||||
result := Retrying(
|
||||
testMonadChain[O.Option[string], O.Option[string]],
|
||||
|
||||
@@ -31,7 +31,7 @@ func (f *Flock) Conjoin(other *Flock) *Flock {
|
||||
}
|
||||
|
||||
func (f *Flock) Breed(other *Flock) *Flock {
|
||||
f.Seagulls = f.Seagulls * other.Seagulls
|
||||
f.Seagulls *= other.Seagulls
|
||||
return f
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ func MakePerson(name string, age int) Person {
|
||||
return Person{name, age}
|
||||
}
|
||||
|
||||
func MakeClient(city string, name string, age int) Client {
|
||||
func MakeClient(city, name string, age int) Client {
|
||||
return Client{person: Person{name, age}, address: Address{city}}
|
||||
}
|
||||
|
||||
|
||||
@@ -167,6 +167,8 @@ Example 1: Merging Configurations
|
||||
|
||||
Example 2: Combining Validators
|
||||
|
||||
import S "github.com/IBM/fp-go/v2/string"
|
||||
|
||||
type Validator func(string) []string // Returns list of errors
|
||||
|
||||
validatorSG := SG.MakeSemigroup(func(v1, v2 Validator) Validator {
|
||||
@@ -178,7 +180,7 @@ Example 2: Combining Validators
|
||||
})
|
||||
|
||||
notEmpty := func(s string) []string {
|
||||
if s == "" {
|
||||
if S.IsEmpty(s) {
|
||||
return []string{"must not be empty"}
|
||||
}
|
||||
return nil
|
||||
@@ -223,11 +225,13 @@ Example 3: Aggregating Statistics
|
||||
|
||||
Example 4: Building Query Strings
|
||||
|
||||
import S "github.com/IBM/fp-go/v2/string"
|
||||
|
||||
querySG := SG.MakeSemigroup(func(a, b string) string {
|
||||
if a == "" {
|
||||
if S.IsEmpty(a) {
|
||||
return b
|
||||
}
|
||||
if b == "" {
|
||||
if S.IsEmpty(b) {
|
||||
return a
|
||||
}
|
||||
return a + "&" + b
|
||||
|
||||
@@ -37,7 +37,7 @@ type semigroup[A any] struct {
|
||||
c func(A, A) A
|
||||
}
|
||||
|
||||
func (self semigroup[A]) Concat(x A, y A) A {
|
||||
func (self semigroup[A]) Concat(x, y A) A {
|
||||
return self.c(x, y)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
S "github.com/IBM/fp-go/v2/semigroup"
|
||||
)
|
||||
|
||||
func concat(left string, right string) string {
|
||||
func concat(left, right string) string {
|
||||
return fmt.Sprintf("%s%s", left, right)
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ var (
|
||||
HasPrefix = F.Bind2of2(strings.HasPrefix)
|
||||
)
|
||||
|
||||
func Eq(left string, right string) bool {
|
||||
func Eq(left, right string) bool {
|
||||
return left == right
|
||||
}
|
||||
|
||||
@@ -58,14 +58,17 @@ func ToRunes(s string) []rune {
|
||||
return []rune(s)
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func IsEmpty(s string) bool {
|
||||
return len(s) == 0
|
||||
return s == ""
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func IsNonEmpty(s string) bool {
|
||||
return len(s) > 0
|
||||
return s != ""
|
||||
}
|
||||
|
||||
//go:inline
|
||||
func Size(s string) int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/IBM/fp-go/v2/number"
|
||||
N "github.com/IBM/fp-go/v2/number"
|
||||
O "github.com/IBM/fp-go/v2/ord"
|
||||
S "github.com/IBM/fp-go/v2/string"
|
||||
@@ -241,7 +240,7 @@ func TestPush3(t *testing.T) {
|
||||
|
||||
// Test Monoid functions
|
||||
func TestMonoid1(t *testing.T) {
|
||||
m := Monoid1(number.MonoidSum[int]())
|
||||
m := Monoid1(N.MonoidSum[int]())
|
||||
t1 := MakeTuple1(5)
|
||||
t2 := MakeTuple1(3)
|
||||
result := m.Concat(t1, t2)
|
||||
@@ -250,7 +249,7 @@ func TestMonoid1(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMonoid2(t *testing.T) {
|
||||
m := Monoid2(S.Monoid, number.MonoidSum[int]())
|
||||
m := Monoid2(S.Monoid, N.MonoidSum[int]())
|
||||
t1 := MakeTuple2("hello", 5)
|
||||
t2 := MakeTuple2(" world", 3)
|
||||
result := m.Concat(t1, t2)
|
||||
@@ -259,7 +258,7 @@ func TestMonoid2(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMonoid3(t *testing.T) {
|
||||
m := Monoid3(S.Monoid, number.MonoidSum[int](), number.MonoidProduct[int]())
|
||||
m := Monoid3(S.Monoid, N.MonoidSum[int](), N.MonoidProduct[int]())
|
||||
t1 := MakeTuple3("a", 2, 3)
|
||||
t2 := MakeTuple3("b", 4, 5)
|
||||
result := m.Concat(t1, t2)
|
||||
@@ -527,10 +526,10 @@ func TestPush5(t *testing.T) {
|
||||
// Test Monoid for larger tuples
|
||||
func TestMonoid4(t *testing.T) {
|
||||
m := Monoid4(
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
)
|
||||
t1 := MakeTuple4(1, 2, 3, 4)
|
||||
t2 := MakeTuple4(5, 6, 7, 8)
|
||||
@@ -540,11 +539,11 @@ func TestMonoid4(t *testing.T) {
|
||||
|
||||
func TestMonoid5(t *testing.T) {
|
||||
m := Monoid5(
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
)
|
||||
t1 := MakeTuple5(1, 2, 3, 4, 5)
|
||||
t2 := MakeTuple5(1, 1, 1, 1, 1)
|
||||
@@ -1044,12 +1043,12 @@ func TestTuple10JSON(t *testing.T) {
|
||||
// Test Monoid for sizes 6-10
|
||||
func TestMonoid6(t *testing.T) {
|
||||
m := Monoid6(
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
)
|
||||
t1 := MakeTuple6(1, 2, 3, 4, 5, 6)
|
||||
t2 := MakeTuple6(1, 1, 1, 1, 1, 1)
|
||||
@@ -1059,13 +1058,13 @@ func TestMonoid6(t *testing.T) {
|
||||
|
||||
func TestMonoid7(t *testing.T) {
|
||||
m := Monoid7(
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
)
|
||||
t1 := MakeTuple7(1, 2, 3, 4, 5, 6, 7)
|
||||
t2 := MakeTuple7(1, 1, 1, 1, 1, 1, 1)
|
||||
@@ -1075,14 +1074,14 @@ func TestMonoid7(t *testing.T) {
|
||||
|
||||
func TestMonoid8(t *testing.T) {
|
||||
m := Monoid8(
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
)
|
||||
t1 := MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8)
|
||||
t2 := MakeTuple8(1, 1, 1, 1, 1, 1, 1, 1)
|
||||
@@ -1092,15 +1091,15 @@ func TestMonoid8(t *testing.T) {
|
||||
|
||||
func TestMonoid9(t *testing.T) {
|
||||
m := Monoid9(
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
)
|
||||
t1 := MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9)
|
||||
t2 := MakeTuple9(1, 1, 1, 1, 1, 1, 1, 1, 1)
|
||||
@@ -1110,16 +1109,16 @@ func TestMonoid9(t *testing.T) {
|
||||
|
||||
func TestMonoid10(t *testing.T) {
|
||||
m := Monoid10(
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
)
|
||||
t1 := MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
t2 := MakeTuple10(1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
|
||||
@@ -1779,17 +1778,17 @@ func TestTuple15JSON(t *testing.T) {
|
||||
// Test Monoid for sizes 11-15
|
||||
func TestMonoid11(t *testing.T) {
|
||||
m := Monoid11(
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
)
|
||||
t1 := MakeTuple11(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
|
||||
t2 := MakeTuple11(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
|
||||
@@ -1799,18 +1798,18 @@ func TestMonoid11(t *testing.T) {
|
||||
|
||||
func TestMonoid12(t *testing.T) {
|
||||
m := Monoid12(
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
)
|
||||
t1 := MakeTuple12(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
|
||||
t2 := MakeTuple12(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
|
||||
@@ -1820,19 +1819,19 @@ func TestMonoid12(t *testing.T) {
|
||||
|
||||
func TestMonoid13(t *testing.T) {
|
||||
m := Monoid13(
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
)
|
||||
t1 := MakeTuple13(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)
|
||||
t2 := MakeTuple13(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
|
||||
@@ -1842,20 +1841,20 @@ func TestMonoid13(t *testing.T) {
|
||||
|
||||
func TestMonoid14(t *testing.T) {
|
||||
m := Monoid14(
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
)
|
||||
t1 := MakeTuple14(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
|
||||
t2 := MakeTuple14(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
|
||||
@@ -1865,21 +1864,21 @@ func TestMonoid14(t *testing.T) {
|
||||
|
||||
func TestMonoid15(t *testing.T) {
|
||||
m := Monoid15(
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
number.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
N.MonoidSum[int](),
|
||||
)
|
||||
t1 := MakeTuple15(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
|
||||
t2 := MakeTuple15(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
|
||||
|
||||
Reference in New Issue
Block a user