Hacker News new | past | comments | ask | show | jobs | submit login

Not OP so there might be others, but the two that I see are:

* most crucially, x isn't actually spliced in, meaning that the macro always literally expands to (+ x x). For example, (+ (double 2) 5) just expands to (+ (+ x x) 5), which will either crash if x is undefined or do something unexpected if x is.

* Even if x were spliced in properly, it gets evaluated twice. That's wasteful at best, and if x has some kind of side effect you (arguably) would get unexpected behaviour - the side effects would run twice.




Just for completeness, the correct macro would look like this:

  (defmacro double (x)
    (let ((temp (gensym))
    `(let ((,temp ,x))
       (+ temp temp))))
This way, when you write (double (parse-integer (read-line)) it would expand to

  (let ((#:uniqueName123 (parse-integer (read-line)))
    (+ #:uniqueName123 #:unique_symbol_123))
This guarantees that that the macro will not accidentally refer to some outside variable, and that it's argument will only be evaluated once (so that we don't read two lines of input in this example).

To explain a little bit what is going on: normally if you want to have an s-expression as a piece of data, you can use the quote special form - (quote (a b c)), usually shortened to '(a b c), returns a list containing three symbols (think of these as special strings), "a", "b", "c". If you want instead to evaluate a variable named "a", you can use the ` syntax, together with , and ,@. That is, `(,a b ,@c) will produce a list containing the value of a variable named "a", the symbol "b", and the value of a variable named "c", spliced in. If a is '(1 2 3) and c is '(4 5 6), `(,a b ,@c) will return the 5-element list ((1 2 3) b 4 5 6). Depending on how this is used further, b itself may be evaluated or just printed as is.

So, when expanding the macro, x will be initialized to the form provided as argument to double (not the value of that form). Then, temp will first be assigned a value that is produced by gensym, which generates a unique symbol; then, we'll return a list that represents some Lisp code binding the form represented by x to a variable whose name is the value returned by gensym, and then using this same variable name in a call to +. Finally, if this macro was "called" from regular lisp code, the expression it returned will be compiled or interpreted.

The macro could also be called from a special form like macroexpand-1, which would just return the list returned by the macro, without evaluating it; or macroexpand, which would do the same but recursively until there are no more macros in the expansion.

Note: a symbol is basically a string that can be used as a Lisp identifier, and is registered as such in the Lisp runtime. It is a separate type from string, but you can create a string from a symbol, or try to create a symbol from a string (which fails if the string is not a valid Lisp identifier).


I made a mistake when copy pasting and renaming something, the expanded code should have been

  (let ((#:uniqueName123 (parse-integer (read-line)))
    (+ #:uniqueName123 #:uniqueName123))


Surely

    (defmacro double (x)
        (let ((temp (gensym))
        `(let ((,temp ,x))
           (+ ,temp ,temp))))


The correct Common Lisp "macro" is

   (declaim (inline double))

   (defun double (x) (+ x x))


Oops, yes...


And this thread is why types help :)


Actually, if I had compiled the code produced by macroexpanding the macro, I would have gotten a warning:

  (compile nil '(lambda (x) (double x)))
  ;Compiler warnings :
  ;   In an anonymous lambda form: Undeclared free variable TEMP
  ;   In an anonymous lambda form: Unused lexical variable #:G520
Unfortunately, HN doesn't compile code you add in the comments...

Note, this is a warning and not an error because of CL's semantics, as the following is a valid program:

  (let ((temp 100))
    (double 10))
  ;returns 200
If you really wanted this effect though, normally you would have to declare that `temp` is a special variable:

  (declaim (special temp))
  (compile 'nil (lambda (x) (double x))
  ;Compiler warnings :
  ;   In an anonymous lambda form: Unused lexical variable #:G524


> meaning that the macro always literally expands to (+ x x)

Make that three bugs. The macro is called "double," but the numeric result is square.





Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: