[racket] racket/sandbox, read-eval-print-loop, and #%top-interaction
So I got a bug report that Typed Racket type info is appearing twice,
in the REPL for my emacs racket-mode:
https://github.com/greghendershott/racket-mode/issues/21
All the relevant code is there, but I'll do a quick recap in this post.
Given this sample program:
#lang racket/base
(require racket/sandbox)
;; 1. Run this.
;; 2. In the resulting REPL, enter "1".
;; Result: The type information is printed twice:
;; - : Integer [more precisely: One]
;; - : Integer [more precisely: One]
;; 1
(call-with-trusted-sandbox-configuration
(lambda ()
(parameterize ([current-namespace (make-empty-namespace)]
[sandbox-input (current-input-port)]
[sandbox-output (current-output-port)]
[sandbox-error-output (current-error-port)])
(define e (make-evaluator 'typed/racket)) ;; [^note]
(parameterize ([current-eval e])
(read-eval-print-loop)))))
;; [note]: Same behavior instead using make-module-evaluator:
;; (define e (make-module-evaluator '(module m typed/racket 1)))
You get this kind of interaction.
> 1
- : Integer [generalized from One]
- : Integer [generalized from One]
1
Commenting out the sandbox-output param, so that output goes nowhere,
would give you this instead:
> 1
1
IOW the REPL prints the result, but Typed Racket's #%top-interaction
prints the type through a `display` side-effect. (Which is reasonable,
and what any similar #%top-interaction would do, IIUC.)
After lots of code spelunking and experimentation, I discovered that
using an alternative to read-eval-print-loop gets the desired
behavior:
(define (alternative-read-eval-print-loop)
(let repl-loop ()
;; This prompt catches all error escapes, including from read and print.
(call-with-continuation-prompt
;; 1. proc
(lambda ()
(let ([v ((current-prompt-read))])
(unless (eof-object? v)
(call-with-values
(lambda ()
;; This prompt catches escapes during evaluation.
;; Unlike the outer prompt, the handler prints
;; the results.
(call-with-continuation-prompt
(lambda ()
((current-eval) v) ;; <== JUST DO THIS, NOT THE FOLLOWING...
;; (let ([w (cons '#%top-interaction v)])
;; ((current-eval) (if (syntax? v)
;; (namespace-syntax-introduce
;; (datum->syntax #f w v))
;; w)))
)))
(lambda results (for-each (current-print) results)))
;; Abort to loop. (Calling `repl-loop' directory would not
be a tail call.)
(abort-current-continuation (default-continuation-prompt-tag)))))
;; 2. prompt-tag
(default-continuation-prompt-tag)
;; 3. handler
(lambda args (repl-loop)))))
After puzzling why, finally I noticed in sandbox.rkt a reference to
#%top-interaction that I'd overlooked previously:
https://github.com/plt/racket/blob/533a2f21f864a25e27e744ef2fef04343e1d02e4/pkgs/sandbox-lib/racket/sandbox.rkt#L858
~~~~~~~~~~~~~~~~~~~
So: I _think_ I've found a reasonable work-around -- use my
alternative-read-eval-print-loop with racket/sandbox.
However I wanted to share this in case any Racket core devs might want
to advise, "Yeah, no, you really don't want to do that, and here's
why...". Anyone?