diff --git a/README.md b/README.md index 07ba523..b46607d 100644 --- a/README.md +++ b/README.md @@ -753,6 +753,32 @@ itx.FromSlice([]int{1, 2, 3}).TransformError(double) > itx.From2(it.MapError(slices.Values([]int{1, 2, 3}), double)).Collect() > ``` +### MapUp & MapDown + +`MapUp` and `MapDown` are iterators for converting between `iter.Seq` and `iter.Seq2`. They behave +similarly to the `Map` family of iterators except `MapUp` takes a function that receives one +argument and returns two and `MapDown` does the inverse. + +```go +numberAndString := func(n int) (int, string) { + return n, strconv.Itoa(n) +} + +for n, s := range it.MapUp(slices.Values([]int{1, 2, 3})) { + fmt.Println(n, s) +} + +lines := it.Lines(strings.NewReader("one\ntwo\nthree\n")) + +for line := range it.MapDown(lines, op.Must) { + fmt.Println(string(line)) +} +``` + + +> [!NOTE] +> The `itx` package does not contain `MapUp` and `MapDown` due to limitations with Go's type system. + ### NaturalNumbers NaturalNumbers yields all non-negative integers in ascending order. diff --git a/it/map.go b/it/map.go index af8f21d..9168eed 100644 --- a/it/map.go +++ b/it/map.go @@ -29,7 +29,13 @@ func Map2[V, W, X, Y any](delegate func(func(V, W) bool), f func(V, W) (X, Y)) i // MapError yields values from an iterator that have had the provided function // applied to each value where the function can return an error. func MapError[V, W any](delegate func(func(V) bool), f func(V) (W, error)) iter.Seq2[W, error] { - return func(yield func(W, error) bool) { + return MapUp(delegate, f) +} + +// MapUp yields pairs of values from an iterator of single values that have had +// the provided function applied. +func MapUp[V, W, X any](delegate func(func(V) bool), f func(V) (W, X)) iter.Seq2[W, X] { + return func(yield func(W, X) bool) { for value := range delegate { if !yield(f(value)) { return @@ -37,3 +43,15 @@ func MapError[V, W any](delegate func(func(V) bool), f func(V) (W, error)) iter. } } } + +// MapDown yields single values from an iterator of pairs of values that have had +// the provided function applied. +func MapDown[V, W, X any](delegate func(func(V, W) bool), f func(V, W) X) iter.Seq[X] { + return func(yield func(X) bool) { + for v, w := range delegate { + if !yield(f(v, w)) { + return + } + } + } +} diff --git a/it/map_test.go b/it/map_test.go index c39653a..80e6e4d 100644 --- a/it/map_test.go +++ b/it/map_test.go @@ -130,3 +130,71 @@ func TestMapErrorErrorYieldsFalse(t *testing.T) { return false }) } + +func ExampleMapUp() { + identityAndDouble := func(n int) (int, int) { return n, n * 2 } + + for left, right := range it.MapUp(slices.Values([]int{1, 2, 3}), identityAndDouble) { + fmt.Println(left, right) + } + + // Output: + // 1 2 + // 2 4 + // 3 6 +} + +func TestMapUpEmpty(t *testing.T) { + t.Parallel() + + identityAndDouble := func(n int) (int, int) { return n, n * 2 } + + assert.Equal(t, len(maps.Collect(it.MapUp(it.Exhausted[int](), identityAndDouble))), 0) +} + +func ExampleMapDown() { + ignoreErr := func(n int, err error) int { + return n + } + + numbers := maps.All(map[int]error{ + 1: nil, + 2: nil, + 3: errors.New("Oops"), + }) + + for number := range it.MapDown(numbers, ignoreErr) { + fmt.Println(number) + } + + // Output: + // 1 + // 2 + // 3 +} + +func TestMapDownEmpty(t *testing.T) { + t.Parallel() + + ignoreErr := func(n int, err error) int { + return n + } + assert.Equal(t, len(slices.Collect(it.MapDown(it.Exhausted2[int, error](), ignoreErr))), 0) +} + +func TestMapDownYieldFalse(t *testing.T) { + t.Parallel() + + ignoreErr := func(n int, err error) int { + return n + } + + numbers := it.MapDown(maps.All(map[int]error{ + 1: nil, + 2: nil, + }), ignoreErr) + + numbers(func(int) bool { + return false + }) +} diff --git a/it/op/op.go b/it/op/op.go index 2535cb4..5835182 100644 --- a/it/op/op.go +++ b/it/op/op.go @@ -17,3 +17,11 @@ func Ref[V any](v V) *V { func Deref[V any](v *V) V { return *v } + +func Must[V any](v V, err error) V { + if err != nil { + panic(err) + } + + return v +} diff --git a/it/op/op_test.go b/it/op/op_test.go index 70092aa..7f480a2 100644 --- a/it/op/op_test.go +++ b/it/op/op_test.go @@ -1,8 +1,11 @@ package op_test import ( + "errors" "fmt" + "maps" "slices" + "testing" "github.com/BooleanCat/go-functional/v2/it" "github.com/BooleanCat/go-functional/v2/it/op" @@ -34,3 +37,33 @@ func ExampleDeref() { fmt.Println(slices.Collect(it.Map(values, op.Deref))) // Output: [4 5 6] } + +func ExampleMust() { + numbers := maps.All(map[int]error{ + 1: nil, + 2: nil, + 3: nil, + }) + + for number := range it.MapDown(numbers, op.Must) { + fmt.Println(number) + } +} + +func TestMustPanic(t *testing.T) { + t.Parallel() + + defer func() { + r := recover() + + if r == nil { + t.Error("expected panic") + } + }() + + slices.Collect(it.MapDown(maps.All(map[int]error{ + 1: nil, + 2: nil, + 3: errors.New("error"), + }), op.Must)) +}