You can use a closure as a generator: package main
import "fmt" func getPositiveOdds( numbers []int, ) ( iter func() (int, bool), ) { iter = func() (ret int, ok bool) { for len(numbers) > 0 { if numbers[0] > 0 && numbers[0]&1 == 1 { ret = numbers[0] numbers = numbers[1:] ok = true return } else { numbers = numbers[1:] } } return 0, false } return } func main() { iter := getPositiveOdds([]int{ -2, -1, 0, 1, 2, 3, }) for { n, ok := iter() if !ok { break } fmt.Printf("%d\n", n) } } On Tuesday, October 27, 2020 at 2:22:20 AM UTC+8 axel.wa...@googlemail.com wrote: > > > On Mon, Oct 26, 2020, 19:05 Oliver Smith <oliver...@superevilmegacorp.com> > wrote: > >> >> Hi Axel, thanks for replying; >> >> It isn't a pattern I teach anyone, rather it's a pattern which people I'm >> encouraging to learn golang ask me about. Frequently. I was also under the >> impression that generally passing a send-only channel to a function could >> typically be considered an indicator the caller retains responsibility for >> closing the channel. >> > > The fact that there is no real clarity on that is exactly why I would > advise against exposing channels in an API. > > > On Sunday, October 25, 2020 at 5:18:27 PM UTC-7 axel.wa...@googlemail.com >> wrote: >> >>> On Mon, Oct 26, 2020 at 12:29 AM Oliver Smith < >>> oliver...@superevilmegacorp.com> wrote: >>> >>>> This pattern/idiom is very un-golike in that it's an eyesore, and one >>>> of the hardest things I've found to teach people new to trying to read go >>>> code. >>>> >>> >>> FWIW, I definitely disagree that this is somehow "un-golike" >>> (non-specific, subjective terms like this have exactly this problem - no >>> one really knows what it means). And I mostly disagree that it's a >>> particularly bad problem - I can see where you are coming from, but at >>> worst, this seems a minor issue, not worth changing the language for. >>> >>> I would also recommend against teaching this to newcomers to the >>> language. Returning a channel in this way has a couple of issues - among >>> others, it doesn't allow the caller to control buffering of the channel. >>> This can be solved (which also, IMO, solves your readability issues) by >>> instead *taking* a channel to write to: >>> >>> func filterPositiveOdds(numbers []int, ch chan<- int) { >>> defer close(channel) >>> for _, value := range numbers { >>> if value > 0 && (value & 1) == 1 { >>> channel <- value >>> } >>> } >>> } >>> >>> func caller() { >>> var numbers []int >>> ch := make(chan int) >>> go filterPositiveOdds(numbers, ch) >>> for n := range ch { >>> } >>> } >>> >>> This has fundamentally the same control-flow, but we give a name to the >>> function, thus making it clearer, what the extra goroutine is for. >>> >>> However, IMO this is still bad form. In general, I would advise exposing >>> channels in APIs. It requires you to specify extra properties, like "what >>> happens if the channel blocks" or "how does the operation get cancelled" in >>> the documentation, without a way to get correctness compiler-checked. In >>> particular, the code (both mine and yours) suffers from exactly these >>> problems - if the channel is not consumed, we leak a goroutine and there is >>> no way to prematurely abort consumption. Getting an error back is even >>> worse. >>> >>> A better way is to provide a simple, synchronous iterator API like >>> bufio.Scanner <https://golang.org/pkg/bufio/#Scanner> does. >>> For example, you could have >>> >>> type IntIterator struct { >>> numbers []int >>> } >>> >>> func FilterPositiveOdds(numbers []int) *IntIterator { >>> return &IntIterator{numbers} >>> } >>> >>> func (it *IntIterator) Next() (n int, ok bool) { >>> for _, n := range it.numbers { >>> it.numbers = it.numbers[1:] >>> if (n > 0 || n & 1 != 0) { >>> return n, true >>> } >>> } >>> return 0, false >>> } >>> >>> In complex cases, concurrency can be hidden behind the iterator API. In >>> simple cases, you could also reduce boilerplate by doing >>> >>> func FilterPositiveOdds(numbers []int) (next func() (n int, ok bool)) { >>> return func() (n int, ok bool) { >>> // same as above, but closing over numbers >>> } >>> } >>> >>> In any case - if you are unhappy with your pattern, there are many >>> alternatives to choose from, within the language as it exists today. It >>> seems hardly worth extra language features, to simplify this IMO rather >>> uncommon construct. >>> >>> >>> >>>> I've toyed with a few alternatives, but I'm having a hard time finding >>>> something I think everyone would like. >>>> >>>> Pattern the first; 'go return` which flips the idiom with syntactic >>>> sugar. 'go return' returns the function and continues the remainder of it >>>> in a go func() { ... } >>>> >>>> ```go >>>> func getPositiveOdds(numbers []int) <-chan int { >>>> channel := make(chan int) >>>> go return channel >>>> >>>> defer close(channel) >>>> for _, value := range numbers { >>>> if value > 0 && (value & 1) == 1 { >>>> channel <- value >>>> } >>>> } >>>> } >>>> ``` >>>> >>>> The second more closely binds to the channel-generator pattern by >>>> extending make(chan) to take a 'go func' parameter: >>>> >>>> ```go >>>> func getPositiveOdds(numbers []int) <-chan int { >>>> generator := make(chan int, go func (channel chan int) { >>>> defer close(channel) >>>> for _, value := range numbers { >>>> if value > 0 && (value & 1) == 1 { >>>> channel <- value >>>> } >>>> } >>>> }) >>>> return generator >>>> } >>>> ``` >>>> >>>> I was tempted by the notion of having this auto-defer-close the >>>> channel, but I think retaining the explicit close is generally better and >>>> more teachable. >>>> >>>> Where it might be tenable would be using syntactic sugar to perhaps >>>> better promote the notion of generators toward 1st class citizens: >>>> >>>> ```go >>>> >>>> // 'generator' is syntactic sugar, and instead of a return type you provide >>>> // the make statement to create the required channel type. >>>> generator getPositiveOdds(numbers []int) make(chan int, 1) { >>>> >>>> // The variable 'yield' is automatically defined as the channel created >>>> for _, value := range numbers { >>>> if value > 0 && (value & 1) == 1 { >>>> yield <- value >>>> } >>>> } >>>> } >>>> ``` >>>> >>>> which is roughly equivalent to: >>>> >>>> ```golang >>>> func getPositiveOdds(numbers []int]) <-chan int { >>>> generator := func (yield chan int) { >>>> numbers := numbers >>>> for _, value := range numbers { >>>> if value > 0 && (value & 1) == 1 { >>>> yield <- value >>>> } >>>> } >>>> } >>>> >>>> channel := make(chan int, 1) >>>> >>>> go func () { >>>> defer close(channel) >>>> generator(channel) >>>> }() >>>> >>>> return channel >>>> } >>>> ``` >>>> >>>> `gen` instead of `generator` might be ok, I just wanted to be >>>> unambiguous. >>>> >>>> -- >>>> You received this message because you are subscribed to the Google >>>> Groups "golang-nuts" group. >>>> To unsubscribe from this group and stop receiving emails from it, send >>>> an email to golang-nuts...@googlegroups.com. >>>> To view this discussion on the web visit >>>> https://groups.google.com/d/msgid/golang-nuts/5aa92ac7-1c9b-48af-8389-a6870563b831n%40googlegroups.com >>>> >>>> <https://groups.google.com/d/msgid/golang-nuts/5aa92ac7-1c9b-48af-8389-a6870563b831n%40googlegroups.com?utm_medium=email&utm_source=footer> >>>> . >>>> >>> -- >> You received this message because you are subscribed to the Google Groups >> "golang-nuts" group. >> To unsubscribe from this group and stop receiving emails from it, send an >> email to golang-nuts...@googlegroups.com. >> > To view this discussion on the web visit >> https://groups.google.com/d/msgid/golang-nuts/383cbb48-5bda-426a-805a-8d3efa82d6e8n%40googlegroups.com >> >> <https://groups.google.com/d/msgid/golang-nuts/383cbb48-5bda-426a-805a-8d3efa82d6e8n%40googlegroups.com?utm_medium=email&utm_source=footer> >> . >> > -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/a2a6771b-e446-4199-b015-5a5a7ac09527n%40googlegroups.com.