A good question for any "macro" systems like this is something like the following:
1. Suppose you've written a function 'foo' in your language that does something useful (e.g.: partitions a sequence, validates a map, or pretty much anything really).
2. Some time later you want to write a macro 'bar'.
3. Can you use function 'foo' when writing 'bar'?
If the answer to 3 is "no" then you don't really have Lisp-like macros.
This is what makes Lisp macros so powerful. It's not just that you have a way to mangle abstract syntax trees. If that's all you want then yeah, you can write a parser and template language to do it, like this thing or sweet js, but it's not the same.
Lisp macros are beautiful because there's no real divide between "writing code" and "writing macros". It's all just code. You don't have to worry about which things are available in code-land and which are available in macro-land because they're the same place. There's just "the language" which you extend and mold with functions and macros woven together as necessary.
Contrast this with a language without real Lisp-like macros, like Clojurescript. If I want to write the 'bar' macro in Clojurescript I need to think "wait 'foo' is a Clojurescript function so now I need to port it back into Clojure land before I can use it in this macro because macros in Clojurescript live in Clojure-land not Clojurescript-land". I need to think about this for everything I call inside a macro.
(Admittedly the situation isn't as bad in Clojurescript because it's fairly close to Clojure in syntax and it's possible to cross-compile code so it lives in both "lands", but the ugly divide is still there.)
Common Lisp, Clojure, Scheme, Wisp, Julia, etc have Lisp-like macros. Sweet JS, Clojurescript, C, etc don't.
These types of macros are very similar to Scheme's syntax-rules macros without the hygiene.
Viewing Lisp macros as an arbitrary function from syntax to syntax is fine, but such macros are not very Scheme-y. It's very difficult to have such macros and guarantee they're hygienic, which is why syntax-rules limits you to matching patterns and producing templates -- it effectively limits the kinds of functions which can be macros.
Other types of Scheme macros (which are non-standard), like explicit renaming and syntactic closures, require programmers to opt-in to hygiene. syntax-case (which is in R6RS) allows programmers to break hygiene by jumping through hoops, but is otherwise similar to TFA's system as well.
So if Scheme is a Lisp and these macros are like Scheme macros, I don't think it's inaccurate to call them Lisp-like macros. But it is imprecise.
As an aside, I've written a compiler for a Lisp-like language. Speaking from experience, getting the compiler to answer "yes" to 3 is non-trivial (even though it's dead-simple in an interpreter). I suppose that's why Racket has all the phase distinctions it has.
For something similar, take a look at Terra [1], the "low-level counterpart to Lua". It's based on Lua, but has "low-level" functions that are JIT-compiled to machine code using LLVM. It supports calling Lua macros from low-level functions, or you can use Lua code to write complex high-level frameworks (e.g. Go-like object system [2], Java-like object system [3]).
It's still in development, so there are parts of the system that don't work very well (e.g. global variables in compiled executables cannot be statically initialized), but it's a very interesting project.
It would be great to be able to integrate this into the Clang pipeline so that Clang based auto completion and error detection would not trip over it but correctly evaluate the resulting source and use that. Then, it would be easy to integrate this into an existing project workflow. Otherwise it would limit usability due to a lack in tooling support, I'd guess.
Clang already does that for the C preprocessor, although that is probably much easier to do since it is not Turing complete [1]
I actually started out trying different parsers (Clang, pycparser), but they validate too much. cmacro uses a home-spun parser because it's meant to let you write things like:
> Because a language without macros is a tool: You write application with it. A language with macros is building material: You shape it and grow it into your application.
"Lisp isn't a language, it's a building material."
...in a positive sense, because he was also quoted describing Lisp as "the greatest single programming language ever designed", though I bet this was not in a Lisp vs. Smalltalk comparison :)
This is a standard Lisp idea, but I think it's kind of off base.
Lisp has this idea of creating a language in which you can solve your problem. That's fine, but the Lisp people mean something rather different than the rest of us do.
When I "create a language to solve a problem" in, say, C++, I create some nouns (objects), some verbs (methods), maybe some adjectives (other objects or flags) and adverbs (more flags). Then I can write my application using that "language", but using normal C/C++ syntax.
When Lisp people say "create a language to solve a problem", they mean "write completely different syntax". The article gives some examples.
Why would I want to do that? Well, I might want to explore a new paradigm - some new thing like aspect oriented programming, say. I don't have to wait for a language that implements it, I can just do it myself. This is great for academic research projects, but not nearly so great for production code. (Bringing new people up to speed becomes a much longer process, if your code outlives the original developers.)
But I might have to create new language features just to get anything done in Lisp. One example is LOOP. It's a macro, because Lisp without macros doesn't have very good looping ability. "It's a building material" partly in this sense: It's not a very good tool. It's not all that usable until you add the parts that, in most other languages, you already get.
Now, does LOOP do more than a C-style for or while loop? I doubt it, but perhaps it does it more neatly. But C gives you 90% of what you need without a macro, whereas Lisp gives you 10% without a macro. If you need more in C, you can do it, even if it's a bit clumsy. But in Lisp without macros, it's horribly clumsy all the time.
(Yes, I know that there are Lisp and Haskell types who seem to regard writing a for loop over a container as a great waste of programmer time. I think that they are mistaken.)
You can do some amazing things with Lisp macros. Paul Graham gives the example of writing an extension language for Viaweb that Lisp macros turned into Lisp code, which was then run on the server. That's really slick (though in the current situation, you have to watch out for security issues unless you validate that file very carefully). But if he had chosen another approach, what would change? The macros have to turn the file syntax into Lisp syntax. If he had written an ordinary parser, he would have had to do the same. The only difference is that, by using macros, he let the Lisp compiler do the grunt work of the parsing.
TL;DR: Lisp needs macros to be usable. I'm not convinced that C does.
Normally, the lack of tooling support is the first thing that turns me off to attempts such as this to adjust fundamental aspects of the coding pipeline.
In the context of C, however, you're up against a language (the preprocessor) that itself has only bare-minimum tooling support, so you've got a leg-up over similar projects in that regard. :)
So I tried installing this on a debian box, did anyone else have issues compiling? I apt-got sbcl, flex, but make failed with "asdf-linguist" not found until I swapped lines 46/47 in the Makefile. Now it looks okay
[saving current Lisp image into cmc:
writing 5952 bytes from the read-only space at 0x20000000
writing 4000 bytes from the static space at 0x20100000
writing 49545216 bytes from the dynamic space at 0x1000000000
done]
I think it does. In your implementation of `square`, you could use `gensym` to first create a new variable to store x in. Then you would write a second statement that evaluates to that variable times itself.
C++. Too many other languages get wrapped up in ideology. D comes pretty close. I can't get past Rusts unreadability. I'd like to give Nimrod a try... especially since it can be compiled to C and presumably integrated in to C++ projects.
[Edit] Read Nimrod tutorials and ported a few toy programs. It's encouragingly clean and doesn't seem to shy away from features... I wonder if Alex Stepanov knows that, in Nimrod, "if you overload the == operator, the != operator is available automatically and does the right thing" ;)
There are many people here, from many backgrounds, with many different perspectives. Almost all languages have proponents here (with COBOL the possible exception), and all languages have detractors here.
C macros are a cute but dangerous hack. But this software implements Lisp macros for C. Lisp-style macros are safe and nonhacky. The main feature that allows this is `gensym`, which lets you store values in variables that are guaranteed to not name-clash.
Also, the “goto fail” issue had nothing to do with macros. So people might hate C’s block syntax while loving other features such as macros. People can hate parts of a language and like other parts. Like the author of the book JavaScript: The Good Parts, who likes JavaScript, but recommends against using certain features of it.
[+] [-] stevelosh|12 years ago|reply
1. Suppose you've written a function 'foo' in your language that does something useful (e.g.: partitions a sequence, validates a map, or pretty much anything really).
2. Some time later you want to write a macro 'bar'.
3. Can you use function 'foo' when writing 'bar'?
If the answer to 3 is "no" then you don't really have Lisp-like macros.
This is what makes Lisp macros so powerful. It's not just that you have a way to mangle abstract syntax trees. If that's all you want then yeah, you can write a parser and template language to do it, like this thing or sweet js, but it's not the same.
Lisp macros are beautiful because there's no real divide between "writing code" and "writing macros". It's all just code. You don't have to worry about which things are available in code-land and which are available in macro-land because they're the same place. There's just "the language" which you extend and mold with functions and macros woven together as necessary.
Contrast this with a language without real Lisp-like macros, like Clojurescript. If I want to write the 'bar' macro in Clojurescript I need to think "wait 'foo' is a Clojurescript function so now I need to port it back into Clojure land before I can use it in this macro because macros in Clojurescript live in Clojure-land not Clojurescript-land". I need to think about this for everything I call inside a macro.
(Admittedly the situation isn't as bad in Clojurescript because it's fairly close to Clojure in syntax and it's possible to cross-compile code so it lives in both "lands", but the ugly divide is still there.)
Common Lisp, Clojure, Scheme, Wisp, Julia, etc have Lisp-like macros. Sweet JS, Clojurescript, C, etc don't.
[+] [-] groovy2shoes|12 years ago|reply
Viewing Lisp macros as an arbitrary function from syntax to syntax is fine, but such macros are not very Scheme-y. It's very difficult to have such macros and guarantee they're hygienic, which is why syntax-rules limits you to matching patterns and producing templates -- it effectively limits the kinds of functions which can be macros.
Other types of Scheme macros (which are non-standard), like explicit renaming and syntactic closures, require programmers to opt-in to hygiene. syntax-case (which is in R6RS) allows programmers to break hygiene by jumping through hoops, but is otherwise similar to TFA's system as well.
So if Scheme is a Lisp and these macros are like Scheme macros, I don't think it's inaccurate to call them Lisp-like macros. But it is imprecise.
As an aside, I've written a compiler for a Lisp-like language. Speaking from experience, getting the compiler to answer "yes" to 3 is non-trivial (even though it's dead-simple in an interpreter). I suppose that's why Racket has all the phase distinctions it has.
[+] [-] regularfry|12 years ago|reply
[+] [-] unknown|12 years ago|reply
[deleted]
[+] [-] tomp|12 years ago|reply
It's still in development, so there are parts of the system that don't work very well (e.g. global variables in compiled executables cannot be statically initialized), but it's a very interesting project.
[1] http://terralang.org/
[2] https://github.com/zdevito/terra/blob/master/tests/lib/golik...
[3] https://github.com/zdevito/terra/blob/master/tests/lib/javal...
[+] [-] sea6ear|12 years ago|reply
[+] [-] atrilumen|12 years ago|reply
[+] [-] terhechte|12 years ago|reply
Clang already does that for the C preprocessor, although that is probably much easier to do since it is not Turing complete [1]
[1] By definition. Though some people throw crazy things at it: http://stackoverflow.com/questions/3136686/is-the-c99-prepro...
[+] [-] eudox|12 years ago|reply
As for integration: It's pretty simple to operate from the command line, which is good enough for integration with eg. a Makefile-built project.
[+] [-] gavinpc|12 years ago|reply
Is this the OP's analogy? It's quite interesting.
[+] [-] nnq|12 years ago|reply
[+] [-] mrottenkolber|12 years ago|reply
"Growing the language toward your problem" is a common Lisp design pattern.
[+] [-] AnimalMuppet|12 years ago|reply
Lisp has this idea of creating a language in which you can solve your problem. That's fine, but the Lisp people mean something rather different than the rest of us do.
When I "create a language to solve a problem" in, say, C++, I create some nouns (objects), some verbs (methods), maybe some adjectives (other objects or flags) and adverbs (more flags). Then I can write my application using that "language", but using normal C/C++ syntax.
When Lisp people say "create a language to solve a problem", they mean "write completely different syntax". The article gives some examples.
Why would I want to do that? Well, I might want to explore a new paradigm - some new thing like aspect oriented programming, say. I don't have to wait for a language that implements it, I can just do it myself. This is great for academic research projects, but not nearly so great for production code. (Bringing new people up to speed becomes a much longer process, if your code outlives the original developers.)
But I might have to create new language features just to get anything done in Lisp. One example is LOOP. It's a macro, because Lisp without macros doesn't have very good looping ability. "It's a building material" partly in this sense: It's not a very good tool. It's not all that usable until you add the parts that, in most other languages, you already get.
Now, does LOOP do more than a C-style for or while loop? I doubt it, but perhaps it does it more neatly. But C gives you 90% of what you need without a macro, whereas Lisp gives you 10% without a macro. If you need more in C, you can do it, even if it's a bit clumsy. But in Lisp without macros, it's horribly clumsy all the time.
(Yes, I know that there are Lisp and Haskell types who seem to regard writing a for loop over a container as a great waste of programmer time. I think that they are mistaken.)
You can do some amazing things with Lisp macros. Paul Graham gives the example of writing an extension language for Viaweb that Lisp macros turned into Lisp code, which was then run on the server. That's really slick (though in the current situation, you have to watch out for security issues unless you validate that file very carefully). But if he had chosen another approach, what would change? The macros have to turn the file syntax into Lisp syntax. If he had written an ordinary parser, he would have had to do the same. The only difference is that, by using macros, he let the Lisp compiler do the grunt work of the parsing.
TL;DR: Lisp needs macros to be usable. I'm not convinced that C does.
[+] [-] norswap|12 years ago|reply
Although I'll acknowledge the criticism made elsewhere in the comments: this is not really usable due to lack of tooling support.
[+] [-] fixermark|12 years ago|reply
In the context of C, however, you're up against a language (the preprocessor) that itself has only bare-minimum tooling support, so you've got a leg-up over similar projects in that regard. :)
[+] [-] canweriotnow|12 years ago|reply
1) You do not write macros.
2) You do not write macros that violate expectations of normal code behavior.
3) If this is your first time, you must write a macro.
4) Don't think object-oriented.
5) No shirt, No shoes, No dynamic scope.
6) Don't create new scoping rules.
[1] http://stuartsierra.com/download/2010-10-23-clojure-conj-mac...
[+] [-] geon|12 years ago|reply
http://voodoo-slide.blogspot.se/2010/01/amplifying-c.html
[+] [-] arh68|12 years ago|reply
[+] [-] 1ris|12 years ago|reply
#define square(x) ((x)*(x))
breaks when called with a++ as argument. Does this pre processor address this problem?
[+] [-] AnimalMuppet|12 years ago|reply
[+] [-] roryokane|12 years ago|reply
[+] [-] jderick|12 years ago|reply
[+] [-] Dewie|12 years ago|reply
Maybe Nimrod?
[+] [-] nly|12 years ago|reply
[Edit] Read Nimrod tutorials and ported a few toy programs. It's encouragingly clean and doesn't seem to shy away from features... I wonder if Alex Stepanov knows that, in Nimrod, "if you overload the == operator, the != operator is available automatically and does the right thing" ;)
[+] [-] CountHackulus|12 years ago|reply
[+] [-] lucian1900|12 years ago|reply
[+] [-] danieltillett|12 years ago|reply
[+] [-] SixSigma|12 years ago|reply
[+] [-] aryastark|12 years ago|reply
Macros need to be tossed into the dustbin of history, right next to self-modifying code and other cute but dangerous hacks.
[+] [-] AnimalMuppet|12 years ago|reply
There are many people here, from many backgrounds, with many different perspectives. Almost all languages have proponents here (with COBOL the possible exception), and all languages have detractors here.
[+] [-] roryokane|12 years ago|reply
Also, the “goto fail” issue had nothing to do with macros. So people might hate C’s block syntax while loving other features such as macros. People can hate parts of a language and like other parts. Like the author of the book JavaScript: The Good Parts, who likes JavaScript, but recommends against using certain features of it.