[Prev][Next][Index][Thread]

Re: Critique this macro, please



On Mon, 18 Dec 2000 11:15:02 -0500 (EST), dauclair@hotmail.com wrote:

> So, I'm trying to create a function macro that returns a deferred
> function.  Or, in functional terms:
> 
> begin
>   with("foo.txt", do: parse-foo);
> end;
> 
> define method parse-foo(a-line :: <string>)
>   *foo* := a-line;
> end method parse-foo;
> 
> (Where "with" reads in a file one line at a time and drops lines
> starting with '#').  I have a bunch of these parse-foo's so I wanted to
> create a macro, thus:
> 
> define macro parse-string
>   { parse-string(?var:variable) }
>     => { method(x) ?var := x; end }
> end macro parse-string;
> 
> ... and then call it like so:
> 
> begin
>   with("foo.txt", do: parse-string(*foo*));
> end;
> 
> The problem is that the compiler (d2c) complains:
> 
> No applicable methods in call of {GF build-defn-ref} when given
> arguments:

Ironically, FunDev also errors internally with:

Error: 
  No applicable method, applying {generic function call-as-fragment-tokens} to
  #[#P{ with ("foo.txt", do: parse-string(*foo*)) }].

but this is when I misdefine "with" without the "()" you use in your example:

  define macro with { with ?file:expression, do: ?func:expression } => ...

When I define it properly then FunDev parses the example fine:

  define macro with { with (?file:expression, do: ?func:expression) } => ..

BTW I don't think that this is idiomatic Dylan. I'd prefer a macro as follows
(renamed to be more self-documenting):

  define macro with-each-line
    { with-each-line ( ?line:name = ?file:expression ) ?body:body end }
    =>
    { // capture ?body in method with ?line as parameter and call
      // on each line read from ?file }
  end macro;

then use it as follows:

  with-each-line ( line = "foo.txt )
    parse-foo( line );
  end;

Quite often macro designers arrange for their macro eg "with-each-line" to
expand into a function which takes the captured a body of code in a closure. 

  define macro with-each-line
    { with-each-line ( ?line:name = ?file:expression ) ?body:body end }
    =>
    { 
      do-each-line( method ( ?line ) ?body end, ?file );
    }
  end macro;

  define method do-each-line ( func :: <function>, file :: <pathname> )
    // applies func to each line read from file
  end method;

The advantages of this style are:

(i) clients get to use the functional equivalent if that is more convenient; 

(ii) the Dylan compiler will be able to compile do-each-line in the designers
library and report problems, without waiting for some external client library
to use the macro;

(iii) it reduces the size of the macroexpansion, so if the macro is used a lot
then it doesn't bloat the application code;

(iv) debugging is easier, since there is a common function that can be
breakpointed or traced; and

(v) patching is easier, since it increases the chance that you only need to
replace the library implementing do-each-line.

The disadvantages are:

(i) The potential cost of closure creation and function call for small macros
where efficiency is important. You can inline the functional version (often
done), but it loses some of the advantages listed above.

(ii) "It is addictive." -- a quote from Scott McKay I think. :-j

__Jason


Follow-Ups: References: