sschwarzer
2021-6-5 10:09:37

I’m trying to figure out, so far without success, how the multiple id form of a for loop works. I read https://docs.racket-lang.org/reference/for.html and https://docs.racket-lang.org/guide/for.html

The reference says "[…] the sequence produced by a seq-expr must return as many values for each iteration as corresponding ids." How does a sequence return anything? :thinking_face:

Anyway, I tried (for ([(i j) '((1 2) (3 4))]) (display (list i j))) which gives me result arity mismatch; expected number of values not received expected: 2 received: 1 in: local-binding form arguments...: and I tried (for ([(i j) (list (values 1 2) (values 3 4))]) (displayln (list i j))) but in DrRacket that highlights the (list ...) and gives me the message result arity mismatch; expected number of values not received expected: 1 received: 2 which I can’t interpret in any helpful way.

Then, the documentation gives the example (for ([(i j) #hash(("a" . 1) ("b" . 20))]) (display (list i j))) which prints key/value pairs, but I don’t know how the hash interacts with the for loop. Since #hash(...) by itself is just the hash, the use in for just looks like “here’s some magic you can do with a hash in a for loop.” :wink:

What would most likely help me is how in-my-list would need to look in the following example: (define my-list '((1 2) (3 4))) (define (in-my-list lst) ; What to put here? ...) ; Displays `'(1 2)` and `'(3 4)` (for ([(i j) (in-my-list my-list)]) (display (list i j)))


rokitna
2021-6-5 11:59:53

I think this would work:

(define (in-my-list lst) (sequence-map values lst))


rokitna
2021-6-5 12:03:00

Oops, I think it needs to be:

(define (in-my-list lst) (sequence-map (lambda (entry) (apply values entry)) lst))


rokitna
2021-6-5 12:05:52

A Racket sequence or Racket stream, unlike a list or most other collections, can have elements which consist of multiple values. The elements of a stream can be computed on demand, and the behavior that computes an element can end up computing multiple values for that element.


sschwarzer
2021-6-5 12:09:56

Thanks! Yes, I now see that (list (values 1 2) (values 3 4)) as such gives me an “arity mismatch.” Different from what I had assumed, it didn’t have anything to do with the for context.


rokitna
2021-6-5 12:13:57

Yeah, the result of (values 1 2) is two values, but function arguments (and almost everything else) have to be one value. Racket treats this similarly to passing two arguments to a function when only one is expected, hence the arity error.


sschwarzer
2021-6-5 12:18:12

To derive a “recipe” from your answer, if I have a list where each item is a sub-list representing the values of a sequence item, I can define (define (list->sequence lst) (sequence-map (lambda (entry) (apply values entry)) lst))


sschwarzer
2021-6-5 12:18:58

It’s not as intuitive as I had hoped, but it certainly helps. :slightly_smiling_face:


greg
2021-6-5 13:13:28

IIUC with this unfortunately you’ll traverse the whole outer list once with sequence-map, and again with for?


greg
2021-6-5 13:13:56

Myself, instead I’d probably use match-define in the body of the for to destructure each entry. Like

(for ([entry (in-list lst)]) (match-define (list i j) entry) ___)


greg
2021-6-5 13:14:28

It would be neat if for forms let you use a match expression as the lhs.


greg
2021-6-5 13:15:00

Something like (for ([(match-define i j) (in-list lst)]) ___)


greg
2021-6-5 13:15:18

Or something even less verbose than that.


greg
2021-6-5 13:16:08

Unfortunately I don’t think it can be quite as concise as you were (understandably) hoping: (i j). :slightly_smiling_face:


sschwarzer
2021-6-5 13:38:16

> IIUC with this unfortunately you’ll traverse the whole outer list once with sequence-map, and again with for? The documentation of sequence-map says: > Returns a sequence that contains f applied to each element of s. The new sequence is constructed lazily. So my understanding is you’d only traverse once.

By the way, if it wasn’t for this sentence in the documentation, my follow-up question would have been what I have to do in case of infinite sequences :wink: , but this seems already to be covered by sequence-map.


sschwarzer
2021-6-5 13:43:27

match-define looks interesting. Could be that I saw it at some point, but Racket has so many small functions and forms for similar things that I keep forgetting about these over and over. :confused:


sschwarzer
2021-6-5 13:46:19

> Myself, instead I’d probably use match-define in the body of the for to destructure each entry. Like If there hadn’t been a sufficiently succinct solution, I also might have gone with just iterating over a list and deconstructing it somehow. But I also thought about making the binding implicit. I first thought of a macro and then saw that for already supported more than one id for the “iteration variable(s)”, and this caused this thread. :slightly_smiling_face:


sschwarzer
2021-6-5 13:58:56

Sorry for ranting, but my impression is that the multiple values concept complicates so many things. As an example from this thread, iterating over a list with “single values” per item is super-simple (as it should be), but iterating over several values per item is completely different. I wonder if it wouldn’t have been better to just use lists to combine multiple values and have an easy way to “destructure” them, as with match-define. This would seem like a more orthogonal design to me.


rostislav.svoboda
2021-6-5 14:48:30

Is there any “tolerant” implementation of the take function (& friends)? I mean (take '(0 1 2) 5) fails cause the list doesn’t have 5 elements. E.g. in clojure (take 10 '(1 2 3)) returns (1 2 3) …, however the rackjure package doesn’t have it and IMO re-implementing my own wheel is just pointless, even if it’s just two or three LOC.


massung
2021-6-5 14:57:27

There’s no included version, sadly. Usually what I do is use a for loop:

(for/list ([x lst] [_ (range n)]) x)


xlambein
2021-6-5 15:21:37

@xlambein has joined the channel


greg
2021-6-5 15:58:21

I don’t know the history but: I think one motivation was things like hash-tables and dictionaries where each element has known multiple values (the key and the value) so it is handy to write [(k v) (in-hash ht)]. Another motivation was the for and in-hash syntax would expand to code as performant as what you’d write by hand. Using multiple values means a list isn’t allocated to stuff them into… only to take them right out again.


greg
2021-6-5 15:58:54

Someone else could probably explain the history better, and, explain this in a more principled way, but that’s my understanding.


xlambein
2021-6-5 16:25:36

Hey everyone, I was curious about something: how does a module print out all its top-level expressions? I saw in the docs how to en/disable that behaviour, but I can’t find the details of how it’s actually performing this.


greg
2021-6-5 16:31:17

mildc055ee
2021-6-5 16:33:25

@mildc055ee has joined the channel


xlambein
2021-6-5 16:35:00

Thanks! Do you know where I can find the source code of #%module-begin for racket/base?


xlambein
2021-6-5 16:35:58

Or of any link in the docs, actually. Is there a way to “go to source” from a doc page?


xlambein
2021-6-5 16:46:50

I couldn’t find my way around the racket/base source, but I managed to dig it up in the scheme source. For those interested, here’s a wrapper that adds a print statement to each expression: https://github.com/racket/racket/blob/448b77a6629c68659e1360fbe9f9e1ecea078f9c/racket/collects/racket/private/modbeg.rkt


greg
2021-6-5 16:46:50

AFAIK in general there’s not a “go to source” from docs.


greg
2021-6-5 16:46:56

greg
2021-6-5 16:47:12

Yes that looks like it.


xlambein
2021-6-5 16:47:16

Yeah that’s what I usually try to do, but the source can be really hard to navigate


greg
2021-6-5 16:47:54

Also you can use raco expand or the Macro Stepper in Dr Racket to see how things expand. Sometimes how they expand is as interesting (or more) than seeing the macro source code.


xlambein
2021-6-5 16:48:33

oh yep maybe that would’ve been a good idea! I’ll try that next time, thanks for your help!


greg
2021-6-5 16:49:26

If you’re never used the macro stepper, and/or if you’re interested in questions like the one you asked, it can be interesting to take a super simple .rkt program and expand that.


greg
2021-6-5 16:49:40

Even a program like #lang racket/base 42 :slightly_smiling_face:


greg
2021-6-5 16:51:51

The docs at https://docs.racket-lang.org/reference/module.html say > No identifier can be imported or defined more than once at any phase level within a single module, except that a definition via define-values or define-syntaxes can shadow a preceding import via #%require; unless the shadowed import is from the module’s initial module-path, a warning is logged to the initial logger. Is that still true?

If I PLTSTDOUT=warning racket foo.rkt and foo.rkt is (say) #lang racket/base (require net/url) (define get-pure-port 42) get-pure-port I don’t see any warning? Nor when I use PLTSTDOUT=debug and look through all of that output.


greg
2021-6-5 16:52:42

This isn’t of any practical importance to me. I just noticed it and wondered if the doc needs to be updated, or I’m doing it wrong, or whatever. Just curious.


xlambein
2021-6-5 16:53:53

I’ve used it a couple of times, but I have yet to understand in which situations it can be useful. I’ll try expanding a small program to see, that sounds interesting!


mflatt
2021-6-5 17:14:57

I think you’re right that a warning is no longer logged. (I don’t remember it ever being logged, but my lack of memory merely suggests how the current expander ended up not logging anything.)


samth
2021-6-5 17:55:09

There are two questions here: why do multiple values exist, and why does the for family provide support for them the way it does


samth
2021-6-5 17:56:06

The answer to the first question is that it is mostly about efficiency — you don’t have to allocate a data structure for the multiple values


samth
2021-6-5 17:56:53

But also it adds symmetry in the presence of continuations — multiple values is like calling the continuation with multiple arguments


samth
2021-6-5 17:57:22

Given the existence of multiple values, they’re sort of a natural thing to add to for


samth
2021-6-5 17:57:41

And they let you avoid some inefficiency in things like hash iteration


samth
2021-6-5 17:58:34

I think a different design featuring pervasive pattern matching, built in tuples, and a compiler that could eliminate intermediate tuples is potentially better


samth
2021-6-5 17:58:56

But that’s a quite different language and wasn’t the choice made 30+ years ago


samth
2021-6-5 18:04:55

I don’t remember any logging


samth
2021-6-5 18:05:16

Racket in general doesn’t have a great story for warnings


ben.knoble
2021-6-5 18:38:38

This was given to me when I was interested in inspecting modules from other code: https://gist.github.com/shhyou/726d0542aef45b13981173a334a0202c


sschwarzer
2021-6-5 19:04:40

@samth Thanks for the info. I feel less grumpy now. :slightly_smiling_face: