2
votes

I need to implement my_let* using defmacro which works similarly to let*, but while let* is expanded to a series of nested let calls (behind the scenes), my_let* needs to be expanded to a single let call, and use the define statement to define the arguments i get.

an example of using my_let*:

 (my_let* ((a 2)
 (b 3)
 (c (+ a b)))
 (+ a b c))

and the return value of this code should be 10. just as if it was use let*. the code above will be expanded in my_let* to the following:

(let ()
 (define a 2)
 (define b 3)
 (define c (+ a b))
 (+ a b c))

I'm new to using macro, though i successfully written some macros, this one got me lost.
Thank you in advance.

3
Two issues: Do you really want to use defmacro, which Racket does have for compatibility reasons, instead of the (much easier) define-syntax, syntax-rules, and similar? Also, the expansion that you suggest isn't really the same as let* because let* allows you to shadow previous bindings whereas define does not. Are you ok with that? - Brendan Cannell
@BrendanCannell in this case yes, as it is a part of an assignment (and a part of it is macros). i would still like to learn about the use of define-syntax. (maybe it is a part of the course that will be learned in the future). - Dul
Ok. Rather than someone just giving you the answer, maybe you can tell us what you've tried and what you're thinking so we can point you in the right direction. - Brendan Cannell
@BrendanCannell as I mentioned above. this one's got me lost. I did write other macros successfully, but even using (define x y) for defining a variable is new to me, since we always used (let x y) for this purpose. In addition, i can't tell what the inner helper function should do here. - Dul
I cannot reasonably guess what went wrong in your definition since you failed to give it to us. Then the only answer can be to simply write the code for you. There isn't even anything to explain. Is that really what you want? - law-of-fives

3 Answers

3
votes

Use syntax-parse. At the least don't even consider using defmacro in Racket.

#lang racket

(require (for-syntax syntax/parse))

(define-syntax (my-let* stx)
  (syntax-parse stx
    [(_my-let* ([name:id e:expr] ...) body ...)
     #'(let ()
         (define name e) ...
         body ...)]))

The name:id means that name must be an identifier and e:expr means that e must an expression. These simple annotations help syntax-parse to give you better error messages.

Example:

(my-let* ((4 2)
          (b 3)
          (c (+ a b)))
         (+ a b c))

Here the DrRacket will color the 4 read and give the message:

my-let*: expected identifier in: 4
0
votes

The Scheme way is using syntax-rules

(define-syntax my-let*
  (syntax-rules ()
    ((_ ((binding expression) ...) body ...)
     (let ()
       (define binding expression) ...
       body ...))))

Using defmacro is more like making a procedure.

(define (my-let-fun* bindings . body)
  ...)

How it should work is like this:

(my-let-fun* '((a 1) (b 2) (c (+ a b))) "test" '(list a b c))
; ==> (let () (define a 1) (define b 2) (define c (+ a b)) "test" (list a b c))

If you have not called my-let-fun* in your implementation it's just changing it to a defmacro and you're done.

(defmacro my-let* (bindings . body)
  ...)

It's quite simple to do either with a helper to do recursion or foldr to do the bindings. Good luck!

Your my-let* will only work in #lang racket and perhaps #!r6rs and later. In R5RS you will get an error in this case:

(my-let* ((a 1) (b 2) (c (+ a b)))
  (list a b c))
; signals an error that a is undefined.

The reason is that it expands to something like this:

(let ((a 'undefined) (b 'undefined) (c 'undefined))
  (let ((tmp1 1) (tmp2 2) (tmp3 (+ a b)))
    (set! a tmp1)
    (set! b tmp2)
    (set! c tmp3))
  (list a b c))
0
votes

Between the error messages and some judicious use of the macro stepper I think it's hard to go too wrong here. The trouble is just making sure you've put things together right using either conses or unquote-splicing. I believe the standard practice in such macros is heavy use of quasiquote and unquote-splicing in order for the output to as closely match the intended statement as possible, otherwise the macro can become quite inscrutable. But I am not a defmacro expert.

#lang racket/base
(require (for-syntax racket/base)
         compatibility/defmacro)

(defmacro my-let* (binding-pairs . body)
  (define defines (map (lambda (bp) (cons 'define bp)) binding-pairs))
  `(let ()
     ,@defines
     ,@body))

(my-let* ((a 2) 
          (b (expt a 3)))
  (printf "a:~a\nb:~a\n" a b)
  (+ a b))