Skip to content
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

proposal: iter: add Empty function #66187

Open
tamayika opened this issue Mar 8, 2024 · 5 comments
Open

proposal: iter: add Empty function #66187

tamayika opened this issue Mar 8, 2024 · 5 comments
Labels
Milestone

Comments

@tamayika
Copy link

tamayika commented Mar 8, 2024

Proposal Details

Add Empty function to iter package.
This function returns empty iterator.

Implementation

package iter

func Empty[T any]() Seq[T] {
	return func(yield func(T) bool) {}
}

func Empty2[K, V any]() Seq2[K, V] {
	return func(yield func(K, V) bool) {}
}

Usage

This function is useful to replace slice/map implementation to iter implementation.
Please see following code.
This function do something to all item when cond is true.

func do(cond bool) {
	var all []int
	if cond {
		all = findAll()
	}
	for _, v := range all {
		doItem(v)
	}
}

#65629 was declined, so range over nil function panics.
I have to rewrite like following.

func do(cond bool) {
	var all iter.Seq[int]
	if cond {
		all = findAll()
	}
	if all == nil {
		return
	}
	for _, v := range all {
		doItem(v)
	}
}

When Empty function exists, I can migrate code easily.

func do(cond bool) {
	var all = iter.Empty[int]()
	if cond {
		all = findAll()
	}
	for _, v := range all {
		doItem(v)
	}
}
@gopherbot gopherbot added this to the Proposal milestone Mar 8, 2024
@DeedleFake
Copy link

DeedleFake commented Mar 15, 2024

I know the code is contrived as an example, but couldn't you rewrite it as

if cond {
  if all := findAll(); all != nil {
    for _, v := range all {
      doItem(v)
    }
  }
}

instead? And what is findAll() that it would return nil instead of an empty iterator itself? What circumstances would necessitate this particular approach to it often enough that writing func(func(int) bool) {} manually would be too much of a problem?

@tamayika
Copy link
Author

tamayika commented Mar 16, 2024

I prefer unnested code.
If this function needs error handling, is it understandable situation?

func do(cond bool) error {
	var all = iter.Empty[int]()
	if cond {
		var err error
		all, err = findAll()
		if err != nil {
			return fmt.Errorf("failed to find all: %w", err)
		}
	}
	for _, v := range all {
		doItem(v)
	}
	return nil
}

v.s.

func do(cond bool) error {
	if cond {
		if all, err := findAll(); err != nil {
			return fmt.Errorf("failed to find all: %w", err)
		} else if all != nil {
			for _, v := range all {
				doItem(v)
			}
		}
	}
	return nil
}

writing func(func(int) bool) {} manually would be too much of a problem?

I think it's less readable.

var all = func(func(int) bool) {} // what's this!?

v.s.

var all = iter.Empty[int]() // I see, this is empty iterator.

@DeedleFake
Copy link

How about

func do(cond bool) error {
  if !cond {
    return nil
  }

  all, err := findAll()
  if err != nil {
    return fmt.Errorf("failed to find all: %w", err)
  }
  if all == nil {
    return nil
  }

  for _, v := range all {
    doItem(v)
  }
  return nil
}

instead? And again, what is findAll() that it can return a nil error and and a nil iter.Seq? Why would it not just return an empty iter.Seq itself in the non-error case?

I just can't see this kind of thing coming up very often. Maybe I'm wrong, but I feel like this should be added once it's proven to be an annoyance, rather than in advance as a counter to what seems likely to me to be a non-existent problem.

@tamayika
Copy link
Author

tamayika commented Mar 17, 2024

Please consider the sutuation you can't early return by

  if !cond {
    return nil
  }

because we need to do some after iteration

  for _, v := range all {
    doItem(v)
  }
  doAfterIteration()
  return nil

I just want to say there are many codes which depends on iteration safety of nil slice/map.
And it becomes problem when we replace to iter implementation, so we need the direct replacement for this.
You should write like this insteadly means nothing in this case.

@tamayika
Copy link
Author

How about this case?
Do you write empty iterator manually or add nil checks to all getIds() referenced codes?

func getIds() []int {
	if condA {
		return nil
	}
	somethingAfterA()
	if condB {
		return nil
	}
	somethingAfterB()
	if condC {
		return nil
	}
	somethingAfterC()
	return getIdsImpl()
}

func do() {
	for _, v := range getIds() {
		println(v)
	}
}

When iter.Empty() exists, I can migrate directly.

func getIds() iter.Seq[int] {
	if condA {
		return iter.Empty[int]()
	}
	somethingAfterA()
	if condB {
		return iter.Empty[int]()
	}
	somethingAfterB()
	if condC {
		return iter.Empty[int]()
	}
	somethingAfterC()
	return getIdsImpl()
}

func do() {
	for _, v := range getIds() {
		println(v)
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Incoming
Development

No branches or pull requests

3 participants