By default read-line
has breaks enabled for me on Racket 7.4 on macOS:
$ racket -e '(read-line)'
^Cuser break
context...:
eval-one-top12
Maybe you’ve got a parameterize-break #f
somewhere in your code?
$ racket -e '(parameterize-break #f (read-line))'
^C^C^C^C^C^C^Casd
"asd"
Ha! I’m doing read-line
in a with-handler
’s error handler. That’s probably why…
racket -e '(with-handlers ([exn:fail? (thunk* (read-line))]) (error (quote test)))'
@monsieur.toulouse has joined the channel
@solrach has joined the channel
I touched on this in the beginners channel, but I think it’s a more general question: What are the advantages of having map
throw an exception when passed lists of unequal lengths vs. stopping when the shortest list arg is exhausted? I typically want the latter behavior, but maybe I’m missing some advantage.
It’s to prevent silent errors (which are the most difficult to find).
It seems like that error checking should be layered on top though. The current way precludes some niceties. I personally don’t recall ever wanting the current behavior, and if I wanted it, I’d expect to wrap map
with a function that checks the lengths.
for/list
with multiple sequences does what you want
@jaz my original motivation was to have the following work on unequal length lists: (define (zipn . lists)
(apply map list lists))
I’m guessing that it’s not possible to use for/list
in this way, right?
FWIW map
has behaved that way for ever. (At least since 1985 according to R2RS p. 58) https://dspace.mit.edu/bitstream/handle/1721.1/5600/AIM-848.pdf
I’d be fine w/ writing my own version of map
that works the way I want, but I think the built-in is optimized in a way that would be difficult for me to do.
@soegaard2 I agree that a long history is a reasonable factor in designing Racket’s map
. Clojure’s impl works the other way FWIW.
When I’m faced with this type of decision where there are two reasonable behaviors desired, I consider the relative difficulty of implementing A with B vs. B with A.
I suppose I’d have to determine the smallest list, then perform take
on all the lists before passing them to the original map
Expectations differ. In (map list '(a b) '(1 2 3))
there is no third element in ’(a b). Some language would use null
or false
to indicate a missing value.
I know it’s a subjective matter. My hunch is that more often folks would simply want to end when the smallest list is exhausted, but I have no data to back that up :slightly_smiling_face:
This (for/list ([x (in-list xs)] [y (in-list ys)]) (f x y))
stops when one of the lists xs and ys run out.
So the question is how fast it is, compared to (map f xs ys)
.
@soegaard2 yes, but see original motivation above - having for/list
be a macro is sometimes problematic
We can put the for/list away in a function.
Sure. Maybe we can “fix” it in Rhombus :wink:
@badkins Try running this. #lang racket
(define (map2 f xs ys)
(for/list ([x (in-list xs)]
[y (in-list ys)])
(f x y)))
(define xs (make-list 1000000 42))
(define ys (make-list 1000000 42))
(define (our-map)
(map2 + xs ys)
(void))
(define (org-map)
(map + xs ys)
(void))
(for ([_ 3]) (collect-garbage))
(time (our-map))
(for ([_ 3]) (collect-garbage))
(time (org-map))
I get very close results.
@soegaard2 doesn’t that lose the vararg aspect?
@soegaard2 why collect-garbage
three times? I see this in the code that I was working with as well
Throw on uneven by default, add a keyword argument to enable silently dropping extras
Well, no point in implementing something complicated, if the simple case is slower.
@sorawee I’ve always heard it’s related to generations
as in, a single major collection won’t necessarily collect all the garbage it possibly can, because some garbage might be pointed to by other garbage and a single collection will actually just promote it to a longer lived generation
If that is indeed the case, there really should be (collect-garbage 'generations)
or something equivalent.
Writing (collect-garbage)
three times is just absurd.
(collect-garbage 'major)
?
I thought 'major
is the default mode…. Let me see the docs again
you’re right
I wonder if it did a major collection prior to 6.3
When I need to do that for benchmarking purposes I just write a (ensure-garbage-collector-in-clean-state!)
function that does the three collections, to remind myself why the hell I need to write such bizarre code
Could also be related to finalization, ephemerons, or any of the things that complicate gc
(but I really don’t know)
yes, it’s because of those things
it’s of course possible to require arbitrarily many collections to reach a fixed point, but 3 seems like enough
of course, it doesn’t genuinely reach a fixed point either
Okay, here is a version that handles multiple lists as well. #lang racket
(define (map2 f xs ys)
(for/list ([x (in-list xs)]
[y (in-list ys)])
(f x y)))
(define (map3 f xs ys zs)
(for/list ([x (in-list xs)]
[y (in-list ys)]
[z (in-list zs)])
(f x y z)))
(define (map4 f xs ys zs ws)
(for/list ([x (in-list xs)]
[y (in-list ys)]
[z (in-list zs)]
[w (in-list ws)])
(f x y z w)))
(define (map** f xss)
(define done? (ormap empty? xss))
(if done?
'()
(cons (apply f (map car xss))
(map** f (map cdr xss)))))
(define (map* f . xss)
(match xss
[(list xs) (map f xs)]
[(list xs ys) (map2 f xs ys)]
[(list xs ys zs) (map3 f xs ys zs)]
[(list xs ys zs ws) (map4 f xs ys zs ws)]
[xss (map** f xss)]))
I really don’t think map
should accept multiple lists in the first place, and that a separate zip
function should do that.
But that’s another discussion entirely
FWIW, srfi/1’s map
has the behavior that @badkins wants
Maybe something to address in Rhombus?
Yup
@cboekell has joined the channel
The variant of map
from racket/base
is itself written in Racket: https://github.com/racket/racket/blob/0c6f50d57d1de42cf70e2c31069fb04ae43a5f55/racket/collects/racket/private/map.rkt#L33-L64
(define-syntax (map* stx)
(define-syntax-class lst
(pattern e:expr
#:with name (datum->syntax #'e (gensym 'map*))
#:with binder #'[name (in-list e)]))
(syntax-parse stx
[(_ f xs:lst ...+)
#'(for/list (xs.binder ...)
(f xs.name ...))]))
it is with macros :smile:
Didn’t work: #lang racket
(require (for-syntax syntax/parse)
syntax/parse/define)
(define-syntax (map* stx)
(define-syntax-class lst
(pattern e:expr
#:with name (datum->syntax #'e (gensym 'map*))
#:with binder #'[name (in-list e)]))
(syntax-parse stx
[(_ f xs:lst ...+)
#'(for/list (xs.binder ...)
(f xs.name ...))]))
(define numbers '(1 2 3 4))
(define strings '("foo" "bar" "baz"))
(define symbols '(a b c))
(define lists '((4 5 6) () (7 8)))
;(define (zipn . lists)
; (apply map list lists))
;(zipn strings symbols lists)
(define (zipn . lists)
(apply map* list lists))
(zipn numbers strings symbols lists)
The commented out code does work, but only with equal length list args.
I get a bad syntax error with your code.
Sorry, I should’ve been more clear. map*
is really your zipn
.
(map* list numbers strings symbols lists)
should work fine, but you can’t apply
a macro.