multiple rangefunc iterators: restart?

197 views
Skip to first unread message

Rory Campbell-Lange

unread,
May 23, 2024, 1:09:19 PM5/23/24
to golang-nuts
I've been playing with with rangefunc experiment, with help from https://go.dev/wiki/RangefuncExperiment and the possible idioms that might come out of it (https://blog.perfects.engineering/go_range_over_funcs is a good read).

One somewhat eccentric use of nested iterators I built in the past in python was turning an SQL widerows or domain aggregate result into a set of nested objects, so one could use the results in something like the following way:

for p in people:
for c in p.cars:
for t in c.tickets:
print("person {} in car {} got ticket {}", p, c, t)

While I was able to get a very janky version of this type of behaviour with https://go.dev/play/p/gFUcKNSrbMV?v=gotip this only has an iterator on the left hand side and series of nested structs through slices. My attempts to use more iterators (for cars and tickets) fails as these of course stop after the first set of cars and tickets respectively have been yielded.

I realise this is a contrived example, but I wonder if there might be more general cases where iterators could be stopped and restarted. (The docs to iter.Pull suggest next() and stop() are non-resettable also.) Perhaps there could be hidden new iter.Seq constructors in the container for when the cars and tickets iterators are exhausted...hmm...

I'd be grateful for any thoughts about this casual and hypothetical case, although I guess it could be helpful for something like retrieving nested data from an sql cursor efficiently.

Cheers
Rory


That code above turns:

a a1 a2 b1 b2 b3 c1 c2 c3
a a1 a2 b1 b2 b3 c4 c5 c6
a a1 a2 b1 b2 b3 c7 c8 c9
a a1 a2 b4 b5 b6 c10 c11 c12
d d1 d2 e1 e2 e3 f1 f2 f3
d d1 d2 e1 e2 e3 f4 f5 f6
g g1 g2 h1 h2 h3 i1 i2 i3

into:

[a a1 a2]
> [b1 b2 b3]
> > [c1 c2 c3]
> > [c4 c5 c6]
> > [c7 c8 c9]
> [b4 b5 b6]
> > [c10 c11 c12]
[d d1 d2]
> [e1 e2 e3]
> > [f1 f2 f3]
> > [f4 f5 f6]
[g g1 g2]
> [h1 h2 h3]
> > [i1 i2 i3]

called like this:

for a := range collection.Iter() { // iter.Seq
fmt.Println(a.r)
for _, b := range a.s { // slice -- could be iter.Seq?
fmt.Println("> ", b.r)
for _, c := range b.s { // slice -- could be iter.Seq?
fmt.Println("> > ", c.r)
}
}
}

Rory Campbell-Lange

unread,
May 31, 2024, 10:11:00 AM5/31/24
to golang-nuts
On 23/05/24, Rory Campbell-Lange (ro...@campbell-lange.net) wrote:
> I've been playing with with rangefunc experiment...

...reference to python nested yield example...

> for p in people:
> for c in p.cars:
> for t in c.tickets:
> print("person {} in car {} got ticket {}", p, c, t)
>

> [My attempt to recreate in go at] https://go.dev/play/p/gFUcKNSrbMV?v=gotip ... only has an iterator on the left hand side and series of nested structs through slices...

For info, in my attempt to use nested iter.Seq structures I (obviously) didn't need a restart, but simply yielded the left-most struct and used the iterators in each nested struct as required. I've got this sort of working as shown below -- sorry if my plain text email formatting gets lost in translation.

type obj[T comparable, U any] struct {
this T
those []U
}

func (o *obj[T, U]) Add(u U) {
o.those = append(o.those, u)
}

func (o *obj[T, U]) Eq(n T) bool {
return o.this == n
}

// Iter could take args for customization
func (o *obj[T, U]) Iter() iter.Seq[U] {
if debug {
fmt.Printf("calling Iter() on %T\n", o.those)
}
return func(yield func(U) bool) {
for _, v := range o.those {
if !yield(v) {
return
}
}
}
}

It's painful to work with so probably not worth the effort. See:
https://go.dev/play/p/PpKpAIz7u6J?v=gotip

Example output from

a1 a1a a1b 1.1 1.2 1.3 n1 n1a n1b
a1 a1a a1b 1.1 1.2 1.3 n2 n2a n2b
a1 a1a a1b 1.1 1.2 1.3 n3 n3a n3b
a1 a1a a1b 2.1 2.2 2.3 n4 n4a n4b
a2 a2a a2b 3.1 3.2 3.3 n5 n5a n6b
a2 a2a a2b 3.1 3.2 3.3 n6 n5a n6b

is as follows:

{a1 a1a a1b}
calling Iter() on []main.obj[main.b,main.c]
> {1.1 1.2 1.3}
calling Iter() on []main.c
> > {n1 n1a n1b}
> {1.1 1.2 1.3}
calling Iter() on []main.c
> > {n1 n1a n1b}
> > {n2 n2a n2b}
> {1.1 1.2 1.3}
calling Iter() on []main.c
> > {n1 n1a n1b}
> > {n2 n2a n2b}
> > {n3 n3a n3b}
> {2.1 2.2 2.3}
calling Iter() on []main.c
> > {n4 n4a n4b}
{a2 a2a a2b}
calling Iter() on []main.obj[main.b,main.c]
> {3.1 3.2 3.3}
calling Iter() on []main.c
> > {n5 n5a n6b}
> {3.1 3.2 3.3}
calling Iter() on []main.c
> > {n5 n5a n6b}
> > {n6 n5a n6b}

Rory Campbell-Lange

unread,
May 31, 2024, 10:55:50 AM5/31/24
to golang-nuts
On 31/05/24, Rory Campbell-Lange (ro...@campbell-lange.net) wrote:
> On 23/05/24, Rory Campbell-Lange (ro...@campbell-lange.net) wrote:
> > I've been playing with with rangefunc experiment...
>
> ...reference to python nested yield example...
>
> > for p in people:
> > for c in p.cars:
> > for t in c.tickets:
> > print("person {} in car {} got ticket {}", p, c, t)
> >
>
> > [My attempt to recreate in go at] https://go.dev/play/p/gFUcKNSrbMV?v=gotip ... only has an iterator on the left hand side and series of nested structs through slices...
>
> For info, in my attempt to use nested iter.Seq structures I (obviously) didn't need a restart, but simply yielded the left-most struct and used the iterators in each nested struct as required.

Apologies -- the code I meant to share for an example of nested iter.Seq structs is here: https://go.dev/play/p/8QZmybr_nOB?v=gotip
Reply all
Reply to author
Forward
0 new messages