Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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

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.



> 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.


Func 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.


Absolutely. 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.


It 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


I 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.


I 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


Aah, 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

>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)


Sure, and that should be somewhere in the very long posts about this or in the very long github issue.



This does not contain anything about the proposed implementation of Pull, of course.


Wouldn'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
    }


That is an implementation in terms of primitives that are not in the proposal.


I 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

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.


You can read the proposal here: 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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: