Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

Re^2: Overcoming addiction to Lisp

by Anonymous Monk
on Jun 15, 2005 at 22:14 UTC ( #467089=note: print w/replies, xml ) Need Help??


in reply to Re: Overcoming addiction to Lisp
in thread Overcoming addiction to Lisp

Macros can provide cutesy solutions to some problems, and can provide a performance boost if you have an inferior compiler, but I challenge you to give a compelling example of what macros can do, that you couldn't also do (almost as succinctly) with higher order functions in perl. Here's a list of things that I probably won't find compelling...
  1. Yet another implementation of Prolog
  2. functions that don't evaluate their arguments (wrap 'em up in a sub{} closure
  3. ).
  4. a 10% speed boost (Perl's too slow to worry much about speed.
  5. custom made regular expression embedded language (already got it)

Replies are listed 'Best First'.
Re^3: Overcoming addiction to Lisp
by Anonymous Monk on Jun 16, 2005 at 01:28 UTC
    None of these, to me, a new Lisper, are what macros are about. Macros are about being amazingly lazy as a programmer. About finding those patterns of code that you write all the time, and making the system do the work for you in ways that are nearly impossible in other languages. It's not about speed, though certainly macros can be faster, especially if you look at things like the CL regex engine. The point is that I'm lazy, and I don't like typing things, and macros let me say it once, and more succinctly than other things. Lisp isn't the hammer for every problem, nor is Perl, nor is any tool. I don't trust a programmer who things his favorite hammer is the best hammer.
      As an experienced Lisper (and mostly Schemer today) I have used both hygienic as well as non-hygienic (Common Lisp) macros. I'm going to be something of a heretic here, but in nearly every case there is likely something *better* to use than a macro. As an implementor of Scheme i've seen a number of additions that supplement R5RS (or even Common Lisp) that remove just about every need for a macro. A simple builtin function such as LAMBDA-CASE removes the need for just about every Scheme macro in existance. And here is the real kicker... the closer you get to a programmable programming language (what Lisp is known for anyhow), the more odd and almost alien macros become. They almost blend into and become functions themselves, in a way. If this sounds confusing, imagine a Lisp interpreter (or Lisp Machine) that is always running (and evolving.. just like the good old days). Macros *must* become first-class citizens, or they simply don't make sense at all. And, of course, many people have already proposed such additions! In this manner, they aren't much different from functions which are first-class already. And there have even been "lexical macros" which retain source-level information for debugging purposes. Hmm.. I wonder what else can be tied to lexical scope and is first-class.... functions!
        Do you mean case-lambda, or are you refering to something different?
      Eliminating reduntant code has more advantages than just laziness. It improves code quality, especially during maitenance.
        Exactly. That's what subroutines and modules are for.
Re^3: Overcoming addiction to Lisp
by Anonymous Monk on Jun 16, 2005 at 19:12 UTC

    Whether you can do something with higher order functions or even simpler constructs isn't a great argument against having macros.

    Recently I needed to do something for every string of length N or fewer. So I wrote a function that returned a closure that enumerated all strings until exhausting the string space. But I had to loop over the string space a few times in the program, and it got really cumbersome to write things like this:

    (flet ((next-string (make-string-enumerator <num>))) (loop for str = (next-string) until (null str) do <body>))

    Once I'd written that twice, I realized that I was eventually going to make mistakes writing or rewriting expressions like that, so I wrote a macro that expanded into that expression. Once I had the macro, I could write the following instead of the above:

    (dostrings (str <num>) <body>)

    Now, maybe you're thinking "That's only a two-line savings in any place where you'd need to do what you're doing", but to me the win is that the macro lets me write only the important things (the <body>, the maximum string length <num>), and the macroexpander takes care of the tedious and error-prone code for me. This isn't just saving typing: I don't have to think up a variable name like next-string whenever I need to loop over all strings, and so I never have to track down bugs introduced by changing variable names when refactoring. If I don't use it outside the macro, I don't even need to remember the purpose or interface for make-string-enumerator, either.

    Yeah, I could have written something analogous to the <body> form as a function of the string returned by the enumerator, and written the original loop like this:

    (let ((next-string (make-string-enumerator <num>))) (labels ((looper (body enumerator))) (let ((str (funcall enumerator))) (when str (funcall body str) (looper body enumerator)))))

    But I'm not sure that's easier to understand than either the original loop form or the dostrings macro, and anyhow it's still long enough that I think I'd want something like my dostrings macro to generate it if I had to write something like that more than once.

    Functions (including higher order functions and closures) are one way of factoring code for maintainability; macros are another. They complement each other.

      Maybe I missed it, but why would dostrings need to be a macro, instead of a function? And if you don't like making up non-sense names, just use perl's default name, $_. Besides, it really sounds like you should be using the builtin list traversal operators like grep, map, and foreach instead of rolling your own.
        ...it really sounds like you should be using the builtin list traversal operators...

        The code is not traversing a list. It is creating an iterator, and executing a block of code for each item returned by the iterator. You might argue that a foreach{...} is an iterator, but that would require generating the entire list first, which may not be appropriate here. In perl it would be more like:

        my $nxt_str = make_strings($n); while (my $str = $nxt_str->()) { #..do stuff with $str }

Re^3: Overcoming addiction to Lisp
by Anonymous Monk on Jun 16, 2005 at 21:13 UTC
      What is the special feature about those examples that couldn't have been done just as well (or succinctly, or whatever) without macros?
        All three (in different ways) aim to provide a syntax for concisely expressing something that otherwise could only be expressed via some (rather convoluted) coding patterns. That is, they provide syntactic abstractions (which is what macros are really all about.) But you'll get a far better explanation from the chapters I pointed to than I'll be able to type her in this browser window. -Peter

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://467089]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others perusing the Monastery: (9)
As of 2019-03-22 16:08 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    How do you Carpe diem?





    Results (113 votes). Check out past polls.

    Notices?