lispm

joined 11 months ago
[–] lispm@alien.top 1 points 9 months ago

Common Lisp is a bit like an onion. There are different layers of language and the inner layers date back to the first Lisp implementation from 1958. It's grown over decades.

A beginner is now confronted with history and inconsistencies. On the positive side a goal was not to throw away useful older applications or libraries, they could be ported over the different generations of Lisp. Same for the book. If you read a CL introduction from the mid 80s it's still mostly valid, but lacks newer stuff. It's also the implementations: some SBCL code may date back to the early 80s, with Spice Lisp, which was started even before Common Lisp existed.

At that time PROG was a widely used control structure for imperative programming. PROG1, PROG2 and PROGN were a bit similar and indicate in the name which result is returned.

You walk in a street, where some house are a few hundred years old and some are relatively new, using different building styles.

[–] lispm@alien.top 1 points 10 months ago

why would evaluate the comparison? The comparison is a symbol, which a name for the function. Just use that symbol and insert it into the code.

(defmacro for ((param-name start-value end-value &optional (step1 1)) &body body)
  (let* ((func-name (gensym))
         (comparison (if (< step1 0) '<= '>=))
         (start (gensym))
         (end (gensym))
         (step (gensym))
         (k (gensym)))
    `(let ((,start ,start-value)
           (,end ,end-value)
           (,step ,step1))
       (labels ((,func-name (,param-name ,k)
                  (if (,comparison ,end ,param-name)
                      (progn (progn ,@body)
                        (,func-name (+ ,param-name ,step)
                                    (1+ ,k)))
                    ,k)))
         (,func-name ,start 0)))))

If you actually want to make sure that function does not get redefined, you need to understand that functions and variables are in different namespaces. You would need to get a function object and funcall it.

(defmacro for ((param-name start-value end-value &optional (step1 1))
               &body body)
  (let* ((func-name (gensym))
         (comp (gensym))
         (comparison (if (< step1 0) '#'<= '#'>=))
         (start (gensym))
         (end (gensym))
         (step (gensym))
         (k (gensym)))
    `(let ((,start ,start-value)
           (,comp ,comparison)
           (,end ,end-value)
           (,step ,step1))
       (labels ((,func-name (,param-name ,k)
                  (if (funcall ,comp ,end ,param-name)
                      (progn (progn ,@body)
                        (,func-name (+ ,param-name ,step)
                                    (1+ ,k)) ) ,k)))
         (,func-name ,start 0)))))

But you don't need that.

If you want to debug macros, you need to see the expansion. Do it like this:

CL-USER 14 > (pprint
              (macroexpand
               '(for (i start end)
                  (print i))))

(LET ((#:G1788 START) (#:G1789 END) (#:G1790 1))
  (LABELS ((#:G1787 (I #:G1791)
             (IF (>= #:G1789 I)
                 (PROGN
                   (PROGN (PRINT I))
                   (#:G1787 (+ I #:G1790) (1+ #:G1791)))
               #:G1791)))
    (#:G1787 #:G1788 0)))
[–] lispm@alien.top 1 points 10 months ago (2 children)

(defmacro for   ((param-name start-value end-value &optional (step1 1)) &body body)     (let* ((func-name (gensym))            (comp (gensym))            (comparison (if (< step1 0) '<= '>=))            (start (gensym))            (end (gensym))            (step (gensym))            (k (gensym)))       `(let ((,start ,start-value)              (,comp ,comparison)              (,end ,end-value)              (,step ,step1))         (labels ((,func-name (,param-name ,k)                    (if (,comparison ,end ,param-name)                        (progn (progn ,@body)                          (,func-name (+ ,param-name ,step)                                      (1+ ,k)) ,k))))         (,func-name ,start 1)))))

Your indentation is wrong. The last ,k is at a wrong place. Shouldn't it be returned by the other if clause.

If you have an error and you need help, then you need to provide the necessary information:

  • a reproducible example
  • the error message
[–] lispm@alien.top 1 points 10 months ago (4 children)

sure, remove the LET

[–] lispm@alien.top 1 points 10 months ago (7 children)
(defmacro for ((param-name start-value end-value &amp;optional (step1 1)) &amp;body body)
  (let* ((func-name (gensym))
         (comparison (if (&lt; step1 0) '&lt; '>))
         (start (gensym))
         (end (gensym))
         (step (gensym))
         (k (gensym)))

    `(let ((,start ,start-value)
           (,end   ,end-value)
           (,step  ,step1))

       (labels ((,func-name (,param-name ,k)
                  (let ((new-exprs (progn ,@body)))
                    (if (,comparison ,end ,param-name)
                        (,func-name (+ ,param-name ,step) (1+ ,k))
                      ,k))))
         (,func-name ,start 1)))))

Make sure that the new form evaluates the macro arguments in the correct order. The LET enforces it.

Now you would need to think what to do about the step thing.

Can it only be a number? Can it be computed? What if the step value is negative?

How to check that the PARAM-NAME is actually a symbol?

Write some test cases.

[–] lispm@alien.top 1 points 10 months ago (1 children)

You might want to check the indentation of your code. It looks not right. Do you use TAB characters in your editor? Don't!

Besides the eval is evil, what are other pitfalls of writing code this way?

One problem could be, that your code is not working, because you have a bug in your EVAL form. Did you actually try to run it?

Why the usual gensym in macro is not interned?

Because then there is no name clash with symbols written by the macro user. If you would intern gensyms, where would you intern it? In which package Your variant does not say, whatever the current package is the symbol will be interned. If the user has a symbol there, one may get random (hard to debug) name clashes.

Besides the eval is evil, what are other pitfalls of writing code this way?

EVAL is not 'evil'. It's just most of the time not needed and you need to understand what it does. EVAL evaluates the code always in the global environment. EVAL also may not compile the code, so it may run interpreted, depending on the Common Lisp implementation.

[–] lispm@alien.top 1 points 10 months ago

allows you to skip the often tedious parsing step by using the host's read

READ mostly gives a tree of tokens. This makes it a form of a tokenizer. After we call READ we don't know what the tokens mean: is FOO a variable, a macro, a function, a built-in special form, a type, ... This can only be determined by interpreting/compiling the token tree.

[–] lispm@alien.top 1 points 10 months ago

Smalltalk also has similar interactive development styles. One can build the application incrementally piece-by-piece. It has a residual graphical integrated development environment -> the development environment is a part of the program. It uses late-binding via message-passing. It starts and saves images (-> dumps of the heap contents, data and code). It is introspective & reflective (one can find out about the running program and can change it).

[–] lispm@alien.top 1 points 10 months ago (2 children)
[–] lispm@alien.top 1 points 10 months ago

No, the compiler does not do that. If you look into your macro here, it just puts the args start and end back in. The generated code then is executed. Remember: START and END are bound to the source forms, not computed values.

In the original macro you had the form (loop for port from start to end ...), which you tried to compute at macro-expansion time. But the values of START and END are not necessary numbers, but source forms, like variable names.

[–] lispm@alien.top 1 points 10 months ago (2 children)

If you compile code, then the compiler expands macro forms at compile-time. If you want to generate a variable number of let bindings, then the number needs to be known at compile-time.

start and end thus can't be variables at run-time.

(let ((start 1))
  (with-free-ports start 3 port-1))

If we compile above form, then at compile-time the value of start generally is unknown.

view more: next ›