Arc Forumnew | comments | leaders | submitlogin
Don't understand how macros work
4 points by zhtw 5873 days ago | 16 comments
After defining that:

(mac assign (name expr) `(= ,(eval name) ,expr))

this works:

arc> (= x 'variable) variable arc> (assign x 33) 33 arc> variable 33

So, why this doesn't work then?

arc> (each k (keys h) (assign k (h k))) Error: "reference to undefined identifier: _k"

12 points by absz 5873 days ago | link

As cchooper observed, macros are expanded at compile time, not runtime. This means that they cannot see lexical variables like k. (Since x was a global variable, it could be seen at compile time, thus making your first example work.) In order to look at the lexical environment, you need an ordinary function:

  (def assign (name expr)
    (eval `(= ,name ',expr)))
The reason you don't need a macro here is that you aren't actually suppressing evaluation, which is what macros do; you want the values of name and expr. Thus, assign should be a function, which runs at runtime. Also note that expr needs to be quoted, or you attempt to evaluate it, which fails if it is a symbol.

The one thing that might trip you up with this function is that it (like =) will not create lexical variables, but only globals, unless lexicals of the names you are about to assign are already declared.

Also, a formatting tip: to get text to appear as code, surround it by blank lines and indent it by two spaces.


4 points by zhtw 5872 days ago | link

That explains everything. Thanks!


4 points by cchooper 5873 days ago | link

All macros in an expression are expanded before any part of the expression is executed. So (assign k (h k)) will be expanded before each is executed, and at that point k has no value, so (eval k) will fail.

So you need to move eval into the result of the macro expansion, and avoid calling it in the macro expansion itself:

  (mac assign (name expr) `(eval (list '= ',name ',expr)))


2 points by zhtw 5873 days ago | link

OK. I understood the problem. But your solution doesn't work.

  arc> (= x 'var)
  arc> (assign x 20)
  arc> var
  Error: "reference to undefined identifier: _var"
Indeed. What you wrote is "assign a value to the given name". I meant "to assign a value to a variable which name is stored in the given variable".

My original example was just attempt to copy the table into the current environment:

  (= h (obj name0 0 name1 1 name2 2))
  (each x (keys h)
    (assign x (h x)))


3 points by almkglor 5872 days ago | link

Then use 'eval :

  (ontable k v h
    (eval `(= ,k ,v)))
Note that this does not copy to the current environment, just the global environment. So if you're doing something like this:

  (let foo 42
    (= h (table 'foo 1)) ; Anarki only
    (ontable k v h
      (eval `(= ,k ,v)))
Then the 'foo you access is the local one, but the one written by 'eval is the global.


3 points by absz 5872 days ago | link

Careful! You're evaluating the value of v here, which can break:

  arc> (ontable k v (table 'var 'a)
         (eval `(= ,k ,v)))
  Error: "reference to undefined identifier: __a"
You need to quote the value of v here to prevent it from being evaluated; when you do so, you get

  arc> (ontable k v (table 'var 'a)
         (eval `(= ,k ',v)))
  #hash((var . a))
  arc> var
And then the body of he loop is the same as the body of my assign function from


1 point by almkglor 5872 days ago | link

This is correct ^^


2 points by zhtw 5872 days ago | link

> Note that this does not copy to the current environment, just the global environment.

Yeah, I'm aware of that. But it's a good point anyway, thanks.


2 points by stefano 5873 days ago | link

You don't really need to use eval:

  (mac assign (name expr)
    `(= ,name ,expr))
then (assign k 3) will be expanded to (= k 3).


2 points by zhtw 5873 days ago | link

No. You didn't understand. I meant exactly what I wrote. I want to assign a value to variable which name is stored in the variable "name".


3 points by jmatt 5873 days ago | link

What are you doing with all these variables?

There is no doubt that you'll be able to get arc to do it with enough time and energy. But this use of variables seems un-functional. Or otherwise not in the spirit of lisp. Have you considered a hash table where the key is the variable name?

This is coming from someone that writes VB.NET and C# at work. I understand the need for variables. But, I also know that I use very few when writing lisp.


4 points by tung 5870 days ago | link

It's risky to say "you should do X" or "you shouldn't use Y" when talking about using programming languages, because they don't look at the problem being solved. Using variables may be a very natural way to approach problem A, while they'd be very clunky for problem B.

There are places where even goto is useful.


2 points by jmatt 5870 days ago | link

I am asking because it is a common mistake when moving from an imperative language to a functional language. I know I made the mistake of trying to use many variables when I first started writing functional code. I was asking so that I could help out.

I never said "you shound do ..." or "you shouldn't use ...".

I have no problem with people using whatever style or approach that they want. If you want to write arc with gotos and a bunch of variables, go for it. I just wanted to help this arc coder out.


2 points by applepie 5873 days ago | link

Spirit of Lisp?

Lisp is what you want it to be, not pg's fundamentalist view on it.


6 points by absz 5873 days ago | link

That's not quite true. You would not see Lisp code written as though it were assembly, full of gotos and working with a finite number of registers, to take an extreme example. Lisp is a multiparadigm language, but it is heavily functional. As such, writing imperative code is not generally advisable ("not in the spirit of Lisp"), but is possible. Similarly, code with such a heavy use of variables is likely unfunctional, and thus would similarly be outside the "spirit of Lisp". Yes, you can do anything you want in Lisp (cf. the Church-Turing thesis), but it may not be advisable ("in the spirit of Lisp"). Again, for instance, if you want to work in a stack-based manner, it would probably be advisable to either (1) rework your code, or (2) switch to a stack-based language like Factor.

This is not unique to Lisp: if you want to write a tail-recursive, functional, list-based program in C, that's probably not a good idea. You can, but since C is optimized for iteration (with many implementations not even supporting tail call elimination) and for imperative programming (it lacks closures and anonymous functions), and since memory management in C is…clunky…you would be better off (1) reworking your code, or (2) switching to a more functional language with lists and tail call elimination, such as a Lisp.

In short, yes, as a Turing-complete language, Lisp can do anything. And as a multi-paradigm language (with macros), it can do a good job at performing a given task in any way. But it has strengths, inclinations, and intentions, which together do comprise what could be called a "spirit of Lisp".


5 points by almkglor 5872 days ago | link

> Lisp is what you want it to be, not pg's fundamentalist view on it.

The Turing Machine can be anything you want it to be, but it's not always easy - or desirable - to force the machine into doing what you want.

You can use hedge clippers to cut your toenails, but I doubt you'd want to do that.