4 ms·
I'm guessing this is following the same / highly similar motivation as rsc's coroutines post about a month ago: https://research.swtch.com/coro https://research
by Groxx 3y ago
I'm guessing this is following the same / highly similar motivation as rsc's coroutines post about a month ago: https://research.swtch.com/coro https://research.swtch.com/coro
I welcome people embracing function closures more, as I feel they're a drastically under-used language tool (particularly in Go where everyone crams everything into single-func interfaces which are almost exclusively far worse). More exposure to it helps people become comfortable with it, and now that we have generics it's finally not a pile of pain and agony at all times. And just using functions makes it very flexible / doesn't repeat the "X for me but not for thee" issues of past Go versions.
I'm not entirely sure how I feel about this proposal though. It kinda feels like it's trying to put a round peg into a square hole. If you try hard enough, everything can go in the square hole! But that doesn't mean it's a good fit.
- anonymoushn 3y agoIt would be nice if the proposal defined the semantics of getting single item from the iterator (or detecting that it is out of items) and exposed an api for this, then defined the behavior of range as equivalent to some pattern using these primitives. As is the proposal says "there should be some coro.Pull type thing" which is a bit terse and says nothing about whether the implementation of coro.Pull will be fast or whether it will be built on range + channels
- znkr 3y agohttps://research.swtch.com/coro https://research.swtch.com/coro
- anonymoushn 3y agoThis does not contain anything about the proposed implementation of Pull, of course.
- Groxx 3y agoWouldn't this be the implementation? func Pull[V any](push func(yield func(V) bool)) (pull func() (V, bool), stop func()) { ... // snipped because it's big }
- anonymoushn 3y agoThat is an implementation in terms of primitives that are not in the proposal.
- Groxx 3y agoI don't believe so - the post is pretty much all about how you can do this right now in normal Go code. And e.g. a bit after that is a link to the "full code" which is runnable: https://go.dev/play/p/hniFxnbXTgH https://go.dev/play/p/hniFxnbXTgH Possibly you're interpreting yield as a new keyword? It's just an argument to Pull. The only not-(currently-)available-in-Go stuff that I can recall from the post is the performance optimization at the very end.
- anonymoushn 3y agoYou can read the proposal here: https://github.com/golang/go/issues/61405 https://github.com/golang/go/issues/61405. It is linked in the first paragraph of TFA. Yes, I know you can build a 100x-too-slow version of this on top of channels, but that's not the proposed extension to the language.
- Groxx 3y agoI suspect getting a single item out will be that you just make a func next[T](i iterfunc[T]) T { var item T i(func(it T) bool){ item = it return true // likely }) return item } and use it. Semantics should be the same as ranging would desugar to (since that's all this is), though the variable arity will be slightly annoying of course.
- anonymoushn 3y agoSure, and that should be somewhere in the very long posts about this or in the very long github issue.
- assbuttbuttass 3y agoI don't think it's so easy. An iterfunc doesn't store any state explicitly, so each time you call i it restarts from the beginning
- Groxx 3y agoAah, yes, that's an excellent point. Thanks! That does complicate things a lot more. Quite a lot more, as it essentially means you can only consume from iterators you create... which sorta defeats the purpose of iterators as a value, and degrades them almost completely to just a `range` helper. --- edit from https://github.com/golang/go/issues/61405 https://github.com/golang/go/issues/61405 >To convert one of these functions to a "next-based" or "pull-based" iterator, we plan to add a function to the standard library that runs the yield-based ("push-based") iterator in a coroutine, along the lines of the coro.Pull function in my recent blog post. that's... an option I guess. Unless the runtime special-cases this coroutine though it seems like it'll probably have non-trivial performance cost, roughly equivalent to the channel + goroutine equivalents people sometimes build now. Which seems... not great. Bad enough that anyone even slightly performance-sensitive will probably avoid it. (his coro post also mentions a proposed runtime change to improve the performance, but afaik previous attempts to get this kind of change into the language haven't worked out reliably, and you can't trust callers to be single-threaded)
- throwaway073123 3y ago> particularly in Go where everyone crams everything into single-func interfaces which are almost exclusively far worse Depending on scope, I've grown to prefer the single func interface. For a smaller scope use, say filepath.Walk, absolutely, pass in a closure, good stuff. For larger stuff, like say a chain of http middleware and handlers, I think it can be very limiting. you get a big benefit from using the interface over a function: the underlying type _can_ have more methods and implement. this gives room to implement a parallel interface, for example, to collect all possible routes, or to trace what handlers a request would pass through. I don't think there's a one-size-fits all approach, use whatever gives the effort:utility tradeoff you're looking for.
- Groxx 3y agoFunc references can have methods as well fwiw :) They just need to have a named type. But sure, if you want to build a multi-method thing (or upcast to detect optional methods), an interface is the natural choice - it's no longer a single func.
- throwaway073123 3y agoAbsolutely. If I see a one method interface, I generally assume that there's a ThingDoerFunc type that implements the interface. Likewise, (ThingDoerImplemetation{}).DoThing is a valid func reference Hence I say there's not really a one size fits all, there are a options for different circumstances.