I dont get Macros

Discussion of Common Lisp
gugamilare
Posts: 406
Joined: Sat Mar 07, 2009 6:17 pm
Location: Brazil
Contact:

Re: I dont get Macros

Post by gugamilare » Mon Feb 01, 2010 7:05 am

Destruct1 wrote:I probably dont get that, but it is very easy:

Code: Select all

def aif (long_calculation, further_func):
  if (long_calucaltion) != 0:
    return (further_func (long_calculation))
  else:
    return (0) # ??
So here is the point: Macros can be easily implemented by other things
Well, aif would be more like:

Code: Select all

(aif (some-function)
     (do-something-with it)
     (do-something-else))
==>

Code: Select all

(let ((it (some-function)))
  (if it
      (do-something-with it)
      (do-something-else)))
And it is true what you said: all that can be done with macros can be done in another way - just type its expansion and you are done. But that would make your work repetitive and boring. Sometimes you would have to write some code twice, which is obviously error-prone and not the right way. Sometimes you would be obligated to know the internals of some library.

Your suggested workaround for aif (using functions) would not be good enough. The easiest way to avoid using aif is just writing its expansion, which is not ugly at all. But it is too long and repetitive, and it will take your time while you could be working on something else in your code. The whole point about aif is its simplicity. It makes your code more direct and will keep you away from the tiny details of you code you just don't care about - in this case, the declaration of a variable just to hold a value before you can test if it is true and use the result.

The macro aif alone can't do very much with the transparency of your code if it is the only macro you have, but, having a small cosmetic macro here, another one there, and another utility function over there, in the end your code will be much more transparent.

The good Lisp style is about brevity and transparency. You should not write only what you want to do, not more, not less. If you want to do something with an open file, you just use with-open-file, instead of telling the compiler what to do (which would be you saying "declare a variable, open a file and assign the result to that variable, check if the file was successfully open, do something, then close the file"). The interface to the functionality of your code should be clear instead of requiring the user to do small repetitive ugly things to work with it.

In the end, Lisp libraries look much more like a DSL created on top of Lisp than a library itself. Each one have its own syntax to make its use simpler and clearer.

Destruct1
Posts: 20
Joined: Wed Jan 20, 2010 1:40 am

Re: I dont get Macros

Post by Destruct1 » Mon Feb 01, 2010 1:51 pm

gugamilare wrote: Your suggested workaround for aif (using functions) would not be good enough.
Why?

The Python function takes 3 Parameters and dispatches them.

I do the exact same thing:
I write a block of code as a definition then use that function in actual practice.
Writing a function definition is easier than a macro expansion.
And there is no difference when calling my construct: (my_def_macro argument1 arg2 arg3) <=> my_python_func (arg1, arg2, arg3)

To really make a difference Makros MUST make things possible who are:
Not implemented in the language
Practical
Not already done by functions

And the problem is: I havent seen a single good example
There are tons of examples for closures, aplicative operators, better native datatypes etc.
But not for Macros

ramarren
Posts: 613
Joined: Sun Jun 29, 2008 4:02 am
Location: Warsaw, Poland
Contact:

Re: I dont get Macros

Post by ramarren » Mon Feb 01, 2010 2:54 pm

Destruct1 wrote:And there is no difference when calling my construct: (my_def_macro argument1 arg2 arg3) <=> my_python_func (arg1, arg2, arg3)

Code: Select all

CL-USER> (aif (and (plusp (+ 2 2)) (+ 2 2)) (+ 3 it))
7

Code: Select all

>>> aif(((2+2)>0) and (2+2),3+it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'it' is not defined
The difference is that AIF macro doesn't take parameters, it takes code, and transforms the code such that there is a new binding for variable IT. And your Python function doesn't do it, you have to give function objects as arguments and it creates no new bindings. It is a matter of typing obviously, you could achieve it by adding 'lambda it:' in there, but that is the point, macro allow you to automate adding boilerplate like that.
Destruct1 wrote:And the problem is: I havent seen a single good example
As was written before: any macro trivial enough to explain as an example is most of the time trivial to expand manually, and hence the benefit is not obvious. A somewhat non-trivial macro may look like the one I mentioned earlier, and there is no way to implement it as a function without adding a lot of junk at every call site.

There is also other thing. One typical example of macros is WITH macros. They are so useful that an equivalent was added to Python 2.5. And the point is that in Lisp, it is just a normal macro, and the user could define it, and can define new language extensions, while in Python it had to be done by language implementation, and any similar extensions can only be added by implementation authors.

JamesF
Posts: 98
Joined: Thu Jul 10, 2008 7:14 pm

Re: I dont get Macros

Post by JamesF » Mon Feb 01, 2010 3:03 pm

Destruct1 wrote:
JamesF wrote: You know how sometimes you find yourself writing the same pattern of code for the nth time, and think, "man, I should really write a programme that would write this code for me"? That's what they do.
The main point is: What is the difference between a function and a macro. What is so special about a macro that cannot be done otherwise?
<snip>
So here is the point: Macros can be easily implemented by other things
Except for the "easily" bit, yes, you're quite correct. Macros generate Lisp code, which means they expand into Lisp forms, which can by definition be written by hand. If you want to.

I think you're missing something very important, though: macros are useful in Lisp for the same reason that you use Python instead of programming directly in assembly language (or even machine code). There's nothing you can do in Python that you can't "easily" implement in assembly, but which one would you rather use? Start the comparison with a trivial example, then scale it up to a really big system, and consider how much more of a win you get with the high-level language.

The point of macros is to abstract away pointless repetition, which both saves time and effort, and massively reduces debugging time. Their value is relative to the size and complexity of the system you're building, so if you're accustomed to writing scripts of a hundred lines or so (as I was when I started with Lisp) it can be hard to see what all the fuss is about. However, the first time you halve a thousand-line codebase by writing a couple of things that write half the code for you, the lightbulb really goes on.

I'll tackle it from another angle: what I really love about Lisp is that it allows me to write the most compact, concise code that still expresses what I want to do - all the pointless, repetitive crap can be factored away. In that sense, it's like a compression algorithm. I can write general-purpose utility functions that take other functions as arguments, and abstract away swathes of repetition, which in itself is a huge win. But then I can identify other general patterns and condense them into a single function that can be called on to generate them at compile-time, compressing the whole thing even further. To borrow a term from alchemy, you can reduce your solution to its quintessence.

Something that programming in lisp taught me was to identify the entire class of problem that I'm solving, and to solve that instead of the immediate issue. Then I have a general solution that I can use the next time I run into that problem. Macros do the same thing, but crank it up an order of magnitude. Now repetition in my code looks like a bug, and my projects are mostly in the single-digit thousands of lines. I don't write many macros, but when I do, I win big in either consistent correctness, code size and complexity, or both.

So what a macro saves you is tedium, boredom and Cobol fingers. Do you really want to write the same things over and over again, or would you rather spend your time solving new problems?

tayssir
Posts: 35
Joined: Tue Jul 15, 2008 2:33 pm

Re: I dont get Macros

Post by tayssir » Tue Feb 02, 2010 9:17 am

You'll likely use far more macros than you write. From DEFUN to LOOP, you already use all sorts of macros someone wrote, particularly those which are so broadly useful that they're well-debugged and documented.

And when you delve into various domains, you'll run into domain-specific macros. Like with HTML: take CL-WHO.

With other languages, you have to wait for the language implementor to develop some handy new for() loop. If you want to play with new abstractions, like nice support for monads, then you're definitely out of luck unless you fork the language, or write a preprocessor or something. With Lisp, this part of the language is more decentralized; someone oblivious to the language's implementation can nevertheless innovate, and package it as a library. And interested others can build upon that innovation, without it necesasrily having to be an officially blessed part of the language.

Perhaps the recent Clojure screencast about parentheses might be interesting to you. They're making fresh new justifications for canonically Lispy features.

Macros help multiply the power of other language features. But that said, Ernst van Waning gave a wonderful talk on the disadvantages of macros, worth keeping in mind. I don't want to breathlessly sing the praise of macros, as they have their place. These are not things you want to write all the time. :|

titanium_geek
Posts: 7
Joined: Mon Feb 01, 2010 7:48 pm

Re: I dont get Macros

Post by titanium_geek » Tue Feb 02, 2010 6:11 pm

Conrad Barski calls macros "SPELS"
http://www.lisperati.com/casting.html

A more direct link: http://www.lisperati.com/no_macros.html

I found it an easy to understand read.

Destruct1
Posts: 20
Joined: Wed Jan 20, 2010 1:40 am

Re: I dont get Macros

Post by Destruct1 » Wed Feb 03, 2010 6:22 am

titanium_geek wrote:Conrad Barski calls macros "SPELS"
http://www.lisperati.com/casting.html

A more direct link: http://www.lisperati.com/no_macros.html

I found it an easy to understand read.
I rewrote the Lisp adventure in Python. Although this first solution is
not 100% without overhead it is very practical to handle and solves the problem nicely.

Code: Select all

currentloc = "living-room"

map_des  = {"living-room":"You are in the living-room of a wizard's house. There is a wizard snoring loudly on the couch.",\
             "garden":"You are in a beautiful garden. There is a well in front of you.",\
             "attic":"You are in the attic of the abandoned house. There is a giant welding torch in the corner."}

map_go   = {"living-room":[("west", "door", "garden"), ("upstairs", "stairway", "attic")],
             "garden":[("east", "door", "living-room")],
             "attic":[("downstairs", "stairway", "living-room")]}

map_items = {"living-room": ["whiskey-bottle", "bucket"],
             "garden" : ["chain", "frog"],
             "attic" : []}

inventory = []
condition = []
For the general enviroment I used a native datatypes of Python, dictonaries. These are comparable to the LISP Hashtable and map a
key/loaction to a value/possible orutes, descriptions, items. Note that most of these datatypes are global and require me to type a lot of words
later on, but I wanted to stay near the LISP code whereever possible.

Code: Select all

def describe ():
    print (map_des[currentloc])
    for e in map_go[currentloc]:
        print ("There is a", e[1], "to the ", e[0])
    for e in map_items[currentloc]:
        print ("A ", e, "is on the floor")
    
def walk (whereto):
    global currentloc
    for e in map_go[currentloc]:
        if e[0]==whereto:
            print ("You go to the "+e[2])
            currentloc = e[2]
            break
    else:
        print ("Not possible")
    
def take (whatitem):
    global currentloc
    if whatitem in map_items[currentloc]:
        inventory.append (whatitem)
        map_items[currentloc].remove (whatitem)
    else:
        print ("Not possible")
The "builtin" functions walk, look, take of this textbased adventure are implemented here. The multiple helper functions that the LISP code uses for the look
command are integrated.

Code: Select all

def require (tupleinput, **keydic):
    global condition; global inventory;global currentloc;
    if "obj1" in keydic:
        if tupleinput[0] != keydic["obj1"]:
            return (False)
    if "obj2" in keydic:
        if tupleinput[1] != keydic["obj2"]:
            return (False)
    if "place" in keydic:
        if currentloc != keydic["place"]:
            return (False)
    if "cond" in keydic:
        while keydic["cond"]:
            if keydic["cond"].pop() not in condition:
                return (False)
    if "inv" in keydic:
        while keydic["inv"]:
            if keydic["inv"].pop() not in inventory:
                return (False)
    return (True)

def weld (*input):
    if require (input, obj1 = "chain", obj2="bucket", place = "attic", inv = ["bucket", "chain"]):
        print ("The chain is now welded to the bucket")
        global condition
        condition.append ("chainweld")
Here we come to the important part:
Lisp code uses a macro generating macro to have the following functionality:

gaming-action Name Object1 Object2 Place
Body

defines a possible action Name and restricts execution unless the current location is Place and the attributes of the action are Obj1 and Obj2.
In Python you have to write for the same effect:

def Name (*input):
if require (input, place = Place, obj1 = Object1, obj2 = Object2):
Body

While that is some overhead, it is very minimal. You basically only have to write input twice and add "if required". The additional keyword passing
which makes the code look bloated can be avoided by a restrictive require function which doesnt take keywords. The Python code can then be rewritten as:

def Name (*input):
if require (input, obj1, obj2, place):
Body

But it is possible to recreate the functionality of the gaming-action macro by using string manipulation and the exec command which
executes the given code passed as string

-> next post

ramarren
Posts: 613
Joined: Sun Jun 29, 2008 4:02 am
Location: Warsaw, Poland
Contact:

Re: I dont get Macros

Post by ramarren » Wed Feb 03, 2010 7:17 am

Destruct1 wrote:But it is possible to recreate the functionality of the gaming-action macro by using string manipulation and the exec command which
executes the given code passed as string
That is not recreating functionality of a macro, but of EVAL. Of course, when ran in the interpreter the difference is minimal, but that is part of the point: macros are extending a compiler. If you do not even have a compiler, then importance of macros drops and you can go with fexpr or something.
Destruct1 wrote:While that is some overhead, it is very minimal.
The problem with overhead is that it tends to grow exponentially with problem complexity. So for simple problems it may be minimal and just as well done by hand, but for complex ones it becomes such that you have to use code emitters anyway, and macros are almost always vastly superior to that.

The problem with doing string manipulation on source code is that it is notoriously brittle. The point of Lisp is that the source code syntax is essentially a literal syntax for basic datastructures, which then can be manipulated safely by code. On the other hand, syntax of most other languages (many concatenative languages being a major exception) does not cleanly map to structured data, which means that you either have to use a complete parser and manipulate the syntax tree which you cannot easily see, which adds a lot of cognitive overhead, which is a reason why every time someone implements this it never gains any significant popularity, or manipulate strings blindly which only works for simplest examples and even then is asking for trouble (with binary operator precedence rule violations a typical example).

Also, is it not missing the point? Nobody doubts that even a bad implementation of macros, or, as it may be, eval, can do what macros in Lisp can do. See Greenspun's Tenth Rule. But by doing this you already acknowledge that macros are useful, which, I believe, was what started this thread.

Destruct1
Posts: 20
Joined: Wed Jan 20, 2010 1:40 am

Re: I dont get Macros

Post by Destruct1 » Wed Feb 03, 2010 7:22 am

And here is the code for the hacked python macro:

This defines a string template machine:

Code: Select all

def gaming_action (name, obj1, obj2, place, body):
    b = "def " + name + " (*input):\n" + "    if require (input, obj1 = '" + obj1 + "', obj2 = '" + obj2 +\
          "', place = '" + place + "'):\n        " + body + "\n\n"
    return (b)


And this binds greet with full code to the top-level:

Code: Select all

exec (gaming_action ("greet", "hand", "wizard", "attic", '''print ("Hi Wiz!");global condition;condition.append ("wizgreeted")'''))

gugamilare
Posts: 406
Joined: Sat Mar 07, 2009 6:17 pm
Location: Brazil
Contact:

Re: I dont get Macros

Post by gugamilare » Wed Feb 03, 2010 9:26 am

I think Ramarren said many things valuable, just complementing.

You are creating a text processor to do the functionality of a macro, which is a bit rude. Of course that is possible, but it is error-prone and, for a reason, not popular. If you heard about C++ Templates, you will understand that trying to manipulate strings to create DSLs will eventually cause you a headache - there will be bugs introduced which you won't find easily or it will not work the way you expected. If you want a safe macro, you need access to the syntax tree. Lisp's code is mapped very easily to syntax trees in a obvious way, so you won't have to understand how a complicated set of operators are represented by the compiler or language. And you can construct such trees also in an easy way, specially with the syntax provided by ` ' , and ,@ in Lisp.

By using macros, you will think with macros. For instance, I bet one wouldn't come out with the "casting spells" idea if that person didn't have knowledge about Lisp macros and wasn't familiar with them. Learning a new paradigm - in this case, the so-called meta-paradigm - you teach you new ways of solving your programming problems. But, in any case, it is completely your choice whether you want to learn how to use them or not.

The idea of Lisp macro system has been brought to other languages. The example I know is OCaml. I don't know OCaml, but a first look at it makes you see that its syntaxes are much more complicated than Lisp's, because you have to handle more complicated syntax trees to handle order of precedence of operators, among other things. And my first thought would be that, since its macros are not so simple to use, you many times will feel discouraged to use them, therefore making their use much less frequent than in Lisp.

Post Reply