christos.perivolaropo
2022-2-3 13:50:19

I am trying to understand generators and I can’t quite understand the following:

> (define gen (generator () (map yield '(a b c)))) > (gen) 'a > (gen) ; result arity mismatch; ; expected number of values not received ; expected: 1 ; received: 0 ; Context (plain; to see better errortrace context, re-run with C-u prefix): ; .../private/map.rkt:40:19 loop


christos.perivolaropo
2022-2-3 13:50:26

Can someone help?


sorawee
2022-2-3 13:52:43

Try (define gen (generator () (for-each yield '(a b c))))


soegaard2
2022-2-3 13:54:00

I think the issue is this (last line in the docs): > The eventual results of _body_ are supplied to an implicit final https://docs.racket-lang.org/reference/Generators.html#%28def._%28%28lib._racket%2Fgenerator..rkt%29._yield%29%29\|yield; after that final https://docs.racket-lang.org/reference/Generators.html#%28def._%28%28lib._racket%2Fgenerator..rkt%29._yield%29%29\|yield, calling the generator again returns the same values, but all such calls must provide 0 arguments to the generator.


sorawee
2022-2-3 13:54:56

As for why map doesn’t work…

generator works both ways. So you can do something like this:

> (define gen (generator () (println (yield 1)))) > (gen) 1 > (gen 42) 42


sorawee
2022-2-3 13:55:30

Because it works both ways, when you use it inside map, it needs a value to be mapped.


sorawee
2022-2-3 13:55:38

So you can’t just invoke it with no argument


sorawee
2022-2-3 13:56:00

for-each doesn’t expect a value to be mapped, so it works fine.


christos.perivolaropo
2022-2-3 13:56:54

aha so the argumewnt of gen is the return value of yield


christos.perivolaropo
2022-2-3 13:56:56

ok


christos.perivolaropo
2022-2-3 13:57:08

makes sense


sorawee
2022-2-3 13:58:26

This aside, you probably shouldn’t use map anyway, since it constructs an output list, which you are not gonna use anyway.


christos.perivolaropo
2022-2-3 14:01:13

What I wanted to do is a frontier-based graph traversal where the user has the chance to modify the frontier at each step. Generators are even better for the job than I thought.


christos.perivolaropo
2022-2-3 14:03:32

The only thing that wories me slightly looking at the docs of racket/genrator is the lack of functions for combining generators. Are generators inherently not composable or is it something else?


sorawee
2022-2-3 14:04:15

you mean something like yield from in Python?


sorawee
2022-2-3 14:05:16

Personally, I just write:

(define (yield-from xs) (for ([x xs]) (yield x)))


sorawee
2022-2-3 14:05:50

The performance is probably going to be horrible


sorawee
2022-2-3 14:06:00

But for small programs it probably works fine


sorawee
2022-2-3 14:07:04

Speaking of graph traversal, have you looked at the graph package? I think their interface is pretty flexible, but I don’t know if it suffices your requirement


christos.perivolaropo
2022-2-3 14:07:12

Is generator performance generally crappy ?


sorawee
2022-2-3 14:07:45

Super crappy in Racket BC. Kinda OK in Racket CS. Still, I still had an old habit to avoid it whenever possible.


christos.perivolaropo
2022-2-3 14:08:38

I have looked at the graph package but my graph is actually an AND-OR DAG which is just different enough from the general graph interface that I decided it’s not worth it…


christos.perivolaropo
2022-2-3 14:09:38

I considered generators to be lightweight transducers, is it the other way round?


christos.perivolaropo
2022-2-3 14:10:10

or does this abstraction just have crappy performance whatever way you cut it?


sorawee
2022-2-3 14:14:21

I think you are right that generator is a lightweight transducer. I don’t have much experience with transducer, only knowing that it’s more expressive.

Generator’s poor performance in Racket BC is from how Racket implements it by using continuation, and the performance of it in Racket BC is not great.


christos.perivolaropo
2022-2-3 14:19:11

I don’t know what BDD is. I have this function that traverses a list that satisfies the contract (graph/c node/c ((set/c node/c) . -> . (set/c node/c)) . -> . void?) . The first arguments are a graph and the starting node and the third argument is a callback that the caller can use to modify the frontier at every step (eg by returning an empty frontier to stop the traversal, by pruning thew frontier, or traversing “hidden” edges, etc).


christos.perivolaropo
2022-2-3 14:19:42

In the callback the caller collects whatever information they need in the closure of the procedure


christos.perivolaropo
2022-2-3 14:21:04

I feel like there is probably a better way to do this so I was looking to, instead of accepting a callback argument, make the whole thing a generator


christos.perivolaropo
2022-2-3 14:21:44

(callback argument == yield)


massung
2022-2-3 15:10:24

One pattern I’m used to doing in CL that I can’t seem to emulate in racket (with for) is this (using Racket for as the example):

(for ([x xs] [r (rest xs)]) (if (null? r) ;; x is the last element of the list ;; x it not the last element )) This fails in racket since r terminates one element too soon. Is there a for pattern in racket I can use to accomplish the same result (without resorting to index tracking and comparing to length, which may be large and O(N) to compute)?


sorawee
2022-2-3 15:12:34

One very hacky solution is to use in-sequences to chain (rest xs) with a sequence of one element.


laurent.orseau
2022-2-3 15:12:52

Not sure if you’re testing the right thing with (null? r), since your clause is equivalent to [r (in-list (rest xs))]


sorawee
2022-2-3 15:13:19

Well, I think @massung meant a special value that can be recognized somehow


massung
2022-2-3 15:13:34

It’s an example, in CL the above would work fine since r would just be nil after being exhauseted


massung
2022-2-3 15:13:58

The in-sequences might work ok for me


laurent.orseau
2022-2-3 15:15:03

Personally I often use a custom in-list+rest (https://github.com/Metaxal/bazaar/blob/master/loop.rkt#L309). I think something like this should be standard


laurent.orseau
2022-2-3 15:15:58

Also in-zip (same file), where the parsed elements are pushed in reverse order to another list that is available, so you have [(left element right) (in-zip lst)]


laurent.orseau
2022-2-3 15:16:38

Then you know if you’re on the first element or the last or not.


sorawee
2022-2-3 15:17:06

It depends on what you want to do, but generally I dislike:

for (i in <e>) { if (p(i)) <do-A> else <do-B> } because you can split that into:

for (i in <e-A>) { <do-A> } for (i in <e-B>) { <do-B> } and it looks clearer to me.


laurent.orseau
2022-2-3 15:17:54

But the number of loops can explode if you have several conditions on several clauses


massung
2022-2-3 15:18:07

in this particular case, I basically want a (map add1 xs) to all elements except the last element of xs


laurent.orseau
2022-2-3 15:19:00

You can always resort to a let loop of course


massung
2022-2-3 15:19:08

obviously there are other good use-cases for the above, but this one is simple if anyone has a quick suggest :wink:


sorawee
2022-2-3 15:19:16

For this particular case, I personally would write:

(for/fold ([e #f]) ([x xs] [y (rest xs)]) <do-something-with-x-and-y> y)


massung
2022-2-3 15:19:22

currently a recursive is what i do


sorawee
2022-2-3 15:19:28

then use the value of for/fold, which is the last element, outside the loop


laurent.orseau
2022-2-3 15:20:37

@massung That would give (for ([(x rst) (in-list+rest xs)]) (if (null? rst) x (+ 1 x))) as you wished.


sorawee
2022-2-3 15:20:42

Then there’s no need to have a conditional inside the loop


massung
2022-2-3 15:21:17

All good suggestions I’ve thought about. Each of them - at the end, though - ends up being an append or reverse at the end. Maybe i’ll just suck it up tho


laurent.orseau
2022-2-3 15:21:38

reverse, split-at, map add1, cons, reverse :slightly_smiling_face:


massung
2022-2-3 15:21:50

yeah, was looking at it. That would definitely work for this case


sorawee
2022-2-3 15:22:35

Oh, I assumed that you are using for for effect, which you don’t need append or reverse.


sorawee
2022-2-3 15:23:08

But if you use for/list… you might want to build the list in a reversed order in for/fold and then reverse it yourself


massung
2022-2-3 15:23:13

I need (2 2 2) -> (3 3 2) basically


sorawee
2022-2-3 15:23:23

Note that this is exactly what for/list does under the hood


sorawee
2022-2-3 15:24:03

Ah, I just saw this.


massung
2022-2-3 15:25:25

To be clear, I totally have a working solution for my problem using a recursive function. I was just frustrated that this couldn’t be done (easily - from what I know) using for/x. So figured I’d see if anyone here had solved it using a for-construct.


greg
2022-2-3 15:25:58

Is it just me or is https://racket.discourse.group/ showing a “blank” page? With JavaScript enabled.


massung
2022-2-3 15:25:59

This is the ~3rd time in a week I’ve wanted a for loop that knew whether or not it was at the last element of the list


sorawee
2022-2-3 15:26:19

you surely meant disabled?


massung
2022-2-3 15:26:20

Blank for me


sorawee
2022-2-3 15:26:40

Oh… yeah, it’s blank for me too


greg
2022-2-3 15:26:41

Seems due to Uncaught Error: Could not find module `handlebars` imported from `discourse-common/lib/raw-handlebars` c _vendor-f4425d4b17932067d5c6d206656c04d9.js:22 c _vendor-f4425d4b17932067d5c6d206656c04d9.js:22 findDeps _vendor-f4425d4b17932067d5c6d206656c04d9.js:34 c _vendor-f4425d4b17932067d5c6d206656c04d9.js:22 findDeps _vendor-f4425d4b17932067d5c6d206656c04d9.js:34 c _vendor-f4425d4b17932067d5c6d206656c04d9.js:22 requireModule _vendor-f4425d4b17932067d5c6d206656c04d9.js:16 <anonymous> _start-discourse-81637605c9aa837e31086f280d3afdaa.js:18 <anonymous> _start-discourse-81637605c9aa837e31086f280d3afdaa.js:16 <anonymous> _start-discourse-81637605c9aa837e31086f280d3afdaa.js:7 _vendor-f4425d4b17932067d5c6d206656c04d9.js:22:30


sorawee
2022-2-3 15:26:54

RIP


sorawee
2022-2-3 15:27:28

lol


sorawee
2022-2-3 15:27:37

There’s no way to report the bug at https://meta.discourse.org/


sorawee
2022-2-3 15:27:40

because it’s also blank


greg
2022-2-3 15:28:21

It’s been awhile since I saw Inception but I think we need to elicit a sensation of falling to escape?


sorawee
2022-2-3 15:31:09

I’d be sad if I have to write:

(for/fold ([acc '()] [last-elem #f] #:result (reverse (cons last-elem acc))) ([x xs] [y (rest xs)]) (values (cons (add1 x) acc) y))


sorawee
2022-2-3 15:31:53

Plus it’s wrong when there’s only one element :face_palm:


massung
2022-2-3 15:33:54

Is there a version of for where it doesn’t terminate early but keeps iterating w/ default values for consumed sequences until all sequences are consumed?


massung
2022-2-3 15:34:13

I could see that being quite useful


sorawee
2022-2-3 15:34:31

I’ve been wanting that too


laurent.orseau
2022-2-3 15:37:59

in-cycle?


massung
2022-2-3 15:38:07

Not sure the difficulty in adding that functionality to for. I wonder if just an optional 3rd parameter to each iteration would work?

(define (zip/default xs ys) (for/list ([x xs #f] [y ys #f]) (cons x y)))


massung
2022-2-3 15:38:42

in-cycle repeats forever, so it’d never terminate, right?


laurent.orseau
2022-2-3 15:39:43

for will terminate as soon as the first sequence terminates


massung
2022-2-3 15:40:22

Right, but it’d be nice if we could make it not (w/o having to know which sequence is the shortest)


laurent.orseau
2022-2-3 15:40:23

so you would have a second sequence in parallel to in-cycle


laurent.orseau
2022-2-3 15:41:23

(in-sequences (in-list xs) (in-cycle (in-value #f))) should do what you want


laurent.orseau
2022-2-3 15:41:38

“after xs, cycle through #f”


laurent.orseau
2022-2-3 15:42:17

(edit: forgot in-value)


massung
2022-2-3 15:42:58

So, that would work perfectly for my original use case. In the general case, no, though, because it required ahead-of-time knowledge which sequence is longer.


massung
2022-2-3 15:43:39

I’m going to use your suggestion now, though, in place of my recursive function :wink:


laurent.orseau
2022-2-3 15:46:06

You can use (require bazaar/loop), but feel free to copy the code


djholtby
2022-2-3 17:05:05

You could If none of your sequences would normally contain #f then you could add a #:break that stops when all loop variables are #f (and if they might contain a #f then you can whip yourself up a unique sentinel by making a (list #f) and checking for it with eq? )



spdegabrielle
2022-2-3 17:47:35

Racket Discourse is back!


spdegabrielle
2022-2-3 17:48:19

It back now


ben.knoble
2022-2-3 18:32:21

(let ([xs* (reverse xs)]) (reverse (cons (car xs*) (map add1 (cdr xs*))))) But that won’t generalize. If the data at the end is somehow special, maybe putting it at the end of a linked list is the wrong choice?