3
votes

Does racket have something like guile's procedure-source function, e.g.:

(define (plus-one x) (+ 1 x))
(procedure-source plus-one) --> (quote (+ 1 x))

I'm trying to make something akin to a debugging tool for beginning students, in which they can see partial or complete evaluations of a particular function while they play with its behavior in a 2htdp/universe. So I could use a macro for this case, except in their program I'd still like the definition to look exactly as usual, so I can't just have them quote it in the first place and eval it later, unless I redefine define... which might be okay, but I'd appreciate pointers on how best to do it.

3
You've got the right idea. First, though, I have to ask: have you tried DrRacket's Stepper? It does everything you're asking for, and then some.John Clements
Not quite everything: to give you context, this is for teaching Bootstrap's onscreen? function -- it is not enough to step through and see when onscreen? is returning true. You want to see onscreen?'s behavior while you're moving your character around the game. It's a crucial piece of this to be able to step through while the function is exhibiting its behavior in the universe, for the values you're currently seeing in the universe, and to see the evaluation change as you step over the border of the "screen".Gregory Marton
Very strange, and should probably be its own bug. I was trying to remind myself exactly what the stepper's bad behavior was, and for my program which runs just fine, it says, "default-load-handler: cannot open input file: "/Teachpacks/bootstrap-teachpack.rkt" (No such file or directory; errno=2)" using an absolute path where a relative path would be in order. Bug?Gregory Marton
If the stepper produces a result other than DrRacket (for certain baseline assumptions--it's a supported language, etc.) then it's a bug. Does this look like the same bug as 11048 (bugs.racket-lang.org/query/…) ?John Clements
Yes, that looks like the same bug, although this more generally shows that relative paths are broken, not just same directory.Gregory Marton

3 Answers

1
votes

The problem you're running into is due to a Beginner Student Language restriction: normally, higher-order functions are a syntactic error in the language, because beginners shouldn't know about them yet.

There's a way to opt-out of this. Your procedure-source can be labeled as one of the exceptions to this restriction, by using provide-higher-order-primitive, which is specifically meant to cooperate with BSL.

Here's what your library looks like with it:

#lang racket/base
(require lang/prim
         racket/bool
         (for-syntax racket/base))

(provide define/save-source)
(provide-higher-order-primitive procedure-name (fn))
(provide-higher-order-primitive procedure-source (fn))


(define *procedure-name-hash* (make-hash))  
(define *procedure-source-hash* (make-hash))  
(define (save-source! fn name body)  
   (hash-set! *procedure-name-hash* fn name)
   (hash-set! *procedure-source-hash* fn body))

(define (procedure-name fn)
  (hash-ref *procedure-name-hash* fn false))
(define (procedure-source fn)
  (hash-ref *procedure-source-hash* fn false))

(define-syntax define/save-source
  (syntax-rules ()
    ((_ (name formals ...) body-expressions ...)
     (begin
       (define name (λ(formals ...) body-expressions ...))
       (save-source! name 'name '(λ(formals ...) body-expressions ...))))
    ((_ name (λ(formals ...) body-expressions ...))
     (begin
       (define name (λ(formals ...) body-expressions ...))
       (save-source! name 'name '(λ(formals ...) body-expressions ...))))
    ((_ name value)
     (define name value))))

BSL programs that use this should be able to use procedure-name and procedure-source fine.

1
votes

I've done something like this:

(provide (rename-out (def/help define)
                     (define-syntax/help define-syntax))
         help)

(define-syntax def/help
  (syntax-rules ()
    ((_ name description signature (λ(vs ...) exps ...))
     (begin
       (add-help! 'name description 'signature '(λ(vs ...)))
       (define name (λ(vs ...) exps ...))))))
...
> (require "working.ss")
> (define (plus a b) (+ a b))
X define: bad syntax in: (define (plus a b) (+ a b))
> (define plus
    "Add two numbers"
    (int int -> int)
    (λ(a b) (+ a b)))
> (help plus)
plus
Add two numbers
(int int -> int)
(λ (a b))

(Some meat is missing, only giving an idea.) It's not super robust as shown but is this the direction you are leaning?

0
votes

This is what I ended up with:

(provide (rename-out (define/save-source define)) procedure-source)

then in the body

(define *procedure-name-hash* (make-hash))  
(define *procedure-source-hash* (make-hash))  
(define (save-source! fn name body)  
   (hash-set! *procedure-name-hash* fn name)
   (hash-set! *procedure-source-hash* fn body))

(define (procedure-name fn)
  (hash-ref *procedure-name-hash* fn false))
(define (procedure-source fn)
  (hash-ref *procedure-source-hash* fn false))

(define-syntax define/save-source
  (syntax-rules ()
    ((_ (name formals ...) body-expressions ...)
     (begin
       (define name (λ(formals ...) body-expressions ...))
       (save-source! name 'name '(λ(formals ...) body-expressions ...))))
    ((_ name (λ(formals ...) body-expressions ...))
     (begin
       (define name (λ(formals ...) body-expressions ...))
       (save-source! name 'name '(λ(formals ...) body-expressions ...))))
    ((_ name value)
     (define name value))))

and in the repl:

> (define/save-source (plus-one x) (+ 1 x))
> (procedure-source plus-one)
(λ (x) (+ 1 x))
> (plus-one 3)
4
> 

What's odd is that in the student repl I get:

> (procedure-source update-target)
(cons 'λ (cons (cons 'x empty) (cons (cons '+ (cons 'x (cons 20 empty))) empty)))
> (update-target 30)
function call: expected a defined function name or a primitive operation name after an open parenthesis, but found something else