Racket - implementing the let* function using macro

﹥>﹥吖頭↗ 提交于 2019-12-24 01:54:54

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!