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. 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+unsubscr...@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.