-
Notifications
You must be signed in to change notification settings - Fork 18k
proposal: iter: add Push
function
#72083
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
Comments
Related Issues Related Discussions (Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.) |
CC @golang/runtime |
This comment has been minimized.
This comment has been minimized.
Change https://go.dev/cl/654516 mentions this issue: |
+1 Additionally, Push2 needs to be added to support value pairs. Push/2 will also enable fan-out to multiple func fanout(i iter.Seq2[any, error]) {
y, stop := Push(func(seq iter.Seq[any]) {
for res := range seq {
fmt.Println("exp1", res)
}
})
defer stop()
y2, stop := Push(func(seq iter.Seq[any]) {
for res := range seq {
fmt.Println("exp2", res)
}
})
defer stop()
for v, _ := range i {
y(v)
y2(v)
}
return nil
} |
+1. I also had the same idea. #61898 (comment) func Push[V any](recv func(iter.Seq[V])) (push func(V) bool) {
var in V
coro := func(yieldCoro func(struct{}) bool) {
seq := func(yieldSeq func(V) bool) {
for yieldSeq(in) {
yieldCoro(struct{}{})
}
}
recv(seq)
}
next, stop := iter.Pull(coro)
return func(v V) bool {
in = v
_, more := next()
if !more {
stop()
return false
}
return true
}
} There is an interactive demo. https://github.com/eihigh/morse-demo fanout demo: https://go.dev/play/p/r2Wp7DUgAVy |
OK, so I was wrong in golang-nuts and this can be implemented with To me that reduces the need for If we do still want func Push[V any](consumer func(seq iter.Seq[V])) (yield func(V) bool, stop func()) {
var in V
coro := func(yieldCoro func(struct{}) bool) {
seq := func(yieldSeq func(V) bool) {
for yieldSeq(in) {
if !yieldCoro(struct{}{}) {
break
}
}
}
consumer(seq)
}
next, stop := iter.Pull(coro)
yield = func(v V) bool {
in = v
_, more := next()
if !more {
stop()
}
return more
}
return yield, stop
} I do think it's valuable to return the |
I put that into a gist because I know I'm going to want it eventually and won't want to figure it out again. 😆 |
Given func Pull[V any](seq Seq[V]) (next func() (V, bool), stop func()) I would expect a function named // Push converts the “pull-style” iterator
// accessed by the two functions next and stop
// into a “push-style” iterator sequence.
// Push essentially is the inverse of [iter.Pull].
// Note that you must consume the resulting iterator;
// otherwise, the underlying pull-based iterator may leak.
func Push[V any](next func() (V, bool), stop func()) iter.Seq[V] |
Do you have a practical purpose for a function with that signature? Your test/example simply consumes the output of For example, can you implement fan-out, @eihigh and @shaj13 demonstrated? I'm perfectly happy to withdraw my proposal, as |
@ncruces True, my What I'm questioning about your function is its the name rather than its expressive power. Given the prominence of the "push"/"pull" parlance in documentation about Go iterators, I would expect a function named "Push" to be the inverse of |
There's no agreement here, and Beyond coro := func(yieldCoro func(struct{}) bool) {
seq := func(yieldSeq func(V) bool) {
for yieldSeq(in) {
if !yieldCoro(struct{}{}) {
break
}
}
}
consumer(seq)
} I'm not sure what's the best way to wrap it into a useful API, what name to give it, and how best to explain it. Beyond the fact that both I and @eihigh independently invented the same API, with the same name, months apart (although I only managed to implement it through channels and goroutines), and apparently both used it to solve real world problems, I have nothing to recommend this API and name over others. So I'll personally withdraw the proposal, though the Go team is obviously free to reopen it. Maybe the real solution is to first start by introducing a version of this in the |
@ncruces, I just saw this. I actually have a function that can handle this scenario in my personal |
Thanks @DeedleFake. For now I "inlined" everything around the "kernel" @eihigh came up with, just because for a given aggregate this can be called millions of times, so every bit counts (the channel based solution I had found was terrible at this). But I agree that a nicer generalized API for coroutines (like yours? I'll definitely study it further) would probably be useful. I'm not sure |
Proposal Details
This proposal stems from a thread in golang-nuts.
The idea is to add a
Push
function to packageiter
(and likely a similarPush2
):This is similar in concept to the
Pull
andPull2
functions, except thatyield
andstop
are used to push values intoseq
.It is based on a similar proposal by @ianlancetaylor in the thread, which unfortunately would not hide goroutine creation/management (a goal of mine).
Their worry with my counter proposal (this one) is, quoting:
However, ISTM that it is possible with my proposal (although potentially racy) to pass the iterator to another goroutine, you just need to ensure this goroutine terminates before
consumer
(perhaps with async.WaitGroup
). The potential races don't seem worse than those caused by passingnext
/stop
fromPull
to other goroutines.Purpose
The purpose of this function is basically the same as
Pull
: adapt/plug existing iteration APIs to the "standard"iter.Seq
interface.It came up when trying to use a function with this signature:
func processor(seq iter.Seq[float64]) float64
(a function that receives a sequence of floats, and outputs a single float, e.g. the sum/average/etc) to implement this interface (which allows you to implement custom aggregate functions in SQLite:In the simple case SQLite calls the
AggregateFunction.Step
once for each row (arg
are the columns of the row, a single column isarg[0].Float()
), and thenValue
once to get the aggregate result (you callctx.ResultFloat(x)
to return the result).It turns out that it's impossible to use a
processor
function like the above to implement the interface, without:Proposed semantics and implementation based on channels
There's a race (and potential
panic: send on closed channel
) between theclose(swtch)
and theswtch <- struct{}{}
inconsumer
ifconsumer
starts a goroutine and theseq
outlivesconsumer
. Otherwise, there is no parallelism, only concurrency.The implementation takes liberally from
iter.Pull
and can translate toruntime.newcoro
/coroswitch
by replacing theswtch
channel. In that case, thepanic
becomesfatal error: coroswitch on exited coro
.The text was updated successfully, but these errors were encountered: