Skip to content

Implement MapUp and MapDown #157

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
```

<!-- prettier-ignore -->
> [!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.
Expand Down
20 changes: 19 additions & 1 deletion it/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,29 @@ 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
}
}
}
}

// 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
}
}
}
}
68 changes: 68 additions & 0 deletions it/map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
}
8 changes: 8 additions & 0 deletions it/op/op.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
33 changes: 33 additions & 0 deletions it/op/op_test.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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))
}