问题
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.
回答1:
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
回答2:
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))
回答3:
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))
来源:https://stackoverflow.com/questions/43985964/racket-implementing-the-let-function-using-macro