Strange readline/racket-mode behavior

Skip to first unread message

Winston Weinert

Sep 24, 2021, 3:01:37 PMSep 24
Hey everyone,

I was working on a procedure to prompt the user for confirmation and found
something a bit strange - it did not appear to read for input when usingt
"racket -i" or in the Emacs Racket REPL buffer. Here is the code:

(define (yn #:read-one-char? [read-one-char? #f])
(display "y/n: ")
(flush-output (current-output-port))
(if read-one-char?
(match (read-char)
[(or #\y #\Y) #t]
[m #f])
(match (read-line)
[(or "y" "Y") #t]
[m #f])))

Regardless if I use read-char or read-line and type y or Y, the behavior seeims
similar, each of the match's second clause is followed because (read-char)
returns #\newline whereas read-line returns "" (empty string).

I mentioned this strange behavior on the Libera chat and got an enlightening
response from tonyg outlining what is happening:

> at the repl, if you type "(read-char)" and press enter, the reader sees ( r e
> a d - c h a r ) NEWLINE. When it gets to the close-parenthesis, it executes
> the expression, which reads the NEWLINE character and returns. Similar for
> read-line, where it sees NEWLINE and returns an empty line
> try typing (list (read-line) (read-line))

I can confirm tonyg's solution works. The (list (read-char) (read-char)) also
appears to work, though one always has to type a final newline to send the
line-buffered input.

The behavior feels very surprising and took me a bit of time to figure out,
even then I didn't really understand it so I had to ask for help. Can this
behavior be changed in a future release? Is a reasonable request or could this
break a lot of code? If this could break stuff, is it worth doing changing it
anyway, so the behavior is less surprising? I hope to understand if it's
agreeable to make a PR for this change before investing the time.

Keep on Racketing!

Winston Weinert

David Storrs

Sep 24, 2021, 3:11:54 PMSep 24
to Winston Weinert, Racket Users
The dev team will have to answer your actual question, but I thought I might offer a more compact solution that incorporates the fix you mentioned:

(define (yn #:read-one-char? [read-one-char? #f])
  (display "y/n: ")
  (flush-output (current-output-port))
  (define func (if read-one-char? read-char read-line))
  (match (list (func) (func))
    [(list _ (or #\y #\Y "y" "Y")) #t]
    [_ #f]))

You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to
To view this discussion on the web visit

Tony Garnock-Jones

Sep 28, 2021, 4:43:29 AMSep 28
to Racket Users
Hi Winston,

I've submitted a suggested improvement to interactive REPLish behaviour around read-char and read-line here:

Please let me know if it improves the situation for you. And of course, if anyone else has comments about this kind of thing, please let me know!

Here's the commit comment explaining a little more of the situation:

`winny` on IRC (and subsequently on the mailing list [1]) remarked
that, at the REPL,

Welcome to Racket v8.2.0.8 [cs].
> (read-line)

which is surprising, since it didn't appear to wait for a line of

Guile does this differently, with its `read-eval-print-loop`
apparently consuming any whitespace after the `read` expression and
before starting the `eval`.

This patch performs a similar trick. In *interactive* contexts
(namely, by action of the default `current-read-interaction`), if
`read-syntax` answers non-`eof`, a procedure
`discard-line-terminators` peeks for and consumes a *single* CR, LF or
CRLF line terminator.

Non-interactive contexts are not affected.

This is very much a special-case in order to improve user experience:
I feel like, because it's an amendment to the REPL and the top-level
is hopeless [2], it's fair game to introduce exceptional handling like


Reply all
Reply to author
0 new messages