Skip to content

Instantly share code, notes, and snippets.

@huandu
Created November 28, 2014 11:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save huandu/73ffed720c7416d11a83 to your computer and use it in GitHub Desktop.
Save huandu/73ffed720c7416d11a83 to your computer and use it in GitHub Desktop.
Javascript style generator in Golang

This is a javascript style iterator sample using go. It's only a demo so don't expect too much from it.

I'm writing this to test whether it's possible to use goroutine to simulate generator without any issue. I take special care on memory issue. I use runtime.SetFinalizer to make sure once it is released all related goroutines will stop properly.

The result is quite good. It's promising.

And I also think of a way to declare generator function in go - using valid go syntax.

// Look at its return type.
// Unlike normal function, which return values at the end of function,
// generator function returns iterator and uses `yield` to exchange
// data. So the return type just contains types, no varaible name.
// The return type means:
// - `string iterator`: yield will send out string value to caller.
// - `int`: yield function will return int value from caller.
func SampleFunc(foo string) (string iterator, int) {
    var i int
    i = yield("first")
    i = yield("second")
    i = yield("third")
}

Of course, above code is not possible to pass go compiler. I'm planning to write a pre-compiler to generate correct code for me. What's the name of the pre-compiler? Rat, maybe.

package main
import (
"fmt"
"runtime"
"time"
)
type Iterator struct {
chans *IteratorChans
}
type IteratorChans struct {
yieldData chan string
nextData chan string
}
type iteratorFinalizedError struct{}
func (it Iterator) Next(msg string) (data string, ok bool) {
fmt.Println("calling Next()...")
it.chans.nextData <- msg
data, ok = <-it.chans.yieldData
return
}
func (chans *IteratorChans) wait() bool {
fmt.Println("calling wait()...")
_, ok := <-chans.nextData
return ok
}
func (chans *IteratorChans) close() {
fmt.Println("calling close()...")
close(chans.yieldData)
}
func (chans *IteratorChans) Yield(data string) string {
fmt.Println("calling yield()...")
chans.yieldData <- data
msg, ok := <-chans.nextData
if !ok {
panic(iteratorFinalizedError{})
}
return msg
}
func iteratorFinalizer(it *Iterator) {
fmt.Println("calling finalizer...")
close(it.chans.nextData)
}
func NewIterator(handler func(*IteratorChans)) *Iterator {
it := &Iterator{
chans: &IteratorChans{
yieldData: make(chan string),
nextData: make(chan string),
},
}
runtime.SetFinalizer(it, iteratorFinalizer)
chans := it.chans
go func() {
defer func() {
fmt.Println("handler exits...")
chans.close()
if e := recover(); e != nil {
if _, ok := e.(iteratorFinalizedError); ok {
return
}
panic(e)
}
}()
if !chans.wait() {
return
}
handler(chans)
}()
return it
}
func SampleFunc(foo string) *Iterator {
return NewIterator(func(chans *IteratorChans) {
fmt.Println("start iterator...", foo)
fmt.Println("yield 1...")
fmt.Println("yield 1 returns", chans.Yield("first"))
fmt.Println("yield 2...")
fmt.Println("yield 2 returns", chans.Yield("second"))
fmt.Println("yield 3...")
fmt.Println("yield 3 returns", chans.Yield("third"))
})
}
func main() {
it := SampleFunc("hello, iterator!")
for data, ok := it.Next(""); ok; data, ok = it.Next("something") {
fmt.Println("read data:", data)
//break
}
it = nil
for {
runtime.GC()
time.Sleep(time.Second)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment