Code Comments
Programming Forum and web based access to our favorite programming groups.Long time programmer but new to Scheme and *really* new to macros .. I'm trying to write a program to teach Swarm Technology to kids using Scheme (chicken). The kids will write something like ,, (move nearer randomly at angle 45 using the 5 th closest particle) (move farther 7 at angle 0 using the 2 nd farthest particle) There's some syntactic sugar, like 'at angle', and 'nd'. I've completed the actual Scheme code that does the real work. It's controlled by one small function. I'd like to implement 'move' as a macro to generate this controlling function. I understand how to do the 'brute force' way i.e. make 'nearer', 'farther', closest, etc. .. literals in the syntax-case and use patterns to control the code generation. The problem is that the macro will grow exponentially in size as I add more parameters. Currently since there are 3 parameters with 2 options each ,, nearer / farther closest / farthest randomly / 7 .. that's 8 pattern/syntax statements .. not too bad but if I add two more options .. it's now 32. Ok .. So another approach would be to not use literals. The parameters are now variables (more standard). Now 'nearer' or 'farther' (for example) are actually functions which are mapped to functions in the currently working engine. This doesn't seem elegant and I would have to write new functions like (define (handle-randomly-or-number the-input) .. .to do the parsing of the operand which can either be 'randomly' or a number (randomly / 7 case from above). Ok .. Maybe it's time to write my own 'eval' .. Someday I'd like to do this but I've got to be done with all this by the 10th of May. Is there a simple way that I'm missing to do this? I'm not just looking for a hack to solve this current problem (as I can always use 'cut-copy-paste' to generate my 32 patterns/code sets) but also trying to learn to write elegant Scheme, Any help greatly appreciated, Tom
Post Follow-up to this messageI am no hygenic macro wiz, but I did notice that you are introducing a special syntax into your "move" command, which is a bit antithetical to the design of Lisp-like languages (in my humble opinion). Instead, I would be tempted to "factor out" the new syntax into standard scheme forms: (move (direction 'nearer 'randomly) (angle '45) (use (closest-particle 5))) ...or somesuch... The "direction", "angle", "use" and "closest-particle" functions could then be data generators that return rich data structures that are fed to the "move" function, no macro needeed. (Of course, these could also be macros, since it would avoid some quote marks) Doing it this way, you decouple the larger "move" command (and the parsing it would have to deal with) into smaller bits that are easily addressed separately. If you think the students would beby the extra parenetheses, you could still hava a parantheses-free design, but I wouldn't personally want to write it as a macro: Instead, I would have them create the commands in a separate data file, then read it in with a parser that breaks out the pieces and "parenthesizes" them and calls EVAL on the result. As the students become more experienced, they could learn to write commands in the more-flexible "paranethized" style directly as scheme forms Anyway, just my 2 cents, and perhaps not so helpful for addressing your central question :) -Conrad Barski
Post Follow-up to this messageTom P wrote: > (move nearer randomly at angle 45 using the 5 th closest particle) > (move farther 7 at angle 0 using the 2 nd farthest particle) Perhaps you might be willing to adjust your syntax slightly so it would look like (move :nearer 'randomly :angle 0 :with-particle 5) That is, using the keyword arguments. The order of keyword-value pairs is arbitrary. If your Scheme supports DSSSL extensions (as I think Chicken does), the keyword arguments are available and so you're done. If DSSSL extensions are not available, or if the change of the syntax is way too much (for example, you would like to use 'randomly without the quote [*]), you may want to look at http://pobox.com/~oleg/ftp/Scheme/m...ml#keyword-args which shows how to add keyword arguments to any function or a *macro*. DSSSL extensions cannot be used for hygienic macros. Also, your languages doesn't have to have keywords: symbols or any type of data whatsoever will suffice as a label. For example, numbers can be labels too. The technique of course permits assigning defaults to some arguments. So, you can indeed write something like (move farther 7 angle 0 using the 2 nd farthest particle) Here `farther', `angle', `using', `2', `farthest' are labels, and `7', `0', `the', `nd' and `particle' are the corresponding values. [*] If one wishes to write randomly without a quotation, one merely needs to add (define randomly 'randomly) and so can use that `name' with or without the quotation. Actually, I have tried this out, using the code in Appendix B of the keyword-arg-macro.txt article, to which I added (gen:define-labeled-arg-macro move (move-positional ; positional procedure ; the following are the descriptors for lookup's three arguments ; the order corresponds to the positions of move-positional (farther #f) ; optional, #f is default value (nearer #f) (angle) ; required, no default (using) ; required (1 #f) ; some numerical labels (2 #f) (3 #f) (farthest #f) (nearest #f) )) (define the 'the) (define randomly 'randomy) (define nd 'nd) (define particle 'particle) ; note that the order corresponds to the order of the descriptors above (define (move-positional farther nearer angle using arg1 arg2 arg3 farthest nearest) (for-each display (list (if farther (list 'farther farther) (if nearer (list 'nearer nearer) (error "no direction"))) " at angle " angle " using the particle number: " (cond (arg1 1) (arg2 2) (arg3 3)) " which is " (cond (farthest "farthest") (nearest "nearest")) #\newline))) (move farther 7 angle 0 using the 2 nd farthest particle) ; The output: ; (farther 7) at angle 0 using the particle number: 2 which is farthest tried using Scheme48, SCM and Petite Chez Scheme.
Post Follow-up to this messageTom P wrote: > I understand how to do the 'brute force' way i.e. > make 'nearer', 'farther', closest, etc. .. literals in > the syntax-case and use patterns to control the code > generation. > > The problem is that the macro will grow exponentially > in size as I add more parameters. Currently since there > are 3 parameters with 2 options each ,, > > nearer / farther > closest / farthest > randomly / 7 > > .. that's 8 pattern/syntax statements .. not too bad > but if I add two more options .. it's now 32. > > Ok .. You can break this problem down into several macros that compose with each other, instead of having one big (exponentially large) macro. For example, you could have your basic macro take the args 'nearer' and 'further', and generate code that then invokes a second macro on the remaining arguments, where the second macro takes the args 'closer' and 'further' and invokes a third macro that it passes the remaining arguments to. This way, you can create one two-case macro per argument position instead of one macro with a case for every combination of arguments. Bear
Post Follow-up to this message>>>>> "Oleg" == oleg <oleg@pobox.com> writes: Oleg> Tom P wrote: Oleg> Perhaps you might be willing to adjust your syntax slightly so Oleg> it would look like Oleg> (move :nearer 'randomly :angle 0 :with-particle 5) Oleg> That is, using the keyword arguments. The order of Oleg> keyword-value pairs is arbitrary. If your Scheme supports Oleg> DSSSL extensions (as I think Chicken does), the keyword Oleg> arguments are available and so you're done. Chicken does have keywords, though by default it uses Smalltalk-style keywords instead of CL-style, e.g. (move nearer: 'randomly angle: 0 with-particle: 5) which I think is easier to read since the : separates the name and the value. This is easier to implement (no parsing involved) and more consistent and therefore easier to program in, however if you really want the original syntax an alternative without macros is just to define the keywords as variables: (define nearer 'nearer) (define farther 'farther) (define at 'at) .. which lets you define move as a normal procedure that can work with your original examples exactly: (move nearer randomly at angle 45 using the 5 th closest particle) Note, this same general approach can be used to add non-macro keyword interfaces to implementations without keywords: (define nearer: 'nearer:) .. and the keywords work as expected, i.e. as though they were self-evaluating, so that (make-animal name: "cat" color: "black") works the same as (apply make-animal '(name: "cat" color: "black")) -- Alex
Post Follow-up to this messageI (Tom P) wrote: > I'm trying to write a program to teach Swarm Technology > to kids using Scheme (chicken). > The kids will write something like ,, > (move nearer randomly at angle 45 using the 5 th closest particle) > (move farther 7 at angle 0 using the 2 nd farthest particle) > I've completed the actual Scheme code that does the real work. > It's controlled by one small function. I'd like to implement 'move' > as a macro to generate this controlling function. Thanks everyone for the great replies! Sorry for my tardy response. There are two (at least) meta-things I'm learning about Scheme .. 1) The Scheme community is anazingly helpful. 2) Scheme is extremely adaptable (will I ever be able code in Java, Ruby, Smalltalk again?) The scope of the replies made me realize that I've really been thinking of the power of Scheme in my perceived-from-textbook-box: - Functions (I started here) - Functions as Arguments (I foundplaces to do this) - Macros (I coded these which led to my post) - Write ad hoc 'eval-loop' (I haven't done this yet) drcode suggested: > I would be tempted to "factor out" the new syntax into > standard scheme forms: > (move (direction 'nearer 'randomly) > (angle '45) > (use (closest-particle 5))) This seems cleaner and simpler as macros introduce an additional level of complexity but complicate the syntax for the kids a bit. A non-macro solution would also seem to introduce a (perhaps small) performance overhead as 'randomly' or 7 (for example) need to be analyzed at run time. drcode also suggested: > create the commands in a separate data file, then > read it in with a parser that breaks out the pieces and "parenthesizes" > them and calls EVAL on the result. This would provide a level of decoupling (this is good) but I'm really bad at writing parsers. oleg suggested keyword macros: > Perhaps you might be willing to adjust your syntax slightly so it > would look like > (move :nearer 'randomly :angle 0 :with-particle 5) I didn't know this was available but I wanted it as I was coding the macros (The IBM 360/370 assembler had these in 1969 which I loved). Thanks for the ad hoc code fragments. Alex said: > Chicken does have keywords, though by default > it uses Smalltalk-style keywords instead of > CL-style, e.g. > (move nearer: 'randomly angle: 0 with-particle: 5) > which I think is easier to read since the : separates > the name and the value. > This is easier to implement (no parsing involved) and > more consistent and therefore easier to program in, Ah, no parsing. I do think the kids would respond to the keyword approach. I'm ashamed to say that I spent about 3 minutes designing the syntax (because I wanted to code!). I guess I need to reread all my UI books. Bear suggested: > You can break this problem down into several macros > that compose with each other, instead of having one > big (exponentially large) macro. Another great idea .. this seems quite powerful and gets rid of my exponential growth problem. I wish I had time to try all the suggestions now but I'm really under the gun to get the material (and coding) completed by next w
.. After that I should have some time. I'm *very* impressed with Scheme .. [Warning: Slightly OT material follows] I broke the softwareinto two programs, the engine (creates a file) and the SDL based viewer (displays the file to the screen) ,, Obviously I'm an old MVC guy :-) I was amazed at the compactness of the Scheme code .. 275 LOC for the engine 125 LOC for the brute force macro 140 LOC for the SDL player . and it's *very* fast. I've remastered a Knoppix CD to include the Scheme particle code so the kids can take it home and play with it. Remastering is quite easy if you use this script .. http://www.knoppix.net/forum/viewtopic.php?t=12530 Here's a movie generated by the code using the following two rules .. (move farther randomly at angle 0 using the 0 th nearest particle) (move nearer randomly at angle 0 using the 0 th farthest particle) http://softcomp.com/scheme/particles/movies/simple.mpeg [Slightly OT material ends] On my quest to learn Scheme I hope to try all your suggestions in the next couple of w
s. Again thanks everyone for the excellent responses. Tom
Post Follow-up to this messagePowered by vBulletin
Copyright 2000-2006 Jelsoft Enterprises Limited.