Closed seiyab closed 1 month ago
function name can be FromProcedure
, FromRoutine
FromFunction
, Go
or something else
This is definitely an interesting feature.
Justifications exist, but weak.
Contrary to iterators from channels, this one would have no overhead and should be preferred if you don't work with concurrency/plug into existing code.
However, real generators require support up from the language level, which Go has not. You can't "suspend" the callstack like a stackless coroutine and return from a function multiple times.
This means that this would have to be implemented with a producer goroutine and channels. Which is not that far away from just using a iterator from a channel:
func ExampleGenerator() {
it := iter.Generator[string](cs chan<- string) {
cs <- "hello"
cs <- "generator"
})
fmt.Println(iter.Collect(it))
// Output: [hello generator]
}
which is doable with just:
func Generator[T any](f func(chan<- T)) *ChannelIter[T] {
cs := make(chan T)
go func() {
f(cs)
close(cs)
}()
return FromChannel[T](cs)
}
In case the iterator is dropped and not exhausted, the spawned goroutine will stay around forever 😕
@dranikpg Thank you for your helpful comment.
In case the iterator is dropped and not exhausted, the spawned goroutine will stay around forever
That's true. So feasible options will be following, and each of them has clear disadvantages.
chan T
, chan any
(for interrupt) and explicit Close
method
func ExampleGenerator() {
it := iter.Generator[string](func(yield func(v string) bool) {
if !yield("hello") { return }
if !yield("generator") { return }
})
defer it.Close()
fmt.Println(iter.Collect(it))
// Output: [hello generator]
}
Close
yield
chan
and then just wrap it by ChanIter
.T[]
func Generator[T any](f func(yield func(v T)))*LiftIter[T] {
var items []T
yield := func(v T) {
items = append(items, v)
}
f(yield)
return Lift(items)
}
Next
func ExampleGenerator() {
it := iter.Generator[int](func() func() int {
var i int = 1
return func() int {
r := i
i *= 2
return r
}
})
fmt.Println(iter.Collect(iter.Take(it, 3)))
// Output: [1 2 4]
}
As far as I know, this suggestion can't be useful enough. I will close this issue if there is no good idea.
I'm afraid the won't be any more ideas 😢
The first option is not that bad actually, except for the Close
. Having to close the iterator is inconvenient almost impossible, especially if you want to pass it around and wrap it up further.
The third option is usable. Instead of passing a function returning a closure, we could just directly pass the closure. If someone needs local state, he can just create this helper function on its own or store it directly in the function using the iterator.
func CreateMagicSequence(offset int) Iterator[int] {
r := rand.New()
return Generator[int](func(index int) Option[int] {
return Option.Some(index * r.Intn(10) + offset)
}
}
This still more compact than defining your own type + contructor + Next()
function.
I like this option too, where the Generator is a simple convenience for a stateless Iterator (or where state is captured through a closure). Unlike @dranikpg I think the Generator input signature should match that of Next()
(without the receiver):
func Generator[T any](gen func() option.Option[T]) GeneratorIter { ... }
Passing a closure directly sounds good.
I prefer the signature that matches that of Next()
.
Perhaps the name Generator
is confusing for current idea?
Perhaps simply iter.New(f) *FuncIter { ... }
Maybe hold fire on this until the Go iterator conversation comes to fruition.
Well Go iterators are a thing soon! I think an API like this would be appropriate:
func Generator[V any](fn func() V) iter.Seq[V] {
...
}
and
func Generator2[V, W any](fn func() (V, W)) iter.Seq2[V, W] {
...
}
Note that the current latest implementation is in the branch v2
.
Not sure if you're still interested in working on this @seiyab ?
Thank you for notifying me! Feel free to implement it ignoring me. I'm still interested in this but I'm busy for a while because of my life event. I might work on it when I will get time.
I don't think this is needed any longer given the new iter.Seq
syntax. Happy to revisit if there's a need for it from users.
Is your feature request related to a problem? Please describe.
No, just an idea. It should be convenient because this pattern can be seen in other languages. examples: Python, JavaScript, Kotlin
Describe the solution you'd like
Creating iterator from imperative function.
Provide code snippets to show how this new feature might be used.
Does this incur a breaking change?
no.
Do you intend to build this feature yourself?
Maybe. Feel free to comment if someone intend to commit.
Additional context I'm not sure whether it is good feature or not. It's because following reasons.
Justifications exist, but weak.