laurent.orseau
2021-11-15 08:09:15

I don’t see it exported from utils-and-no-gui.rkt, which only exports the typed Plot-Metrics<%> . But otherwise it sounds like a case of double instantiation, once in gui the other in no gui. This might need to be resolved within plot rather than on import, but I’m not sure.


alexharsanyi
2021-11-15 09:18:28

Have you looked at the contents of the linked PR? It is not exported in the main codebase, but I exported it as part of the Pull Request I linked — unfortunately that does not work.


laurent.orseau
2021-11-15 09:27:34

Hm, sorry for being slow. I was reading this quickly on my phone and that’s not ideal for PRs. Ok, I see better what you mean now. Does it fail too if you use (except-in plot plot-metrics<%>)in the pr90.rkt test?


badkins
2021-11-15 15:27:48

Wow - thx :)


cperivol
2021-11-15 15:58:37

Is there a library that implements stream-partition (like the partition in racket/list )


cperivol
2021-11-15 15:58:40

?


badkins
2021-11-15 17:05:04

(define (stream-partition pred s) (values (stream-filter pred s) (stream-filter (compose not pred) s)))


badkins
2021-11-15 17:05:48

A more complete example: #lang racket (define s (stream 1 2 3 4 5 6 7 8 9 10)) (define (stream-partition pred s) (values (stream-filter pred s) (stream-filter (compose not pred) s))) (let-values ([ (s1 s2) (stream-partition odd? s) ]) (for ([ n (in-stream s1) ]) (printf "~a" n)) (printf "\n") (for ([ n (in-stream s2) ]) (printf "~a" n)))


cperivol
2021-11-15 17:11:06

That is not ideal unfortunately… I would prefer that pred is called once per element as it might be expensive and may have side effects


badkins
2021-11-15 17:12:03

I see. What would you expect the API to be then? In other words, can you provide an example use of your ideal stream-partition ?


cperivol
2021-11-15 17:17:48

It’s not easy… I had in mind something like haskell’s partition ( https://www.stackage.org/haddock/lts-18.17/base-4.14.3.0/src/Data-OldList.html#partition ) but one would probably need to make use of promises or something like that to implement it in racket and I am not very fluent with those myself


badkins
2021-11-15 17:30:42

Consider a stream of integers that you want to partition into even? and (not even?). If the stream contains all odd? integers, then to compute stream-empty? on the even? stream will require consuming the entire original stream, right?


cperivol
2021-11-15 17:33:08

yes


samdphillips
2021-11-15 17:34:07

I think in the general case (like a lot of other stream functions) you just state that it won’t terminate in cases like that.


badkins
2021-11-15 17:34:09

My hunch is you’d want to create something specific to your use case, since it appears a general stream solution would have some undesirable performance characteristics.


badkins
2021-11-15 17:35:15

@samdphillips but if the requirement is to only evaluate the predicate once per element, then it seems you’d be forced to cache the entire stream, right?


badkins
2021-11-15 17:35:23

(in worst case)


samdphillips
2021-11-15 17:35:54

Since stream is a generic interface/property a custom partitioned stream is probably what you want. The most direct implementation that I can think of right now would be to use a thread and async channels.


samdphillips
2021-11-15 17:35:59

@badkins yes


badkins
2021-11-15 17:36:37

Might as well convert the stream to a list and partition that then.


samdphillips
2021-11-15 17:36:39

If you used async channels with a limit you could do some sort of early termination (because the buffer filled)


samdphillips
2021-11-15 17:38:26

re: thread impl; since you could do this with threads there is probably a continuation based solution that could be performed.


cperivol
2021-11-15 17:38:28

so the function I have in mind has the following behavior

> (let-values ([(x y) (stream-partition (lambda (x) (displayln "prop") #f) '(1 2 3)]) (displayln (stream-empty? x)) (displayln (stream-empty? y))) prop prop prop #t #f


cperivol
2021-11-15 17:38:46

(screwed up the parens but I hope it makes sense)


cperivol
2021-11-15 17:39:33

I think @samdphillips is right


badkins
2021-11-15 17:45:38

I’d be curious how a solution using threads compares to: (define (stream-partition pred s) (let*-values ([ (lst) (stream->list s) ] [ (l1 l2) (partition pred lst) ]) (values (in-list l1) (in-list l2))))


badkins
2021-11-15 17:47:38

Probably depends a lot on the distribution of pred? and (not pred?)


samdphillips
2021-11-15 17:50:42

Also how long the list is.


samdphillips
2021-11-15 17:52:02

I would probably only use it if I had a lot of external data coming in via a sequence or stream, and was either accumulating it or just keeping a small window.


badkins
2021-11-15 17:53:49

Seems like providing a “true” and “false” callback would be the way to go typically, unless you need this to be used specifically as a stream for other purposes.


badkins
2021-11-15 17:54:06

Anyway, I think we’ve plumbed the depths :)


greg
2021-11-15 17:59:47

Yep a lot depends on the “source”/"producing" side, as well as the “destination”/"consuming" side of whatever problem you’re working on. I think it’s hard to say without more context.


greg
2021-11-15 18:00:50

As a handy-wavy comment, I’ve really come to appreciate Racket channels (async and plain) as well as its synchronization primitives. When I must deal with concurrency. Or prefer to use concurrency to make something easier to express.


greg
2021-11-15 18:01:48

A not-horrible template for quite a few things seems to be: (define ch (make-channel)) ...+ (thread (λ () (for ([v (in-producer ___)]) (channel-put ch) ...+))) ...+ (thread (λ () (sync ch ...+)))


greg
2021-11-15 18:02:10

where in-producer could be something like in-port etc.


greg
2021-11-15 18:02:57

And where the sync might wrap the channel event(s) with things like handle-evt or whatever.


samdphillips
2021-11-15 18:19:07

samdphillips
2021-11-15 18:20:38

I probably could have made it simpler with a promise instead of managing the mutable state.


samdphillips
2021-11-15 18:31:56

Ok I wrote a version with promises. It’s a little simpler. (same url)


cperivol
2021-11-15 18:51:11

Nice! Thanks @samdphillips!


badkins
2021-11-15 22:32:38

@samdphillips in your first version, am I correct in observing that stream-partition will immediately consume the entire original stream and place the contents into the two async channels?


badkins
2021-11-15 22:38:43

And if so, is it also possible that the splitter thread will do that before the two streams are returned in the values expression?


samdphillips
2021-11-15 22:43:42

Oh yeah I didn’t intend it to consume the whole stream eagerly. In fact there should be a more complicated protocol to handle that.


samdphillips
2021-11-15 22:44:40

@badkins Excellent Catch


badkins
2021-11-15 22:46:38

Can you query an async channel to see if it’s empty? If so, you could pause (sync?) in that case i.e. just make sure both channels have at least one unconsumed value ?


badkins
2021-11-15 22:49:50

Hmm.. doesn’t look like that’s possible.


samdphillips
2021-11-15 22:49:53

async-channel-try-get


samdphillips
2021-11-15 22:50:25

As long as your stream doesn’t contain #f :stuck_out_tongue:


badkins
2021-11-15 22:52:31

Yeah, but won’t that also remove the value from the channel?


samdphillips
2021-11-15 22:59:25

So you only do that when the client stream needs the “current” element. So it would do something like: (cond [(async-channel-try-get ach) => values] [else (channel-put req-ch #t) ;; signal to splitter that this stream needs an element, and wait for one in the ach (async-channel-get ach)])


samdphillips
2021-11-15 23:03:37

Ugh there is also a bug with getting stream-rest in that implementation


samdphillips
2021-11-15 23:17:45

As I am reworking this “example” I would definitely not do it this way in real code. I would just go straight to channels and not deal with streams.


samdphillips
2021-11-15 23:28:58

The <https://gist.github.com/samdphillips/974df804ca07d8a5323d2a67d8613fcc#file–00_stream-partition-rkt|first module in the gist> now correctly handles stream-rest and has a simple “more-items-please” protocol to avoid eagerly consuming the source stream.


samdphillips
2021-11-15 23:30:12

It probably has a million edge cases, and will probably eat your cat if the partitioned streams are used from multiple threads.


jaz
2021-11-16 03:31:01

Other options: One simple way would be to memoize the predicate and use @badkins’s version with stream-filter: (define (memoized-predicate p?) (define memo-pad (make-hash)) (λ (x) (hash-ref memo-pad x (λ () (define val (p? x)) (hash-set! memo-pad x val) val)))) (define (stream-partition p? s) (define p?* (memoized-predicate p?)) (values (stream-filter p?* s) (stream-filter (λ (x) (not (p?* x))) s))) And here’s a version that doesn’t memoize the predicate (since space usage is uncontrolled with the former example). Instead, you memoize each step of the iteration with a promise: (define (stream-partition p? s) (cond [(stream-empty? s) (values empty-stream empty-stream)] [else (define s0 (stream-first s)) (define s* (stream-rest s)) (define promise (delay (stream-partition p? s*))) (define-syntax-rule (yes) (let-values ([(xs _) (force promise)]) xs)) (define-syntax-rule (no) (let-values ([(_ ys) (force promise)]) ys)) (cond [(p? s0) (values (stream-cons s0 (yes)) (no))] [else (values (yes) (stream-cons s0 (no)))])]))


rokitna
2021-11-16 03:35:19

What about this approach?

  (define checked-s
    (for/stream ([elem (in-stream s)])
      (list (pred elem) elem)))
  (define trues
    (for/stream ([entry (in-stream checked-s)]
                 #:when (first entry))
      (second entry)))
  (define falses
    (for/stream ([entry (in-stream checked-s)]
                 #:unless (first entry))
      (second entry)))
  (values trues falses))

I’d like to make this a bit more complex to support multiple-valued streams, but it seems like it should manage to avoid forcing the whole stream. (I haven’t tested it….)


jaz
2021-11-16 03:35:21

Oh, well, I “simplified” that second version a bit right before I posted it, but the simplification isn’t correct, since the promise is forced outside of a stream-cons


jaz
2021-11-16 03:35:44

If you add back in the stream-lazys that I removed… (define (stream-partition p? s) (cond [(stream-empty? s) (values empty-stream empty-stream)] [else (define s0 (stream-first s)) (define s* (stream-rest s)) (define promise (delay (stream-partition p? s*))) (define-syntax-rule (yes) (let-values ([(xs _) (force promise)]) xs)) (define-syntax-rule (no) (let-values ([(_ ys) (force promise)]) ys)) (cond [(p? s0) (values (stream-cons s0 (yes)) (stream-lazy (no)))] [else (values (stream-lazy (yes)) (stream-cons s0 (no)))])]))


jaz
2021-11-16 03:44:37

@rokitna I like that idea


marsmxm
2021-11-16 05:25:03

Hi,

Recently I found that the https://pkgs.racket-lang.org/package/dracula\|dracula package (for acl2 development) had failed compiling since v8.0, because of the rewrite of htdp-lib/test-engine. I’ve been trying to fix this issue, but am not very familiar with the library of test-engine, especially the high level design behind this rewrite (for instance, I found test-engine/scheme-gui and test-engine/test-info had been removed, but didn’t know which alternatives to use).

So is there any document I could refer to please? Thanks!