Home > Archive > Scheme > May 2005 > Newbie Macro Question
You are viewing an archived Text-only version of the thread.
To view this thread in it's original format and/or if you want to reply to
this thread please [click here]
| Author |
Newbie Macro Question
|
|
|
| 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
| |
| drcode@gmail.com 2005-05-01, 9:10 pm |
| I 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 be by 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
| |
| oleg@pobox.com 2005-05-01, 9:10 pm |
|
Tom 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.
| |
| Ray Dillinger 2005-05-01, 9:10 pm |
| Tom 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
| |
| Alex Shinn 2005-05-02, 4:00 am |
| >>>>> "Oleg" == oleg <oleg@pobox.com> writes:
Oleg> Tom P wrote:[color=darkred]
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
| |
|
| I (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 found places 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
|
|
|
|
|