Why lisp?

Started by
103 comments, last by Vlion 20 years, 1 month ago
quote:
The C preprocessor does not provide anything like the abilities of the Lisp macro system. For starters, C macros cannot perform environment capture, which means WITHLOCKEDSURFACE is unlikely to be hygienic. This is really why people wishing to see the benefits of Lisp need to go and do some learning - it''s no good producing syntactic similarities and assuming that the semantics are the same.


For macros, the C preprocessor just expands the macros inline. That macro would come out to the *exact* code that was used in the first C example, and I don''t see any way you could break it. There is no need to ''capture'' the environment, the code is unrolled in-place before it is compiled, it would work just like I typed the original C routine. Perhaps they dont work exactly the same underneath as the Lisp macros; I would hope not, being as this is C, and that is Lisp. But in all likelihood, the outcome would be exactly the same. Results are what matter, not semantics.

Peace
Advertisement
quote:Original post by Krippy2k
Results are what matter, not semantics.

Exactly. But then you're using this as the only example of using Lisp and C macros towards an end which might lead you to make some erroneous assumptions about the power of lisp macros.

Wicki has some length on macros though I'm not sure it's particularly helpful.

[edited by - woodsman on February 25, 2004 10:51:33 AM]
If a plant cannot live according to its nature, it dies; so a man.
I wasnt using it as an only example, that was just the only lisp example that has been given. I am certain that there are better examples, thats why I said I didnt think it was the best example

At the rate I''m going I''ll get to the macros section in On Lisp in about oh.... 19 years... lol... just kidding, hopefully in a few months I''ll have a better grasp of it. I have so many things going on I can only commit a few hours a week to it. It would be nice if the various abstracts asserted in this thread hold water in the face of a real problem.

Peace
Ya.
Interesting thread, overall.
I''m going to stop replying now.

Anaphora, in linguistics:
#
In the following sequence, the relationship of the pronoun he to the noun phrase a well-dressed man is an example of anaphora:
# A well-dressed man was speaking; he had a foreign accent.

~V'lionBugle4d
quote:Original post by Krippy2k
For macros, the C preprocessor just expands the macros inline. That macro would come out to the *exact* code that was used in the first C example, and I don't see any way you could break it. There is no need to 'capture' the environment, the code is unrolled in-place before it is compiled, it would work just like I typed the original C routine.

One problem is that, when a macro is expanded (not just the current example, but in general), you may be clashing with the local environment. To avoid this, you need to start employing token-pasting to implement an ad-hoc name-mangling scheme. The complexity of the macro goes up, and there is still not a firm guarantee that potential clashes are resolved. This danger also exists in Lisp, but there are mechanisms provided explicitly to avoid this danger. Another point is that the full power of Lisp is available within a macro, whereas C/C++ employ a limited language with a completely different syntax to the host language.
quote:
Perhaps they dont work exactly the same underneath as the Lisp macros; I would hope not, being as this is C, and that is Lisp. But in all likelihood, the outcome would be exactly the same.

Only if you ignore the semantics.
quote:
Results are what matter, not semantics.

Anyone who convinces themselves of this is walking a very risky path. This can lead to all manner of perverse rationalisations, such as ``it doesn't matter if the code is a mess, as long as it works''. It almost sounds like the Turing equivalency argument restated: ``my language can compute anything yours can''. This is not the point. We're having a debate about expressivity, which means it's exactly the semantics which are important.


[edited by - SabreMan on February 25, 2004 12:43:37 PM]
It is my impression that many of the comments posted to this thread have to do with very local particulars of Lisp and its competitors (mostly C++), or with personal preferences, especially about flexibility of the programming languages. Qualitative arguments of this sort can be told in either direction (C's freedom vs. Pascal's constraint, or is that... C's anarchy vs. Pascal's order?).

Personally, I am much more interested in productivity. How fast can programs be written? How fast/how big are the programs when executing? How complex/long is the resulting code? How bug-ridden are they likely to be? How maintainable are they? Such questions cannot be answered directly by appealing to personal taste.

Having only been exposed to Lisp long ago, I found the Lisp adherents' comments here thought-provoking. I searched online for comparative studies (preferably with differences quantified) of Lisp vs. _____. Here are some of the things I found online:

http://www.flownet.com/gat/papers/lisp-java.pdf
http://userpages.umbc.edu/~bcorfm1/C++-vs-Lisp.html
http://www.ai.mit.edu/~gregs/ll1-discuss-archive-html/msg00310.html
http://www.paulgraham.com/paulgraham/avg.html

-Predictor
http://will.dwinnell.com





[edited by - Predictor on February 25, 2004 1:36:23 PM]
i still have hard time with lisp and still a total noob but one things which blow me up is that i could write a program which could continue write himself and even auto debug, i''m not sure this could be done efficiently in other language i have see (but i''m not an expert, i''m an artist , i''m using programmation as an extansion tool of creation)



>>>>>>>>>>>>>>>
be good
be evil
but do it WELL
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>be goodbe evilbut do it WELL>>>>>>>>>>>>>>>
quote:
Anyone who convinces themselves of this is walking a very risky path. This can lead to all manner of perverse rationalisations, such as ``it doesn''t matter if the code is a mess, as long as it works''. It almost sounds like the Turing equivalency argument restated: ``my language can compute anything yours can''. This is not the point. We''re having a debate about expressivity, which means it''s exactly the semantics which are important.


I think maybe we are in different debates I''m not having a debate about expressivity, I''m having a debate about power and usefulness. Semantics and expressivity are only useful to me if they help solve the problem more efficiently. More efficiently meaning efficiently developing the product to solve the problem. And if somebody is going to demonstrate that the semantics of a particular method are more useful than another method, more than just the interface needs to be presented. We can''t compare semantics based only on an interface.

Peace
quote:Original post by Krippy2k
I think maybe we are in different debates

Actually, you''re right. I thought I was in a different thread when I wrote that. Normally, I''d be paying closer attention! However, the comment about expressivity isn''t entirely irrelevant. If someone tries to show an example of the ``power of Lisp macros'''', what is implied is the expressivity of Lisp macros. That is, expressive power. It is easy to find something which achieves something equivalent in another language, but it''s the full semantics of the examples which are important. There''s a lot hidden in the Lisp macro examples which will not be apparent to someone who has not worked with Lisp, since Lisp macros really are unique.
quote:
I''m not having a debate about expressivity, I''m having a debate about power [...]

What other power is there beyond expressive power?

I think I can explain this to all the C++ guys out there.

Recently, I was doing some OpenGL programming. In OpenGL, to render, you first set the rendering state, like this:

glEnable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
etc

Then, you call "glDrawElements" or "glDrawArrays" to render something. The rendering state consists of about a bazillion miscellaneous flags and parameters, so the actual amount of code needed to set up the rendering state can be quite large.

Setting the rendering state can be slow on many implementations of OpenGL, so rather than just calling gldisable or glenable, it's important to wrap each piece of state-setting in a conditional:

if (lighting_enabled) {
glDisable(GL_LIGHTING);
lighting_enabled = false;
}

If you accidentally forget to set up some part of the rendering state, the result is an extremely-hard-to-diagnose bug. For example, let's say I don't want the stencil buffer, but I forget to say:

glDisable(GL_STENCIL_BUFFER);

The result will be that every once in a blue moon, the image will be incomprehensibly missing or mangled, and no error message will be returned. This happens daily. There is so much opengl state that I almost always used to forget something, or used to configure some part incorrectly. Almost every time I wrote some new rendering code, it suffered from inexplicable artifacts until I found the bug in the state-setting code.

The fact that this code was so verbose and so unreliable indicates that clearly, I wasn't expressing myself in the best possible way. So at some point, I asked myself, "what's the right way to express this?" The answer I came up with is:

1. There will be a list of rendering modes. Rendering will consist of picking one of the modes by name, and then rendering polygons.

2. When I use a rendering mode, it should completely override all previous state of the opengl pipeline. In other words, each rendering mode *completely* specifies the state, guaranteeing deterministic behavior.

3. When I activate a rendering mode, it should not alter parts of the opengl pipeline that are already configured correctly, for speed.

3. When I define a rendering mode, I shouldn't have to mention all the parts of the opengl pipeline, only the parts I'm using. The other parts should be implicitly disabled or set to reasonable defaults.

The code I wrote reads in a configuration file that defines the rendering modes. The configuration file contains definitions like this:

define SPRITE_PARAMETERS
parameters mat $PROJ mat $MODV mat $CUBEM vecq $LIGHTPOS vecq $MSLIGHT vecq $MSEYE abgr $TINT abgr $LTINT
parameters int $NVERT arr $VA arr $NA arr $TA arr $CA arr $SA int $NIDX arr $IA tex $TEX tex $BUMP
enddefine


mode sprite_glass_A0
SPRITE_PARAMETERS
alphatest off
blending standard
zbuffer standard
cull backfaces
fragment arbfp10 $TINT $LTINT
TEMP t, tex0, diffuse, ndiffuse, brighter;
PARAM scale = {4,2,1,0.5};
PARAM specpow = {0,0.333,0,0};
TEX tex0, fragment.texcoord[0], texture[0], 2D;
DP3 t, tex0, specpow.yyyz;
MUL t, t, program.env[1];
MAD tex0.rgb, program.env[1].a, t, tex0;
MAD t, fragment.color.primary, scale.x, -scale.y;
MOV_SAT diffuse, t.z;
MOV_SAT ndiffuse, -t.z;
SUB t,scale.z,tex0;
MUL brighter,t,t;
MUL brighter,brighter,t;
SUB brighter,scale.z,brighter;
MAD t,ndiffuse,-scale.w,scale.z;
MUL tex0.rgb, tex0, t;
LRP result.color, diffuse, brighter, tex0;
MOV result.color.a, tex0.a;
END
colormask off
vertex simple $PROJ $MODV
data simple triangles $NVERT arr vtx $VA arr tex0 $TA arr col $CA idx uint $NIDX $IA
texture simple $TEX lin clod -0.5 mip wrap
endmode

This is a definition of the macro SPRITE_PARAMETERS, followed by a mode definition for rendering glass. As you can see, it doesn't mention the stencil buffer - implicitly, this rendering mode operates with the stencil buffer off.

The first lines of the mode are a list of parameters that must be passed into the rendering mode: a projection matrix, a modelview matrix, some light positions, some constant colors, some textures, and so forth. To actually render, you call the C function GscriptRender with the name of the mode, and the parameters to the rendering mode:

GscriptRender("sprite_glass_A0",
MatrixP, MatrixM, MatrixT,
lightpos, mslightvec, mseyevec, color, scenetint,
vcount, vertices, normals, texcoords, colors, scolors,
icount, indices, tex, bump);

The important thing about this notation is that it is error checked. The code that processes the mode definition makes sure that every piece of the OpenGL pipeline has been configured, and it does a few sanity checks for common inconsistencies.

This is the right notation for the job. My error rate has dropped to almost zero - when it was almost impossible to get things right before. I can add new rendering modes effortlessly.

To make it happen, though, I had to write a whole fricking compiler. I had to write a lexical analyzer, a parser, a macro preprocessor, parameter handling code, I had to create a "pcode" representation to store the compiled code in, and I had to write a pcode interpreter. It took a month.

In lisp, adding little languages is easy. In lisp, I could have written:

(define-rendering-mode sprite-glass-A0 (proj modv ...)
(alphatest off)
(blending standard)
(zbuffer standard)
etc...)

The construct "define-rendering-mode" would be a macro that would expand into a definition of a function, in this case, sprite-glass-A0. Since sprite-glass-A0 is a function in its own right, I wouldn't need the "GscriptRender" function that takes a variable number of arguments, instead, I'd call:

(sprite-glass-A0 proj modelview ...)

The amount of code needed would have been vastly less than in C++: Lisp already includes the parser, it already includes the macro preprocessor, it already implements the parameter handling, it already has a built-in "pcode" notation (lisp itself), it already includes a built-in pcode interpreter. The only thing I would actually have had to write would be the code to emit the opengl calls. This would have taken 2 or 3 days in Lisp.

In summary: "little languages" are an important programming tool. Lisp is specifically designed for extension with little languages, it includes all the features needed to add them easily. Having to write a whole compiler each time you want a little language discourages little languages, nearly eliminating a powerful tool from the programmer's toolbox.


[edited by - Nekhmet on February 28, 2004 3:29:35 PM]

This topic is closed to new replies.

Advertisement