Safe macros in newLISP - define-smacro
Posted: Mon Apr 06, 2009 2:54 am
Something that spawned from this thread was a discussion of the proper way of writing safe macros in newLISP.
As mentioned in the API for the define-macro function, it's very easy to write a macro that suffers from variable capture possibilities. Simply naming the arguments that the macro takes is the easiest way to shoot yourself in the foot, but even if you avoid that through the use of the 'args' function, hard-to-fix dangers still lurk.
From that discussion I've learned that the best way to write macros in newLISP is to:
However, I think it would be a lot easier and more in line with the current define-macro and define-function functions to have a define-smacro function that lets you use the same syntax to define safe macros. I finally found a way to write this on my own, but it's neither pretty nor efficient:
You can write your macros as if you were writing normal functions, using named arguments etc., and you don't ever have to worry about something breaking from variable capture.
The only problem so far is that in my tests using define-smacro was 11 times slower than the def-static function (repeated 10000 times). This makes sense because you have to build an expression, turn it into a string, and then evaluate that string. You can't use newLISP's eval function to do this because it won't place the arguments in the correct context.
Still, maybe this function might be useful to someone. It would be *really* nice if it could be included natively in the standard library as then it would be very efficient... (*nudge* *wink*) After all, why write unsafe macros when you could just as easily write safe ones? This would also give newLISP great bragging rights methinks. :-)
As mentioned in the API for the define-macro function, it's very easy to write a macro that suffers from variable capture possibilities. Simply naming the arguments that the macro takes is the easiest way to shoot yourself in the foot, but even if you avoid that through the use of the 'args' function, hard-to-fix dangers still lurk.
From that discussion I've learned that the best way to write macros in newLISP is to:
- Use the default function to encapsulate them in their own namespace
- Use C-style naming conventions to avoid name conflicts with other contexts
Code: Select all
(define (def-static s body)
(def-new 'body (sym s s)))
(def-static 'my-or
(fn-macro (x y)
(let (temp (eval x)) (if temp temp (eval y)))))
(setq temp 5)
(my-or nil temp) => 5
Code: Select all
(define-macro (define-smacro _params)
(let (_newCtx (_params 0))
(eval-string (string (cons 'define-macro (cons (cons (sym _newCtx _newCtx) (rest _params)) $args))) _newCtx)
)
)
; usage:
(define-smacro (my-or x y)
(let (temp (eval x))
(if temp temp (eval y))))
The only problem so far is that in my tests using define-smacro was 11 times slower than the def-static function (repeated 10000 times). This makes sense because you have to build an expression, turn it into a string, and then evaluate that string. You can't use newLISP's eval function to do this because it won't place the arguments in the correct context.
Still, maybe this function might be useful to someone. It would be *really* nice if it could be included natively in the standard library as then it would be very efficient... (*nudge* *wink*) After all, why write unsafe macros when you could just as easily write safe ones? This would also give newLISP great bragging rights methinks. :-)