开发者

Can a language have Lisp's powerful macros without the parentheses?

Can a language have Lisp's powerful macros without the pare开发者_如何转开发ntheses?


Sure, the question is whether the macro is convenient to use and how powerful they are.

Let's first look how Lisp is slightly different.

Lisp syntax is based on data, not text

Lisp has a two-stage syntax.

A) first there is the data syntax for s-expressions

examples:

(mary called tim to tell him the price of the book)

(sin ( x ) + cos ( x ))

s-expressions are atoms, lists of atoms or lists.

B) second there is the Lisp language syntax on top of s-expressions. Not every s-expression is a valid Lisp program.

(3 + 4)

is not a valid Lisp program, because Lisp uses prefix notation.

(+ 3 4)

is a valid Lisp program. The first element is a function - here the function +.

S-expressions are data

The interesting part is now that s-expressions can be read and then Lisp uses the normal data structures (numbers, symbols, lists, strings) to represent them.

Most other programming languages don't have a primitive representation for internalized source - other than strings.

Note that s-expressions here are not representing an AST (Abstract Syntax Tree). It's more like a hierarchical token tree coming out of a lexer phase. A lexer identifies the lexical elements.

The internalized source code now makes it easy to calculate with code, because the usual functions to manipulate lists can be applied.

Simple code manipulation with list functions

Let's look at the invalid Lisp code:

(3 + 4)

The program

(defun convert (code)
   (list (second code) (first code) (third code)))

(convert '(3 + 4)) -> (+ 3 4)

has converted an infix expression into the valid Lisp prefix expression. We can evaluate it then.

(eval (convert '(3 + 4))) -> 7

EVAL evaluates the converted source code. eval takes as input an s-expression, here a list (+ 3 4).

How to calculate with code?

Programming languages now have at least three choices to make source calculations possible:

  1. base the source code transformations on string transformations

  2. use a similar primitive data structure like Lisp. A more complex variant of this is a syntax based on XML. One could then transform XML expressions. There are other possible external formats combined with internalized data.

  3. use a real syntax description format and represent the source code internalized as a syntax tree using data structures that represent syntactic categories. -> use an AST.

For all these approaches you will find programming languages. Lisp is more or less in camp 2. The consequence: it is theoretically not really satisfying and makes it impossible to statically parse source code (if the code transformations are based on arbitrary Lisp functions). The Lisp community struggles with this for decades (see for example the myriad of approaches that the Scheme community has tried). Fortunately it is relatively easy to use, compared to some of the alternatives and quite powerful. Variant 1 is less elegant. Variant 3 leads to a lot complexity in simple AND complex transformations. It usually also means that the expression was already parsed with respect to a specific language grammar.

Another problem is HOW to transform the code. One approach would be based on transformation rules (like in some Scheme macro variants). Another approach would be a special transformation language (like a template language which can do arbitrary computations). The Lisp approach is to use Lisp itself. That makes it possible to write arbitrary transformations using the full Lisp language. In Lisp there is not a separate parsing stage, but at any time expressions can be read, transformed and evaluated - because these functions are available to the user.

Lisp is kind of a local maximum of simplicity for code transformations.

Other frontend syntax

Also note that the function read reads s-expressions to internal data. In Lisp one could either use a different reader for a different external syntax or reuse the Lisp built-in reader and reprogram it using the read macro mechanism - this mechanism makes it possible to extend or change the s-expression syntax. There are examples for both approaches to provide a different external syntax in Lisp.

For example there are Lisp variants which have a more conventional syntax, where code gets parsed into s-expressions.

Why is the s-expression-based syntax popular among Lisp programmers?

The current Lisp syntax is popular among Lisp programmers for two reasons:

1) the data is code is data idea makes it easy to write all kinds of code transformations based on the internalized data. There is also a relatively direct way from reading code, over manipulating code to printing code. The usual development tools can be used.

2) the text editor can be programmed in a straight forward way to manipulate s-expressions. That makes basic code and data transformations in the editor relatively easy.

Originally Lisp was thought to have a different, more conventional syntax. There were several attempts later to switch to other syntax variants - but for some reasons it either failed or spawned different languages.


Absolutely. It's just a couple orders of magnitude more complex, if you have to deal with a complex grammar. As Peter Norvig noted:

Python does have access to the abstract syntax tree of programs, but this is not for the faint of heart. On the plus side, the modules are easy to understand, and with five minutes and five lines of code I was able to get this:

>>> parse("2 + 2")

['eval_input', ['testlist', ['test', ['and_test', ['not_test', ['comparison', ['expr', ['xor_expr', ['and_expr', ['shift_expr', ['arith_expr', ['term', ['factor', ['power', ['atom', [2, '2']]]]], [14, '+'], ['term', ['factor', ['power', ['atom', [2, '2']]]]]]]]]]]]]]], [4, ''], [0, '']]

This was rather a disapointment to me. The Lisp parse of the equivalent expression is (+ 2 2). It seems that only a real expert would want to manipulate Python parse trees, whereas Lisp parse trees are simple for anyone to use. It is still possible to create something similar to macros in Python by concatenating strings, but it is not integrated with the rest of the language, and so in practice is not done.

Since I'm not a super-genius (or even a Peter Norvig), I'll stick with (+ 2 2).


Here's a shorter version of Rainer's answer:

In order to have lisp-style macros, you need a way of representing source-code in data structures. In most languages, the only "source code data structure" is a string, which doesn't have nearly enough structure to allow you to do real macros on. Some languages offer a real data structure, but it's too complex, like Python, so that writing real macros is stupidly complicated and not really worth it.

Lisp's lists and parentheses hit the sweet spot in the middle. Just enough structure to make it easy to handle, but not too much so you drown in complexity. As a bonus, when you nest lists you get a tree, which happens to be precisely the structure that programming languages naturally adopt (nearly all programming languages are first parsed into an "abstract syntax tree", or AST, before being actually interpreted/compiled).

Basically, programming Lisp is writing an AST directly, rather than writing some other language that then gets turned into an AST by the computer. You could possibly forgo the parens, but you'd just need some other way to group things into a list/tree. You probably wouldn't gain much from doing so.


Parentheses are irrelevant to macros. It's just Lisp's way of doing things.

For example, Prolog has a very powerful macros mechanism called "term expansion". Basically, whenever Prolog reads a term T, if tries a special rule term_expansion(T, R). If it is successful, the content of R is interpreted instead of T.


Not to mention the Dylan language, which has a pretty powerful syntactic macro system, which features (among other things) referential transparency, while being an infix (Algol-style) language.


Yes. Parentheses in Lisp are used in the classic way, as a grouping mechanism. Indentation is an alternative way to express groups. E.g. the following structures are equivalent:

A ((B C) D)

and

A
    B
    C
  D


Have a look at Sweet-expressions. Wheeler makes a very good case that the reason things like infix notation have not worked before is that typical notation also tries to add precedence, which then adds complexity, which causes difficulties in writing macros.

For this reason, he proposes infix syntax like {1 + 2 + 3} and {1 + {2 * 3}} (note the spaces between symbols), that are translated to (+ 1 2) and (+ 1 (* 2 3)) respectively. He adds that if someone writes {1 + 2 * 3}, it should become (nfx 1 + 2 * 3), which could be captured, if you really want to provide precedence, but would, as a default, be an error.

He also suggests that indentation should be significant, proposes that functions could be called as fn(A B C) as well as (fn A B C), would like data[A] to translate to (bracketaccess data A), and that the entire system should be compatible with s-expressions.

Overall, it's an interesting set of proposals I'd like to experiment with extensively. (But don't tell anyone at comp.lang.lisp: they'll burn you at the stake for your curiosity :-).


Code rewriting in Tcl in a manner recognizably similar to Lisp macros is a common technique. For example, this is (trivial) code that makes it easier to write procedures that always import a certain set of global variables:

proc gproc {name arguments body} {
    set realbody "global foo bar boo;$body"
    uplevel 1 [list proc $name $arguments $realbody]
}

With that, all procedures declared with gproc xyz rather than proc xyz will have access to the foo, bar and boo globals. The whole key is that uplevel takes a command and evaluates it in the caller's context, and list is (among other things) an ideal constructor for substitution-safe code fragments.


Erlang's parse transforms are similar in power to Lisp macros, though they are much trickier to write and use (they are applied to the entire source file, rather than being invoked on demand).

Lisp itself had a brief dalliance with non-parenthesised syntax in the form of M-expressions. It didn't take with the community, though variants of the idea found their way into modern Lisps, so you get Lisp's powerful macros without the parentheses ... in Lisp!


Yes, you can definitely have Lisp macros without all the parentheses.

Take a look at "sweet-expressions", which provides a set of additional abbreviations for traditional s-expressions. They add indentation, a way to do infix, and traditional function calls like f(x), but in a way that is backwards-compatible (you can freely mix well-formatted s-expressions and sweet-expressions), generic, and homoiconic.

Sweet-expressions were developed on http://readable.sourceforge.net and there is a sample implementation.

For Scheme there is a SRFI for sweet-expressions, SRFI-110: http://srfi.schemers.org/srfi-110/


No, it's not necessary. Anything that gives you some sort of access to a parse tree would be enough to allow you to manipulate the macro body in hte same way as is done in Common Lisp. However, as the manipulation of the AST in lisp is identical to the manipulation of lists (something that is bordering on easy in the lisp family), it's possibly not nearly as natural without having the "parse tree" and "written form" be essentially the same.


I think this was not mentioned.

C++ templates are Turing-complete and perform processing at compile-time.

There is the well-known expression templates mechanism that allow transformations, not from arbitrary code, but at least, from the subset of c++ operators.

So imagine you have 3 vectors of 1000 elements and you must perform:

(A + B + C)[0] 

You can capture this tree in a expression template and arbitrarily manipulate it at compile-time.

With this tree, at compile time, you can transform the expression. For example, if that expression means A[0] + B[0] + C[0] for your domain, you could avoid the normal c++ processing which would be:

  1. Add A and B, adding 1000 elements.
  2. Create a temporary for the result, and add with the 1000 elements of C.
  3. Index the result to get the first element.

And replace with another transformed expression template tree that does:

  1. Capture A[0]
  2. Capture B[0]
  3. Capture C[0]
  4. Add all 3 results together in the result to return with += avoiding temporaries.

It is not better than lisp, I think, but it is still very powerful.


Yes, it is certainly possible. Especially if it is still a Lisp under the bonnet:

http://www.meta-alternative.net/pfront.pdf

http://www.meta-alternative.net/pfdoc.pdf


Boo has a nice "quoted" macro syntax that uses [| |] as delimiters, and has certain substitutions which are actually verified syntactically by the compiler pipeline using $variables. While simple and relatively painless to use, it's much more complicated to implement on the compiler side than s-expressions. Boo's solution may have a few limitations that haven't affected my own code. There's also an alternate syntax that reads more like ordinary OO code, but that falls into the "not for the faint of heart" category like dealing with Ruby or Python parse trees.


Javascript's template strings offer yet another approach to this sort of thing. For instance, Mark S. Miller's quasiParserGenerator implements a grammar syntax for parsers.


Go ahead and enter the Elixir programming language.

Elixir is a functional programming language that feels like Lisp with respect to macros, but it is on Ruby's clothes, and runs on top of the Erlang VM.

For those who do not like the parenthesis, but wish their language has powerful macros, Elixir is a great choice.


You can write macros in R (it have more like Algol Syntax) that have notion of delayed expression like in LISP macros. You can call substitute() or quote() to not evaluate the delayed expression but get actual expression and traverse its source code like in LISP. Even structure of the expression source code is like in LISP. Operators are first item in list. e.g.: input$foo which is getting property foo from list input as expression is written as ['$', 'input', 'foo'] just like in LISP.

You can check the ebook Metaprogramming in R that also show how to create Macros in R (not something you would normally do but it's possible). It's based on Article from 2001 Programmer’s Niche: Macros in R that explain how to write LIPS macros in R.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜