ginger/seq/lazy.go

147 lines
3.4 KiB
Go

package seq
import (
"github.com/mediocregopher/ginger/types"
)
// A Lazy is an implementation of a Seq which only actually evaluates its
// contents as those contents become needed. Lazys can be chained together, so
// if you have three steps in a pipeline there aren't two intermediate Seqs
// created, only the final resulting one. Lazys are also thread-safe, so
// multiple routines can interact with the same Lazy pointer at the same time
// but the contents will only be evalutated once.
type Lazy struct {
this types.Elem
next *Lazy
ok bool
ch chan struct{}
}
// Given a Thunk, returns a Lazy around that Thunk.
func NewLazy(t Thunk) *Lazy {
l := &Lazy{ch: make(chan struct{})}
go func() {
l.ch <- struct{}{}
el, next, ok := t()
l.this = el
l.next = NewLazy(next)
l.ok = ok
close(l.ch)
}()
return l
}
// Implementation of FirstRest for Seq interface. Completes in O(1) time.
func (l *Lazy) FirstRest() (types.Elem, Seq, bool) {
if l == nil {
return nil, l, false
}
// Reading from the channel tells the Lazy to populate the data and prepare
// the next item in the seq, it closes the channel when it's done that.
if _, ok := <-l.ch; ok {
<-l.ch
}
if l.ok {
return l.this, l.next, true
} else {
return nil, nil, false
}
}
// Implementation of String for Stringer
func (l *Lazy) String() string {
return ToString(l, "<<", ">>")
}
// Thunks are the building blocks a Lazy. A Thunk returns an element, another
// Thunk, and a boolean representing if the call yielded any results or if it
// was actually empty (true indicates it yielded results).
type Thunk func() (types.Elem, Thunk, bool)
func mapThunk(fn func(types.Elem) types.Elem, s Seq) Thunk {
return func() (types.Elem, Thunk, bool) {
el, ns, ok := s.FirstRest()
if !ok {
return nil, nil, false
}
return fn(el), mapThunk(fn, ns), true
}
}
// Lazy implementation of Map
func LMap(fn func(types.Elem) types.Elem, s Seq) Seq {
return NewLazy(mapThunk(fn, s))
}
func filterThunk(fn func(types.Elem) bool, s Seq) Thunk {
return func() (types.Elem, Thunk, bool) {
for {
el, ns, ok := s.FirstRest()
if !ok {
return nil, nil, false
}
if keep := fn(el); keep {
return el, filterThunk(fn, ns), true
} else {
s = ns
}
}
}
}
// Lazy implementation of Filter
func LFilter(fn func(types.Elem) bool, s Seq) Seq {
return NewLazy(filterThunk(fn, s))
}
func takeThunk(n uint64, s Seq) Thunk {
return func() (types.Elem, Thunk, bool) {
el, ns, ok := s.FirstRest()
if !ok || n == 0 {
return nil, nil, false
}
return el, takeThunk(n-1, ns), true
}
}
// Lazy implementation of Take
func LTake(n uint64, s Seq) Seq {
return NewLazy(takeThunk(n, s))
}
func takeWhileThunk(fn func(types.Elem) bool, s Seq) Thunk {
return func() (types.Elem, Thunk, bool) {
el, ns, ok := s.FirstRest()
if !ok || !fn(el) {
return nil, nil, false
}
return el, takeWhileThunk(fn, ns), true
}
}
// Lazy implementation of TakeWhile
func LTakeWhile(fn func(types.Elem) bool, s Seq) Seq {
return NewLazy(takeWhileThunk(fn, s))
}
func toLazyThunk(s Seq) Thunk {
return func() (types.Elem, Thunk, bool) {
el, ns, ok := s.FirstRest()
if !ok {
return nil, nil, false
}
return el, toLazyThunk(ns), true
}
}
// Returns the Seq as a Lazy. Pointless for linked-lists, but possibly useful
// for other implementations where FirstRest might be costly and the same Seq
// needs to be iterated over many times.
func ToLazy(s Seq) *Lazy {
return NewLazy(toLazyThunk(s))
}